Added support for DOM cards.

This commit is contained in:
2019-07-12 14:33:15 +02:00
parent d04f92ee7f
commit e1b5c45b52
10 changed files with 941 additions and 386 deletions
+24 -124
View File
@@ -1,11 +1,26 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-console */
/* globals TweenLite debugCanvas */
import Interface from './interface.js'
import { Points, Polygon, Angle, Elements, LowPassFilter } from './utils.js'
import Events from './events.js'
import { InteractionMapper } from './interaction.js'
import { Capabilities } from './capabilities.js'
/** This interface allows scatters to delegate tap events to other objects. */
export class ITapDelegate extends Interface {
/** This method must be defined by the delegate. It handles the tap event. */
tap(event) {
}
/** Tells the delegate that it should handle standard click events. */
handleClicks() {
}
}
/**
* A base class for scatter specific events.
*
@@ -1035,6 +1050,7 @@ export class DOMScatterContainer {
}
export class DOMScatter extends AbstractScatter {
constructor(
element,
@@ -1060,7 +1076,7 @@ export class DOMScatter extends AbstractScatter {
width = null, // required
height = null, // required
resizable = false,
clickOnTap = false,
tapDelegate = null,
triggerSVGClicks = false,
allowClickDistance = 44,
verbose = true,
@@ -1117,8 +1133,7 @@ export class DOMScatter extends AbstractScatter {
this.height = height
this.throwVisibility = Math.min(width, height, throwVisibility)
this.container = container
this.clickOnTap = clickOnTap
this.triggerSVGClicks = triggerSVGClicks
this.tapDelegate = tapDelegate
this.scale = startScale
this.rotationDegrees = this.startRotationDegrees
this.transformOrigin = transformOrigin
@@ -1132,8 +1147,7 @@ export class DOMScatter extends AbstractScatter {
transformOrigin: transformOrigin
}
this.tapNodes = new Map()
this.allowClickDistance = allowClickDistance
// For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues)
this.onResize = onResize
@@ -1165,24 +1179,8 @@ export class DOMScatter extends AbstractScatter {
})
this.resizeButton = button
}
if (clickOnTap) {
/* Since the tap triggers a synthetic click event
we must prevent the original trusted click event which
is also dispatched by the system.
*/
element.addEventListener('click', event => {
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.target.ownerSVGElement) {
if (this.triggerSVGClicks) {
return
}
}
else if (event.isTrusted) {
Events.stop(event)
}
}, true)
if (tapDelegate) {
tapDelegate.handleClicks()
}
container.add(this)
}
@@ -1344,110 +1342,12 @@ export class DOMScatter extends AbstractScatter {
}
onTap(event, interaction, point) {
if (this.clickOnTap) {
let directNode = document.elementFromPoint(event.clientX, event.clientY)
console.log("onTap", event)
if (this.isClickable(directNode)) {
directNode.click()
}
else {
let nearestNode = this.nearestClickable(event)
if (this.isClickable(nearestNode)) {
/* 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 (nearestNode.tagName == 'svg') {
let handler = this.tapNodes.get(nearestNode)
console.log("Clicking near SVG: to be done", handler)
if (this.triggerSVGClicks)
nearestNode.dispatchEvent(new Event('click'))
return
}
console.log("nearestNode clicked")
nearestNode.click()
}
}
if (this.tapDelegate) {
Events.stop(event)
this.tapDelegate.tap(event, "scatter")
}
}
/**
* Adds a click or tap behavior to the node. Uses
* either the scatter clickOnTap version which requires click handlers
* or uses the hammer.js driven tap handler.
*
* @param {*} node
* @param {*} handler
* @memberof DOMScatter
*/
addTapListener(node, handler) {
if (this.clickOnTap) {
node.addEventListener('click', handler)
this.tapNodes.set(node, handler)
}
else {
InteractionMapper.on('tap', node, handler)
}
}
isClickable(node) {
if (node == null)
return false
if (node.tagName == 'A')
return true
if (node.hasAttribute("onclick"))
return true
if (this.tapNodes.has(node))
return true
return false
}
/**
* Returns an array of all clickable 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 clickables
* II. Objects that have been attached a click handler by the scatter itself via
*/
clickableNodes() {
let result = []
for (let node of this.element.querySelectorAll("*")) {
if (this.isClickable(node))
result.push(node)
}
return result
}
nearestClickable(event) {
let element = this.element
let clickables = this.clickableNodes()
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y }
let localClick = Points.fromPageToNode(element, globalClick)
let clickRects = clickables.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 = clickables[closestClickIndex]
if (distances[closestClickIndex] < this.allowClickDistance) {
return closestClickable
}
return null
}
isDescendant(parent, child) {
let node = child.parentNode
while (node != null) {