tapspace
Version:
A zoomable user interface lib for web apps
171 lines (140 loc) • 5.01 kB
JavaScript
var startMouseConversion = function (container) {
// Re-emit mouse events as custom touch-like events, "rats".
// The mouse events that reach the given HTMLElement are converted so
// that the initial mouse event target el is remembered even
// when the mouse exits it.
// This prevents the gestureend being emitted when the mouse leaves the
// original target, even temporarily. That brings a big improvement to
// panning with a mouse over lots of small items.
//
// Parameters
// container
// HTMLElement
//
// Mouse pressed down
var mouseDown = false
var originalTarget = null
var bubbles = true
var cancelable = true
var onMouseDown = function (mouseEv) {
if (!mouseDown && !mouseEv.defaultPrevented) {
mouseDown = true
// Remember the original target
originalTarget = mouseEv.target
// Construct the event
var ev = document.createEvent('Event')
ev.initEvent('ratstart', bubbles, cancelable)
ev.target = originalTarget
ev.pageX = mouseEv.pageX
ev.pageY = mouseEv.pageY
// Dispatch the rat. If it was cancelled, cancel also the original.
var defaultPrevented = !originalTarget.dispatchEvent(ev)
if (defaultPrevented) {
mouseEv.preventDefault()
}
}
}
var onMouseMove = function (mouseEv) {
if (mouseDown && !mouseEv.defaultPrevented) {
// Construct the rat event
var ev = document.createEvent('Event')
ev.initEvent('ratmove', bubbles, cancelable)
ev.target = originalTarget
ev.pageX = mouseEv.pageX
ev.pageY = mouseEv.pageY
// Dispatch the rat. If it was cancelled, cancel also the original.
var defaultPrevented = !originalTarget.dispatchEvent(ev)
if (defaultPrevented) {
mouseEv.preventDefault()
}
}
}
var onMouseUp = function (mouseEv) {
if (mouseDown && !mouseEv.defaultPrevented) {
mouseDown = false
var ev = document.createEvent('Event')
ev.initEvent('ratend', bubbles, cancelable)
ev.target = originalTarget
ev.pageX = mouseEv.pageX
ev.pageY = mouseEv.pageY
// Dispatch the rat. If it was cancelled, cancel also the original.
var defaultPrevented = !originalTarget.dispatchEvent(ev)
if (defaultPrevented) {
mouseEv.preventDefault()
}
// Forget the target. Just to be sure.
originalTarget = null
}
}
// Do not let our custom rat events escape.
// Note that stopPropagation does not affect
// the return value of dispatchEvent. Therefore
// the original mouse events become cancelled
// only if the rat events are explicitly cancelled.
// Note terminology: cancel = preventDefault.
var stop = function (customEvent) {
customEvent.stopPropagation()
}
container.addEventListener('mousedown', onMouseDown)
container.addEventListener('mousemove', onMouseMove)
container.addEventListener('mouseup', onMouseUp)
container.addEventListener('mouseleave', onMouseUp)
container.addEventListener('ratstart', stop)
container.addEventListener('ratmove', stop)
container.addEventListener('ratend', stop)
// Return handlers so that the listeners can be removed.
return {
container: container,
mousedown: onMouseDown,
mousemove: onMouseMove,
mouseup: onMouseUp,
mouseleave: onMouseUp,
ratstart: stop,
ratmove: stop,
ratend: stop
}
}
var stopMouseConversion = function (handlers) {
var h = handlers
var c = h.container
c.removeEventListener('mousedown', h.mousedown)
c.removeEventListener('mousemove', h.mousemove)
c.removeEventListener('mouseup', h.mouseup)
c.removeEventListener('mouseleave', h.mouseleave)
c.removeEventListener('ratstart', h.ratstart)
c.removeEventListener('ratmove', h.ratmove)
c.removeEventListener('ratend', h.ratend)
}
var MouseConverter = function () {
// Mapping from view ids to number of start calls.
// We keep count to be able to remove listeners on last stop call.
this.numRunning = {}
// Mapping from view ids to handler bundles returned by
this.handlers = {}
}
MouseConverter.prototype.start = function (view) {
// If already running
if (Object.prototype.hasOwnProperty.call(this.numRunning, view.id)) {
this.numRunning[view.id] += 1
return // only increase the counter
}
// Init the counter
this.numRunning[view.id] = 1
// Begin conversion for the view.
this.handlers[view.id] = startMouseConversion(view.getContainer())
}
MouseConverter.prototype.stop = function (view) {
// Stops conversion when stop() is called as many
// times as start for the view.
if (Object.prototype.hasOwnProperty.call(this.numRunning, view.id)) {
this.numRunning[view.id] -= 1
} else {
throw new Error('Stop called before start.')
}
if (this.numRunning[view.id] < 1) {
stopMouseConversion(this.handlers[view.id])
delete this.numRunning[view.id]
delete this.handlers[view.id]
}
}
module.exports = MouseConverter