Last active
December 23, 2015 11:39
-
-
Save tilomitra/6630272 to your computer and use it in GitHub Desktop.
The hold module provides a gesture event, "hold", which fires when a
user's finger/mouse stays at the same place for a given period of time. The release module provides a synthetic event, "release", which fires when a user's finger/mouse leaves the screen.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
YUI.add('hold-unit-tests', function(Y) { | |
var HOLD_EVENT = Y.Node.DOM_EVENTS.hold.eventDef, | |
Assert = Y.Assert, | |
noop = function() {}, | |
module = Y.one('#event-hold-module'), | |
submodule1 = Y.one('#event-hold-submodule1'), | |
submodule2 = Y.one('#event-hold-submodule2'), | |
HANDLES = { | |
START: 'Y_HOLD_ON_START_HANDLE', | |
MOVE: 'Y_HOLD_ON_MOVE_HANDLE', | |
END: 'Y_HOLD_ON_END_HANDLE', | |
CANCEL: 'Y_HOLD_ON_CANCEL_HANDLE' | |
}, | |
hasMsTouchActionSupport = (module.getDOMNode().style && ("msTouchAction" in module.getDOMNode().style)), | |
event = { | |
target: module, | |
currentTarget: module, | |
touches: [ | |
{ | |
pageX: 100, | |
pageY: 100, | |
clientX: 100, | |
clientY: 100 | |
} | |
], | |
type: 'touchstart' | |
}, | |
eventMultipleTouches = { | |
target: module, | |
currentTarget: module, | |
touches: [ | |
{ | |
pageX: 100, | |
pageY: 100, | |
clientX: 100, | |
clientY: 100 | |
}, | |
{ | |
pageX: 200, | |
pageY: 200, | |
clientX: 200, | |
clientY: 200 | |
} | |
], | |
type: 'touchstart' | |
}, | |
eventNoTouch = { | |
target: module, | |
currentTarget: module, | |
type: 'mousedown', | |
pageX: 100, | |
pageY: 100, | |
clientX: 100, | |
clientY: 100 | |
}, | |
suite = new Y.Test.Suite('event-hold Test Suite'); | |
suite.add(new Y.Test.Case({ | |
name: 'hold', | |
setUp: function() { | |
this.handles = []; | |
//this.handles.push(submodule1.on('hold', noop)); | |
//this.handles.push(module.delegate('hold', noop, '.module')); | |
event.type = 'touchstart'; | |
eventMultipleTouches.type = 'touchstart'; | |
eventNoTouch.type = 'mousedown'; | |
}, | |
tearDown: function() { | |
Y.Array.each(this.handles, function(h) { | |
h.detach(); | |
}); | |
}, | |
'test: on subscription': function () { | |
var sub = {}, | |
notifier = {}; | |
HOLD_EVENT.on(submodule2, sub, notifier); | |
Assert.isTrue(sub[HANDLES.START] instanceof Y.EventHandle); | |
}, | |
'test: detach': function () { | |
var sub = {}, | |
notifier = {}; | |
HOLD_EVENT.on(submodule2, sub, notifier); | |
Assert.isTrue(sub[HANDLES.START] instanceof Y.EventHandle); | |
HOLD_EVENT.detach(submodule2, sub, notifier); | |
Assert.isNull(sub[HANDLES.START]); | |
}, | |
'test: detachDelegate': function () { | |
var sub = {}, | |
notifier = {}; | |
HOLD_EVENT.delegate(module, sub, notifier, '.module'); | |
Assert.isTrue(sub[HANDLES.START] instanceof Y.EventHandle); | |
HOLD_EVENT.detachDelegate(module, sub, notifier); | |
Assert.isNull(sub[HANDLES.START]); | |
}, | |
'test: processArgs no delegate': function () { | |
var args = [{},{},{},{ duration: 100, threshold: 10 }]; | |
var extras = HOLD_EVENT.processArgs(args); | |
Assert.areEqual(100, extras.duration); | |
Assert.areEqual(10, extras.threshold); | |
Assert.areSame(3, args.length); | |
}, | |
'test: processArgs with delegate': function () { | |
var args = [{},{},{},{ duration: 100, threshold: 10 }]; | |
var extras = HOLD_EVENT.processArgs(args, true); | |
Assert.isUndefined(extras); | |
Assert.areSame(4, args.length); | |
}, | |
'test: _setTimer default': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._setTimer(event, module, sub, notifier); | |
//since the default hold duration is 500ms, we'll do something at 550ms. | |
this.wait(function(){ | |
Assert.isTrue(flag); | |
}, 550); | |
}, | |
'test: _setTimer with custom duration': function () { | |
var sub = { | |
_extra: { | |
duration: 200 | |
} | |
}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._setTimer(event, module, sub, notifier); | |
//since the custom hold duration is 200ms, we'll do something at 250ms. | |
this.wait(function(){ | |
Assert.isTrue(flag); | |
}, 250); | |
}, | |
'test: _setTimer with no duration': function () { | |
var sub = { | |
_extra: { | |
duration: 0 | |
} | |
}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._setTimer(event, module, sub, notifier); | |
//since the custom hold duration is 200ms, we'll do something at 250ms. | |
this.wait(function(){ | |
Assert.isTrue(flag); | |
}, 100); | |
}, | |
/* Tests with `touch` events */ | |
'test: _hold touch standard': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._hold(event, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isTrue(flag, 'a single e.touch should fire a `hold` event.'); | |
}, 550); | |
}, | |
'test: _hold touch with multiple e.touches': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._hold(eventMultipleTouches, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isFalse(flag, 'multiple e.touches should not fire a `hold` event.'); | |
}, 550); | |
}, | |
'test: _setupListeners with touch': function () { | |
var sub = {}, | |
context = { | |
eventType: 'touchstart' | |
}; | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._setupListeners(event, module, sub, {}, false, context, {}); | |
Assert.isTrue(sub[HANDLES.MOVE] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.MOVE].evt.type, 'touchmove'); | |
Assert.isTrue(sub[HANDLES.END] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.END].evt.type, 'touchend'); | |
Assert.isTrue(sub[HANDLES.CANCEL] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.CANCEL].evt.type, 'touchcancel'); | |
}, | |
'test: _hold touch with preventDefault': function () { | |
var sub = { | |
_extra: { | |
preventDefault: true | |
} | |
}, | |
flag = false, | |
prevFlag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
var customEvent = event; | |
customEvent.preventDefault = function () { | |
prevFlag = true; | |
}; | |
HOLD_EVENT._hold(customEvent, module, sub, notifier, false); | |
Assert.isTrue(prevFlag, '`preventDefault()` should be called'); | |
this.wait(function(){ | |
Assert.isTrue(flag, '`hold` event should fire'); | |
}, 550); | |
}, | |
/* tests with `mouse` events */ | |
'test: _hold mouse standard': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._hold(eventNoTouch, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isTrue(flag, 'a regular mouse event should fire a `hold` event.'); | |
}, 550); | |
}, | |
'test: _hold mouse rightclick': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}, | |
buttonEvent = { | |
button: 3 | |
}; | |
HOLD_EVENT._hold(buttonEvent, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isFalse(flag, 'a right click should not fire a `hold` event.'); | |
}, 550); | |
}, | |
'test: _hold mouse with preventMouse = true': function () { | |
var sub = { | |
preventMouse: true | |
}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
HOLD_EVENT._hold(eventNoTouch, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isFalse(sub.preventMouse, '`preventMouse` should be reset to false'); | |
Assert.isFalse(flag, '`hold` event should not fire if preventMouse = true'); | |
}, 550); | |
}, | |
'test: _hold mouse with preventDefault': function () { | |
var sub = { | |
_extra: { | |
preventDefault: true | |
} | |
}, | |
flag = false, | |
prevFlag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
var customEvent = eventNoTouch; | |
customEvent.preventDefault = function () { | |
prevFlag = true; | |
}; | |
HOLD_EVENT._hold(customEvent, module, sub, notifier, false); | |
Assert.isTrue(prevFlag, '`preventDefault()` should be called'); | |
this.wait(function(){ | |
Assert.isTrue(flag, '`hold` event should fire'); | |
}, 550); | |
}, | |
'test: _setupListeners with mouse': function () { | |
var sub = {}, | |
context = { | |
eventType: 'mousedown' | |
}; | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._setupListeners(eventNoTouch, module, sub, {}, false, context, {}); | |
Assert.isTrue(sub[HANDLES.MOVE] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.MOVE].evt.type, 'mousemove'); | |
Assert.isTrue(sub[HANDLES.END] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.END].evt.type, 'mouseup'); | |
Assert.isTrue(sub[HANDLES.CANCEL] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.CANCEL].evt.type, 'mousecancel'); | |
}, | |
'test: _setupListeners with MSPointer': function () { | |
var sub = {}, | |
context = { | |
eventType: 'MSPointerDown' | |
}; | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._setupListeners(eventNoTouch, module, sub, {}, false, context, {}); | |
Assert.isTrue(sub[HANDLES.MOVE] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.MOVE].evt.type, 'MSPointerMove'); | |
Assert.isTrue(sub[HANDLES.END] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.END].evt.type, 'MSPointerUp'); | |
Assert.isTrue(sub[HANDLES.CANCEL] instanceof Y.EventHandle); | |
Assert.areSame(sub[HANDLES.CANCEL].evt.type, 'MSPointerCancel'); | |
}, | |
'test: _move clearTimeout() with no params': function () { | |
var flag = false, | |
context = { | |
startXY: [90, 90] //the original events are [100, 100] | |
}, | |
timer = setTimeout(function () { | |
flag = true; | |
}, 1000); | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._move(eventNoTouch, module, {}, {}, false, context, timer); | |
//wait until the above timer expires, and make sure that it wasn't executed. | |
this.wait(function(){ | |
Assert.isFalse(flag); | |
}, 1100); | |
}, | |
'test: _move clearTimeout() with custom threshold': function () { | |
var flag = false, | |
sub = { | |
_extra: { | |
threshold: 9 //need atleast a 9px difference to clearTimeout(). | |
} | |
}, | |
context = { | |
startXY: [90, 90] //the original events are [100, 100] | |
}, | |
timer = setTimeout(function () { | |
flag = true; | |
}, 300); | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._move(eventNoTouch, module, {}, {}, false, context, timer); | |
//wait until the above timer expires, and make sure that it wasn't executed. | |
this.wait(function(){ | |
Assert.isFalse(flag); | |
}, 350); | |
}, | |
'test: _move no clearTimeout() called': function () { | |
var flag = false, | |
context = { | |
startXY: [98, 98] //the original events are [100, 100] | |
}, | |
timer = setTimeout(function () { | |
flag = true; | |
}, 300); | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._move(eventNoTouch, module, {}, {}, false, context, timer); | |
//wait until the above timer expires, and make sure that it wasn't executed. | |
this.wait(function(){ | |
Assert.isTrue(flag); | |
}, 350); | |
}, | |
'test: _end': function () { | |
var flag = false, | |
sub = {}, | |
timer = setTimeout(function () { | |
flag = true; | |
}, 300); | |
//populate subscription handles with some generic info | |
sub[HANDLES.MOVE] = new Y.EventHandle(); | |
sub[HANDLES.END] = new Y.EventHandle(); | |
sub[HANDLES.CANCEL] = new Y.EventHandle(); | |
//arguments are [event, node, subscription, notifier, delegate, context, timer] | |
HOLD_EVENT._end(eventNoTouch, module, {}, {}, false, {}, timer); | |
//clearTimeout() will always get called. | |
this.wait(function(){ | |
Assert.isFalse(flag); | |
}, 350); | |
Assert.isNull(sub[HANDLES.MOVE]); | |
Assert.isNull(sub[HANDLES.END]); | |
Assert.isNull(sub[HANDLES.CANCEL]); | |
} | |
})); | |
Y.Test.Runner.add(suite); | |
}, '', {requires:['event-hold', 'test', 'node']}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
The hold module provides a gesture event, "hold", which fires when a | |
user's finger/mouse stays at the same place for a given period of time. | |
@example | |
YUI().use('event-hold', function (Y) { | |
Y.one('#my-button').on('hold', function (e) { | |
Y.log('Button was held down.'); | |
}); | |
}); | |
@module event | |
@submodule event-hold | |
@author tilo mitra | |
@since 3.12.0 | |
*/ | |
var doc = Y.config.doc, | |
GESTURE_MAP = Y.Event._GESTURE_MAP, | |
EVT_START = GESTURE_MAP.start, | |
EVT_HOLD = 'hold', | |
HOLD_THRESHOLD = 'threshold', | |
HOLD_DURATION = 'duration', | |
PREVENT_DEFAULT = 'preventDefault', | |
TOUCH = 'touch', | |
MOUSE = 'mouse', | |
POINTER = 'pointer', | |
HANDLES = { | |
START: 'Y_HOLD_ON_START_HANDLE', | |
MOVE: 'Y_HOLD_ON_MOVE_HANDLE', | |
END: 'Y_HOLD_ON_END_HANDLE', | |
CANCEL: 'Y_HOLD_ON_CANCEL_HANDLE' | |
}; | |
function detachHandles(subscription, handles) { | |
handles = handles || Y.Object.values(HANDLES); | |
Y.Array.each(handles, function (item) { | |
var handle = subscription[item]; | |
if (handle) { | |
handle.detach(); | |
subscription[item] = null; | |
} | |
}); | |
} | |
/** | |
Sets up a "hold" event, that is fired when a user has their mouse or | |
finger down on an element for a given amount of time. In mobile | |
devices (especially iOS), holding down on a DOM element can make that | |
node selectable. If you wish to prevent this behavior, add the | |
following CSS to the element in question: | |
-webkit-user-select: none; | |
-webkit-user-drag: none; | |
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |
@event hold | |
@param type {string} "hold" | |
@param fn {function} The method the event invokes. It receives the | |
event facade of the underlying DOM event. | |
@param config {object} Optional. An object which specifies any of the | |
following: | |
* duration {Integer}: The number of milliseconds before `hold` is fired. | |
Defaults to `500` | |
* threshold {Integer}: The max. number of pixels that the user can move | |
their mouse/finger before the `hold` event is cancelled. Defaults | |
to `5`. | |
* preventDefault {Boolean}: Optional. Defaults to `false`. Set this to | |
true if you want the `touchstart`/`mousedown` event to not trigger | |
default browser behavior. Setting this value to `true` will prevent | |
iOS from showing modals/tooltips when the element is held, but it | |
will also prevent users from flicking on that element to scroll the page. | |
@for Event | |
@return {EventHandle} the detach handle | |
*/ | |
Y.Event.define(EVT_HOLD, { | |
processArgs: function (args, isDelegate) { | |
//if we return for the delegate use case, then the `filter` | |
//argument returns undefined, and we have to get the filter | |
//from sub._extra[0] (ugly) | |
if (!isDelegate) { | |
var extra = args[3]; | |
// remove the extra arguments from the array as specified by | |
// http://yuilibrary.com/yui/docs/event/synths.html | |
args.splice(3,1); | |
return extra; | |
} | |
}, | |
/** | |
This function should set up the node that will eventually fire the | |
event. | |
Usage: | |
node.on('hold', function (e) { | |
Y.log('the node was held.'); | |
}); | |
@method on | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@public | |
@static | |
**/ | |
on: function (node, subscription, notifier) { | |
subscription[HANDLES.START] = node.on(EVT_START, this._hold, this, node, subscription, notifier); | |
}, | |
/** | |
Detaches all event subscriptions set up by the event-hold module | |
@method detach | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@public | |
@static | |
**/ | |
detach: function (node, subscription, notifier) { | |
detachHandles(subscription); | |
}, | |
/** | |
Event delegation for the `hold` event. The delegated event will | |
use a supplied CSS selector or filtering function to test if the | |
event references at least one node that should trigger the subscription | |
callback. | |
Usage: | |
node.delegate('hold', function (e) { | |
Y.log('`li a` inside node was held.'); | |
}, 'li a'); | |
@method delegate | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@param {String | Function} filter | |
@public | |
@static | |
**/ | |
delegate: function (node, subscription, notifier, filter) { | |
subscription[HANDLES.START] = node.delegate(EVT_START, function (e) { | |
this._hold(e, node, subscription, notifier, true); | |
}, filter, this); | |
}, | |
/** | |
Detaches the delegated event subscriptions set up by the | |
event-hold module. | |
Only used if you use node.delegate(...) instead of node.on(...); | |
@method detachDelegate | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@public | |
@static | |
**/ | |
detachDelegate: function (node, subscription, notifier) { | |
detachHandles(subscription); | |
}, | |
/** | |
Called when the monitor(s) are touched, either through | |
`touchstart`, `mousedown`, `MSPointerDown` or a combination. | |
@method _hold | |
@param {DOMEventFacade} event | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@param {Boolean} delegate | |
@protected | |
@static | |
**/ | |
_hold: function (event, node, subscription, notifier, delegate) { | |
var context = { | |
canceled: false, | |
//if it's a synthetic event, then get the underlying DOM event. | |
eventType: (event._event) ? event._event.type : event.type | |
}, | |
params = subscription._extra, | |
//explicit undefined check here so that `0` passes through | |
prevDefault = (params && params[PREVENT_DEFAULT] !== undefined) ? params[PREVENT_DEFAULT] : this.preventDefault, | |
//preventMouse keeps track to see if a previous touch | |
//event occured, so that it prevents the corresponding | |
//mouse event from firing the event again. | |
preventMouse = subscription.preventMouse || false, | |
//Reference to a timer variable that will store the | |
//object passed from setTimeout() | |
timer; | |
//move ways to quit early to the top. | |
// no right clicks | |
if (event.button && event.button === 3) { | |
return; | |
} | |
// for now just support a 1 finger count | |
if (event.touches && event.touches.length !== 1) { | |
return; | |
} | |
context.node = delegate ? event.currentTarget : node; | |
if (event.touches) { | |
context.startXY = [ event.touches[0].pageX, event.touches[0].pageY ]; | |
} | |
else { | |
context.startXY = [ event.pageX, event.pageY ]; | |
} | |
//If `onTouchStart()` was called by a touch event, set up touch | |
//event subscriptions. Otherwise, set up mouse/pointer event | |
//event subscriptions. | |
if (event.touches) { | |
if (prevDefault) { | |
event.preventDefault(); | |
} | |
timer = this._setTimer(event, node, subscription, notifier); | |
this._setupListeners(event, node, subscription, notifier, delegate, context, timer); | |
//Since this is a touch* event, there will be corresponding | |
//mouse events that will be fired. We don't want these | |
//events to get picked up and fire another `hold` event, | |
//so we'll set this variable to `true`. | |
subscription.preventMouse = true; | |
} | |
//If a mouse event comes in after a touch event, it will go in | |
//here and reset preventMouse to `false`. | |
//If a mouse event comes in without a prior touch event, | |
//preventMouse will be false in any case, so this block | |
//won't do anything. | |
else if (context.eventType.indexOf('mouse') !== -1 && preventMouse) { | |
subscription.preventMouse = false; | |
} | |
//Only add these listeners if preventMouse is `false` | |
//ie: not when touch events have already been subscribed to | |
else { | |
if (prevDefault) { | |
event.preventDefault(); | |
} | |
timer = this._setTimer(event, node, subscription, notifier); | |
this._setupListeners(event, node, subscription, notifier, delegate, context, timer); | |
} | |
}, | |
/** | |
Called internally to set up a timer that will eventually fire | |
the `hold` event after a set duration. | |
@method _setTimer | |
@param {DOMEventFacade} event | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@protected | |
@static | |
**/ | |
_setTimer: function (event, node, subscription, notifier) { | |
var params = subscription._extra, | |
//explicit undefined check here so that `0` passes through | |
holdDuration = (params && params[HOLD_DURATION] !== undefined) ? params[HOLD_DURATION] : this.duration, | |
timer; | |
timer = setTimeout(function() { | |
event.type = EVT_HOLD; | |
notifier.fire(event); | |
}, holdDuration); | |
return timer; | |
}, | |
/** | |
Called internally to set up event listeners that can clear the | |
timer, and prevent `hold` from being fired. These event listeners | |
vary based on target environment. | |
@method _setupListeners | |
@param {DOMEventFacade} event | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier, | |
@param {Boolean} delegate, | |
@param {Object} context, | |
@param {Number} timer | |
@protected | |
@static | |
**/ | |
_setupListeners: function (event, node, subscription, notifier, delegate, context, timer) { | |
if (event.touches) { | |
subscription[HANDLES.MOVE] = node.on('touchmove', this._move, this, node, subscription, notifier, delegate, context, timer); | |
subscription[HANDLES.END] = node.once('touchend', this._end, this, node, subscription, notifier, delegate, context, timer); | |
subscription[HANDLES.CANCEL] = node.once('touchcancel', this._end, this, node, subscription, notifier, delegate, context, timer); | |
} | |
else if (context.eventType.indexOf('mouse') !== -1) { | |
subscription[HANDLES.MOVE] = node.on('mousemove', this._move, this, node, subscription, notifier, delegate, context, timer); | |
subscription[HANDLES.END] = node.once('mouseup', this._end, this, node, subscription, notifier, delegate, context, timer); | |
subscription[HANDLES.CANCEL] = node.once('mousecancel', this._end, this, node, subscription, notifier, delegate, context, timer); | |
} | |
else if (context.eventType.indexOf('MSPointer') !== -1) { | |
subscription[HANDLES.MOVE] = node.on('MSPointerMove', this._move, this, node, subscription, notifier, delegate, context, timer); | |
subscription[HANDLES.END] = node.once('MSPointerUp', this._end, this, node, subscription, notifier, delegate, context, timer); | |
subscription[HANDLES.CANCEL] = node.once('MSPointerCancel', this._end, this, node, subscription, notifier, delegate, context, timer); | |
} | |
}, | |
/** | |
Event callback that fires for `touchmove`, `mousemove`, or | |
`MSPointerMove` events. The `hold` event is cancelled if the | |
user moves their finger/mouse over a certain threshold. | |
@method _move | |
@param {DOMEventFacade} event | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier, | |
@param {Boolean} delegate, | |
@param {Object} context, | |
@param {Number} timer | |
@protected | |
@static | |
**/ | |
_move: function (event, node, subscription, notifier, delegate, context, timer) { | |
var currentXY = [ event.pageX, event.pageY ], | |
startXY = context.startXY, | |
params = subscription._extra, | |
//explicit undefined check here so that `0` passes through | |
holdThreshold = (params && params[HOLD_THRESHOLD] !== undefined) ? params[HOLD_THRESHOLD] : this.threshold; | |
if (Math.abs(currentXY[0] - startXY[0]) > holdThreshold || | |
Math.abs(currentXY[1] - startXY[1]) > holdThreshold) { | |
clearTimeout(timer); | |
detachHandles(subscription, [HANDLES.END, HANDLES.CANCEL]); | |
} | |
}, | |
/** | |
Event callback that fires for `touchend`, `mouseup`, or | |
`MSPointerUp` events. The `hold` event is cancelled if the | |
user lifts their finger/mouse off an element. | |
@method _end | |
@param {DOMEventFacade} event | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier, | |
@param {Boolean} delegate, | |
@param {Object} context, | |
@param {Number} timer | |
@protected | |
@static | |
**/ | |
_end: function (event, node, subscription, notifier, delegate, context, timer) { | |
clearTimeout(timer); | |
detachHandles(subscription, [HANDLES.MOVE, HANDLES.END, HANDLES.CANCEL]); | |
}, | |
threshold: 5, | |
duration: 500, | |
preventDefault: false | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
YUI.add('release-unit-tests', function(Y) { | |
var RELEASE_EVENT = Y.Node.DOM_EVENTS.release.eventDef, | |
Assert = Y.Assert, | |
noop = function() {}, | |
module = Y.one('#event-release-module'), | |
submodule = Y.one('#event-release-submodule1'), | |
HANDLES = { | |
END: 'Y_RELEASE_ON_END_HANDLE' | |
}, | |
hasMsTouchActionSupport = (module.getDOMNode().style && ("msTouchAction" in module.getDOMNode().style)), | |
event = { | |
target: module, | |
currentTarget: module, | |
changedTouches: [ | |
{ | |
pageX: 100, | |
pageY: 100, | |
clientX: 100, | |
clientY: 100 | |
} | |
], | |
type: 'touchend' | |
}, | |
eventMultipleTouches = { | |
target: module, | |
currentTarget: module, | |
changedTouches: [ | |
{ | |
pageX: 100, | |
pageY: 100, | |
clientX: 100, | |
clientY: 100 | |
}, | |
{ | |
pageX: 200, | |
pageY: 200, | |
clientX: 200, | |
clientY: 200 | |
} | |
], | |
type: 'touchend' | |
}, | |
eventNoTouch = { | |
target: module, | |
currentTarget: module, | |
type: 'mouseup', | |
pageX: 100, | |
pageY: 100, | |
clientX: 100, | |
clientY: 100 | |
}, | |
suite = new Y.Test.Suite('event-release Test Suite'); | |
suite.add(new Y.Test.Case({ | |
name: 'hold', | |
setUp: function() { | |
this.handles = []; | |
//this.handles.push(submodule1.on('hold', noop)); | |
//this.handles.push(module.delegate('hold', noop, '.module')); | |
event.type = 'touchend'; | |
eventMultipleTouches.type = 'touchend'; | |
eventNoTouch.type = 'mouseup'; | |
}, | |
tearDown: function() { | |
Y.Array.each(this.handles, function(h) { | |
h.detach(); | |
}); | |
}, | |
'test: on subscription': function () { | |
var sub = {}, | |
notifier = {}; | |
RELEASE_EVENT.on(submodule, sub, notifier); | |
Assert.isTrue(sub[HANDLES.END] instanceof Y.EventHandle); | |
}, | |
'test: detach': function () { | |
var sub = {}, | |
notifier = {}; | |
RELEASE_EVENT.on(submodule, sub, notifier); | |
Assert.isTrue(sub[HANDLES.END] instanceof Y.EventHandle); | |
RELEASE_EVENT.detach(submodule, sub, notifier); | |
Assert.isNull(sub[HANDLES.END]); | |
}, | |
'test: detachDelegate': function () { | |
var sub = {}, | |
notifier = {}; | |
RELEASE_EVENT.delegate(module, sub, notifier, '.module'); | |
Assert.isTrue(sub[HANDLES.END] instanceof Y.EventHandle); | |
RELEASE_EVENT.detachDelegate(module, sub, notifier); | |
Assert.isNull(sub[HANDLES.END]); | |
}, | |
/* Tests with `touch` events */ | |
'test: _release touch standard': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
RELEASE_EVENT._release(event, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isTrue(flag, 'a single e.touch should fire a `release` event.'); | |
}, 550); | |
}, | |
'test: _release touch with multiple e.touches': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
RELEASE_EVENT._release(eventMultipleTouches, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isFalse(flag, 'multiple e.touches should not fire a `release` event.'); | |
}, 550); | |
}, | |
/* tests with `mouse` events */ | |
'test: _release mouse standard': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
RELEASE_EVENT._release(eventNoTouch, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isTrue(flag, 'a regular mouse event should fire a `release` event.'); | |
}, 550); | |
}, | |
'test: _release mouse rightclick': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}, | |
buttonEvent = { | |
button: 3 | |
}; | |
RELEASE_EVENT._release(buttonEvent, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isFalse(flag, 'a right click should not fire a `release` event.'); | |
}, 550); | |
}, | |
'test: _release mouse with preventMouse = true': function () { | |
var sub = { | |
preventMouse: true | |
}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
RELEASE_EVENT._release(eventNoTouch, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isFalse(sub.preventMouse, '`preventMouse` should be reset to false'); | |
Assert.isFalse(flag, '`release` event should not fire if preventMouse = true'); | |
}, 550); | |
}, | |
/* make sure release isn't fired twice. */ | |
'test: _release not fired twice': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
if (flag) { | |
flag = false; | |
} | |
else { | |
flag = true; | |
} | |
} | |
}; | |
RELEASE_EVENT._release(event, module, sub, notifier, false); | |
sub.preventMouse = true; | |
this.wait(function(){ | |
Assert.isTrue(flag, 'a regular mouse event should fire a `release` event.'); | |
}, 550); | |
}, | |
/* tests with `MSPointer` events */ | |
'test: _release MSPointer standard': function () { | |
var sub = {}, | |
flag = false, | |
notifier = { | |
fire: function () { | |
flag = true; | |
} | |
}; | |
msPointerEvent = eventNoTouch; | |
msPointerEvent.type = 'MSPointerUp'; | |
RELEASE_EVENT._release(msPointerEvent, module, sub, notifier, false); | |
this.wait(function(){ | |
Assert.isTrue(flag, 'a regular MSPointerUp event should fire a `release` event.'); | |
}, 550); | |
}, | |
})); | |
Y.Test.Runner.add(suite); | |
}, '', {requires:['event-release', 'test', 'node']}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
The release module provides a synthetic event, "release", which fires when a user's finger/mouse leaves the screen. | |
@example | |
YUI().use('event-release', function (Y) { | |
Y.one('#my-button').on('release', function (e) { | |
Y.log('Button was released.'); | |
}); | |
}); | |
@module event | |
@submodule event-release | |
@author tilo mitra | |
@since 3.12.0 | |
*/ | |
var doc = Y.config.doc, | |
EVT_END = Y.Event._GESTURE_MAP.end, | |
EVT_RELEASE = 'release', | |
HANDLES = { | |
END: 'Y_RELEASE_ON_END_HANDLE' | |
}; | |
function detachHandles(subscription, handles) { | |
handles = handles || Y.Object.values(HANDLES); | |
Y.Array.each(handles, function (item) { | |
var handle = subscription[item]; | |
if (handle) { | |
handle.detach(); | |
subscription[item] = null; | |
} | |
}); | |
} | |
/** | |
The release module provides a synthetic event, "release", which fires when a user's finger/mouse leaves the screen. This is a useful event to listen to when performing user interactions that require the user's mouse/finger to stay on the screen. For example, you could listen to a `hold` or `drag` event, and use the `release` event to reset your application's state. | |
@event release | |
@param type {string} "release" | |
@param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event. | |
@for Event | |
@return {EventHandle} the detach handle | |
*/ | |
Y.Event.define(EVT_RELEASE, { | |
/** | |
This function should set up the node that will eventually fire the event. | |
Usage: | |
node.on('release', function (e) { | |
Y.log('the node was released.'); | |
}); | |
@method on | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@public | |
@static | |
**/ | |
on: function (node, subscription, notifier) { | |
subscription[HANDLES.END] = node.on(EVT_END, this._release, this, node, subscription, notifier); | |
}, | |
/** | |
Detaches all event subscriptions set up by the event-release module | |
@method detach | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@public | |
@static | |
**/ | |
detach: function (node, subscription, notifier) { | |
detachHandles(subscription); | |
}, | |
/** | |
Event delegation for the 'release' event. The delegated event will use a | |
supplied CSS selector or filtering function to test if the event references at least one | |
node that should trigger the subscription callback. | |
Usage: | |
node.delegate('release', function (e) { | |
Y.log('`li a` inside node was released.'); | |
}, 'li a'); | |
@method delegate | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@param {String | Function} filter | |
@public | |
@static | |
**/ | |
delegate: function (node, subscription, notifier, filter) { | |
subscription[HANDLES.END] = node.delegate(EVT_END, function (e) { | |
this._release(e, node, subscription, notifier, true); | |
}, filter, this); | |
}, | |
/** | |
Detaches the delegated event subscriptions set up by the event-release module. | |
Only used if you use node.delegate(...) instead of node.on(...); | |
@method detachDelegate | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@public | |
@static | |
**/ | |
detachDelegate: function (node, subscription, notifier) { | |
detachHandles(subscription); | |
}, | |
/** | |
Called when a user's mouse/finger leaves the screen. Fires | |
the `release` event under-the-hood. | |
@method _release | |
@param {DOMEventFacade} event | |
@param {Y.Node} node | |
@param {Y.Subscriber} subscription | |
@param {Object} notifier | |
@param {Boolean} delegate | |
@protected | |
@static | |
**/ | |
_release: function (event, node, subscription, notifier, delegate) { | |
var preventMouse = subscription.preventMouse || false, | |
context = { | |
eventType: event.type | |
}; | |
//move ways to quit early to the top. | |
// no right clicks | |
if (event.button && event.button === 3) { | |
return; | |
} | |
// for now just support a 1 finger count | |
// A 'touchend' event does not have `event.touches` - it has event.changedTouches | |
// instead. | |
if (event.changedTouches && event.changedTouches.length !== 1) { | |
return; | |
} | |
//If `touchend` fired, fire `release` and prevent further mouse interactions | |
//from firing a second `release` event. | |
if (event.changedTouches) { | |
this._fireRelease(event, notifier); | |
//Since this is a touch* event, there will be corresponding mouse events | |
//that will be fired. We don't want these events to get picked up and fire | |
//another `release` event, so we'll set this variable to `true`. | |
subscription.preventMouse = true; | |
} | |
//Only add these listeners if preventMouse is `false` | |
//ie: not when touch events have already been subscribed to | |
else if (context.eventType.indexOf('mouse') !== -1 && !preventMouse) { | |
this._fireRelease(event, notifier); | |
} | |
//If a mouse event comes in after a touch event, it will go in here and | |
//reset preventMouse to `false`. | |
//If a mouse event comes in without a prior touch event, preventMouse will be | |
//false in any case, so this block doesn't do anything. | |
else if (context.eventType.indexOf('mouse') !== -1 && preventMouse) { | |
subscription.preventMouse = false; | |
} | |
else if (context.eventType.indexOf('MSPointer') !== -1) { | |
this._fireRelease(event, notifier); | |
} | |
}, | |
/** | |
Fires the release event. | |
@method _fireRelease | |
@param {DOMEventFacade} event | |
@param {Object} notifier | |
@protected | |
@static | |
**/ | |
_fireRelease: function (event, notifier) { | |
event.type = EVT_RELEASE; | |
notifier.fire(event); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment