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-12 14:33:15 +02:00
|
|
|
|
|
|
|
constructor(domNode, { triggerSVGClicks = true, allowClickDistance = 44 } = {}) {
|
|
|
|
super()
|
|
|
|
this.domNode = domNode
|
|
|
|
this.triggerSVGClicks = triggerSVGClicks
|
|
|
|
this.allowClickDistance = allowClickDistance
|
|
|
|
this.tapNodes = new Map()
|
|
|
|
this.tapHandler = new Map()
|
|
|
|
}
|
|
|
|
|
|
|
|
handleClicks() {
|
|
|
|
this.domNode.addEventListener('click', event => {
|
|
|
|
if (event.isTrusted) {
|
|
|
|
Events.stop(event)
|
|
|
|
if (this.triggerSVGClicks && this.isSVGNode(event.target)) {
|
|
|
|
this.tap(event, "triggerSVGClicks")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
handleClicksAsTaps() {
|
|
|
|
this.domNode.addEventListener('click', event => {
|
|
|
|
if (event.isTrusted) {
|
|
|
|
Events.stop(event)
|
|
|
|
|
|
|
|
}
|
|
|
|
this.tap(event)
|
|
|
|
}, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
isClickable(node) {
|
|
|
|
if (node == null)
|
|
|
|
return false
|
|
|
|
if (node.tagName == 'A' && node.hasAttribute("href"))
|
|
|
|
return true
|
|
|
|
if (node.hasAttribute("onclick"))
|
|
|
|
return true
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
hasClickHandler(node) {
|
|
|
|
if (node == null)
|
|
|
|
return false
|
|
|
|
if (this.tapNodes.has(node))
|
|
|
|
return true
|
|
|
|
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
|
|
|
|
* II. Objects that have been attached a click handler by the scatter itself via
|
|
|
|
*/
|
|
|
|
activeNodes() {
|
|
|
|
let result = []
|
|
|
|
for (let node of this.domNode.querySelectorAll("*")) {
|
|
|
|
if (this.isClickable(node))
|
|
|
|
result.push(node)
|
|
|
|
if (this.hasClickHandler(node))
|
|
|
|
result.push(node)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
nearestActive(event) {
|
|
|
|
let element = this.domNode
|
|
|
|
let activeNodes = this.activeNodes()
|
|
|
|
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y }
|
|
|
|
let localClick = Points.fromPageToNode(element, globalClick)
|
|
|
|
|
|
|
|
let clickRects = activeNodes.map(link => {
|
|
|
|
let rect = link.getBoundingClientRect()
|
|
|
|
let topLeft = Points.fromPageToNode(element, rect)
|
|
|
|
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 }
|
|
|
|
})
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (this.isClickable(node)) {
|
|
|
|
this.simulateClick(node, event)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if (this.tapNodes.has(node)) {
|
|
|
|
handler = this.tapNodes.get(node)
|
|
|
|
handler(event)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for (let [selector, handler] of this.tapHandler.entries()) {
|
|
|
|
for (let obj of this.domNode.querySelectorAll(selector)) {
|
|
|
|
if (node == obj) {
|
|
|
|
handler(event)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
tap(event, calledBy='unknown') {
|
|
|
|
if (event.isTrusted) {
|
|
|
|
let node = this.nearestActive(event)
|
|
|
|
this.nodeTapped(node, event)
|
|
|
|
|
|
|
|
/* let node = document.elementFromPoint(event.clientX, event.clientY)
|
|
|
|
if (!this.nodeTapped(node, event)) {
|
|
|
|
node = this.nearestActive(event)
|
|
|
|
this.nodeTapped(node, event)
|
|
|
|
} */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onTap(objOrSelector, handler) {
|
|
|
|
if (typeof (objOrSelector) == 'string') {
|
|
|
|
this.tapHandler.set(objOrSelector, handler)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.tapNodes.set(objOrSelector, handler)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|