iwmlib/lib/card/wrapper.js

196 lines
6.2 KiB
JavaScript
Raw Normal View History

2019-07-12 14:33:15 +02:00
/* eslint-disable no-console */
/* global TweenLite */
import Events from '../events.js'
import { Points } from '../utils.js'
2019-07-16 09:27:48 +02:00
export default class CardWrapper extends Object {
2019-07-30 16:56:29 +02:00
constructor(domNode, { triggerSVGClicks = true, allowClickDistance = 44 } = {}) {
2019-07-12 14:33:15 +02:00
super()
this.domNode = domNode
this.triggerSVGClicks = triggerSVGClicks
this.allowClickDistance = allowClickDistance
this.tapNodes = new Map()
this.tapHandler = new Map()
}
handleClicks() {
2019-07-18 12:26:39 +02:00
this.domNode.addEventListener(
'click',
event => {
if (event.isTrusted) {
Events.stop(event)
if (this.triggerSVGClicks && this.isSVGNode(event.target)) {
this.tap(event, 'triggerSVGClicks')
}
2019-07-12 14:33:15 +02:00
}
2019-07-18 12:26:39 +02:00
},
true
)
2019-07-12 14:33:15 +02:00
}
handleClicksAsTaps() {
2019-07-18 12:26:39 +02:00
this.domNode.addEventListener(
'click',
event => {
if (event.isTrusted) {
Events.stop(event)
}
this.tap(event)
},
true
)
2019-07-12 14:33:15 +02:00
}
2019-07-21 21:42:03 +02:00
isClickPrevented(node) {
if (node == null) {
return false
}
if (node.style && node.style.pointerEvents == 'none') {
return true
}
return this.isClickPrevented(node.parentNode)
}
2019-07-12 14:33:15 +02:00
isClickable(node) {
2019-07-18 12:26:39 +02:00
if (node == null) return false
2019-07-30 16:56:29 +02:00
// console.log("isClickable", node, this.isClickPrevented(node))
2019-07-21 21:42:03 +02:00
if (this.isClickPrevented(node)) {
return false
}
2019-07-18 12:26:39 +02:00
if (node.tagName == 'A' && node.hasAttribute('href')) return true
if (node.hasAttribute('onclick')) return true
2019-07-12 14:33:15 +02:00
return false
}
hasClickHandler(node) {
2019-07-18 12:26:39 +02:00
if (node == null) return false
if (this.tapNodes.has(node)) return true
2019-07-12 14:33:15 +02:00
for (let [selector, handler] of this.tapHandler.entries()) {
for (let obj of this.domNode.querySelectorAll(selector)) {
if (node == obj) {
return true
}
}
}
return false
}
/**
* Returns an array of all active nodes.
* Unfortunately we cannot search for all nodes with an attached 'click' event listener
* See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not
* Therefore we can only detect the following standard cases:
* I. All clickable objects like activeNodes
2019-07-18 12:26:39 +02:00
* II. Objects that have been attached a click handler by the scatter itself via
2019-07-12 14:33:15 +02:00
*/
activeNodes() {
let result = []
2019-07-18 12:26:39 +02:00
for (let node of this.domNode.querySelectorAll('*')) {
if (this.isClickable(node)) result.push(node)
if (this.hasClickHandler(node)) result.push(node)
2019-07-12 14:33:15 +02:00
}
return result
}
nearestActive(event) {
let element = this.domNode
let activeNodes = this.activeNodes()
2019-07-30 16:56:29 +02:00
let globalClick = event.center ? event.center : { x: event.x, y: event.y }
2019-07-12 14:33:15 +02:00
let localClick = Points.fromPageToNode(element, globalClick)
let clickRects = activeNodes.map(link => {
let rect = link.getBoundingClientRect()
let topLeft = Points.fromPageToNode(element, rect)
2019-07-18 12:26:39 +02:00
let center = Points.fromPageToNode(element, {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2
})
return {
x: topLeft.x,
y: topLeft.y,
width: rect.width,
height: rect.height,
center,
link
}
2019-07-12 14:33:15 +02:00
})
let distances = []
clickRects.forEach(rect => {
let distance = Points.distanceToRect(localClick, rect)
distances.push(parseInt(distance))
})
let closestClickIndex = distances.indexOf(Math.min(...distances))
let closestClickable = activeNodes[closestClickIndex]
if (distances[closestClickIndex] < this.allowClickDistance) {
return closestClickable
}
return null
}
isSVGNode(node) {
return node.ownerSVGElement || node.tagName == 'svg'
}
simulateClick(node, event) {
/* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a
proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page.
Therefore we use the original click event (see constructor). */
if (this.isSVGNode(node)) {
if (this.triggerSVGClicks) {
let click = new Event('click')
node.dispatchEvent(click)
}
return
}
node.click()
}
nodeTapped(node, event) {
2019-07-30 16:56:29 +02:00
console.log('nodeTapped', node, this.isClickable(node))
2019-07-12 14:33:15 +02:00
if (this.isClickable(node)) {
this.simulateClick(node, event)
return true
}
if (this.tapNodes.has(node)) {
handler = this.tapNodes.get(node)
2019-07-24 11:45:03 +02:00
handler(event, node)
2019-07-12 14:33:15 +02:00
return true
}
for (let [selector, handler] of this.tapHandler.entries()) {
2019-07-30 16:56:29 +02:00
console.log('nodeTapped', selector)
2019-07-12 14:33:15 +02:00
for (let obj of this.domNode.querySelectorAll(selector)) {
if (node == obj) {
2019-07-24 11:45:03 +02:00
handler(event, node)
2019-07-12 14:33:15 +02:00
return true
}
}
}
return false
}
2019-07-18 12:26:39 +02:00
tap(event, calledBy = 'unknown') {
2019-07-12 14:33:15 +02:00
if (event.isTrusted) {
let node = this.nearestActive(event)
2019-07-30 16:56:29 +02:00
console.log('tap', node)
2019-07-12 14:33:15 +02:00
this.nodeTapped(node, event)
2019-07-18 12:26:39 +02:00
/* let node = document.elementFromPoint(event.clientX, event.clientY)
2019-07-12 14:33:15 +02:00
if (!this.nodeTapped(node, event)) {
node = this.nearestActive(event)
this.nodeTapped(node, event)
} */
}
}
onTap(objOrSelector, handler) {
2019-07-18 12:26:39 +02:00
if (typeof objOrSelector == 'string') {
2019-07-12 14:33:15 +02:00
this.tapHandler.set(objOrSelector, handler)
2019-07-18 12:26:39 +02:00
} else {
2019-07-12 14:33:15 +02:00
this.tapNodes.set(objOrSelector, handler)
}
}
}