Last active
May 1, 2021 02:16
-
-
Save tilomitra/8663d5ff0a042a1df7c1 to your computer and use it in GitHub Desktop.
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('event-hammer', function (Y, NAME) { | |
/* | |
Copyright 2013 Yahoo! Inc. | |
Licensed under the BSD License. | |
http://yuilibrary.com/license/ | |
*/ | |
'use strict'; | |
//loop through Hammer.gestures | |
var HAMMER_GESTURES = [ | |
'hold', | |
'tap', | |
'doubletap', | |
'drag', | |
'dragstart', | |
'dragend', | |
'dragup', | |
'dragdown', | |
'dragleft', | |
'dragright', | |
'swipe', | |
'swipeup', | |
'swipedown', | |
'swipeleft', | |
'swiperight', | |
'transform', | |
'transformstart', | |
'transformend', | |
'rotate', | |
'pinch', | |
'pinchin', | |
'pinchout', | |
'touch', | |
'release' | |
], | |
HANDLES = {}, | |
HAMMER = '_hammer', | |
PREFIX = 'hammer:', | |
detachHandles = function detachHandles(subscription, gesture) { | |
var handle = subscription[gesture]; | |
if (handle) { | |
handle.detach(); | |
subscription[gesture] = null; | |
} | |
}, | |
eventDef = { | |
processArgs: function (args, isDelegate) { | |
if (isDelegate) { | |
return args.splice(4,1)[0]; | |
} | |
else { | |
return args.splice(3,1)[0]; | |
} | |
}, | |
on: function (node, subscription, notifier) { | |
var self = this; | |
this._setupHammer(node, subscription); | |
// Delegate the gesture event to HammerJS. | |
subscription[this.type] = node.on(PREFIX + this.type, function (ev) { | |
self.handleHammerEvent(ev, node, subscription, notifier); | |
}); | |
}, | |
delegate: function (node, subscription, notifier, filter) { | |
var self = this; | |
this._setupHammer(node, subscription); | |
subscription[this.type + '_del'] = node.on(PREFIX + this.type, function (ev) { | |
var srcEv = ev.gesture.srcEvent; | |
//This is what Y.Event.Delegate runs under the hood to determine if a given `filter` applies to a given `ev.target`. | |
if (Y.Selector.test(srcEv.target.getDOMNode(), filter, srcEv.currentTarget.getDOMNode())) { | |
self.handleHammerEvent(ev, node, subscription, notifier); | |
} | |
}); | |
}, | |
handleHammerEvent: function (ev, node, subscription, notifier) { | |
// do event facade normalization here | |
notifier.fire(ev); | |
}, | |
_setupHammer: function (node, subscription) { | |
//var params = subscription._extra; | |
var gestureOpts = node.get('gestureOpts'); | |
this._hammer = node.getData(HAMMER); | |
// start new hammer instance | |
if(!this._hammer) { | |
this._hammer = new Hammer(node.getDOMNode(), gestureOpts); | |
node.setData(HAMMER, this._hammer); | |
console.log('initial hammer opts:'); | |
console.log(this._hammer.options); | |
// gestureOpts and this._hammer.options reference the same object. | |
node.set('gestureOpts', this._hammer.options); | |
this._hammer.options = node.get('gestureOpts'); | |
} | |
return this._hammer; | |
}, | |
detach: function (node, subscription, notifier) { | |
detachHandles(subscription, this.type); | |
}, | |
detachDelegate: function () { | |
detachHandles(subscription, this.type + '_del'); | |
} | |
}; | |
Hammer.utils.on = function (element, type, handler) { | |
Y.one(element).on(type, handler); | |
} | |
Hammer.Instance.prototype.trigger = function(gesture, eventData) { | |
var el = Y.one(this.element); | |
el.fire(PREFIX + gesture, {gesture: eventData}); | |
}; | |
Y.Array.each(HAMMER_GESTURES, function (gesture) { | |
Y.Node.DOM_EVENTS[gesture] = 0; //we want all of these to be custom dom events. | |
Y.Event.define(gesture, eventDef); | |
}); | |
}, '@VERSION@', {"requires": ["node-base", "event-touch", "event-synthetic"]}); |
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Hammer.js + YUI</title> | |
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css" rel="stylesheet"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<meta http-equiv="X-UA-Compatible" content="IE=Edge" /> | |
<style> | |
body { | |
padding: 15px; | |
} | |
.container { position: relative; } | |
.hitarea { | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,.05); | |
text-align: center; | |
border: solid 3px rgba(33,33,33,1); | |
} | |
.log li { | |
display: inline-block; | |
width: 49%; | |
overflow: hidden; | |
} | |
@media screen and (min-width: 640px) { | |
.log li { width: 30%; } | |
} | |
.properties li { white-space: nowrap; } | |
.properties span { margin-left: 5px; } | |
.log li.active { background: lightgreen; } | |
.log li.property-gesture { | |
position: fixed; | |
right: 0; | |
top: 0; | |
background: lightgreen; | |
padding: 1px 4px; | |
width: auto; | |
} | |
</style> | |
</head> | |
<body class="yui3-skin-sam"> | |
<h1>YUI Gesture Events <small>Hammer.js + YUI = <3</small></h1> | |
<p class="alert"><strong>Press shift on your desktop for multitouch.</strong></p> | |
<p> | |
<label class="checkbox"> | |
<input type="checkbox" id="prevent-default"> block browser behavior (preventDefault) | |
</label> | |
</p> | |
<p> | |
<button class="btn" type="button" id="removeActiveBtn">Clear All Interactions</button> | |
</p> | |
<p> | |
<button class="btn" type="button" id="detachBtn">Detach Event Listeners</button> | |
</p> | |
<div class="container"> | |
<div class="log well well-small"> | |
<h4>Events</h4> | |
<ul class="unstyled events" id="events-list"> | |
<li id="log-gesture-touch">touch</li> | |
<li id="log-gesture-release">release</li> | |
<li id="log-gesture-hold">hold</li> | |
<li id="log-gesture-tap">tap</li> | |
<li id="log-gesture-doubletap">doubletap</li> | |
<li id="log-gesture-dragstart">dragstart</li> | |
<li id="log-gesture-drag">drag</li> | |
<li id="log-gesture-dragend">dragend</li> | |
<li id="log-gesture-dragdown">dragdown</li> | |
<li id="log-gesture-dragleft">dragleft</li> | |
<li id="log-gesture-dragright">dragright</li> | |
<li id="log-gesture-dragup">dragup</li> | |
<li id="log-gesture-swipe">swipe</li> | |
<li id="log-gesture-swipeleft">swipeleft</li> | |
<li id="log-gesture-swiperight">swiperight</li> | |
<li id="log-gesture-swipeup">swipeup</li> | |
<li id="log-gesture-swipedown">swipedown</li> | |
<li id="log-gesture-transformstart">transformstart</li> | |
<li id="log-gesture-transform">transform</li> | |
<li id="log-gesture-transformend">transformend</li> | |
<li id="log-gesture-rotate">rotate</li> | |
<li id="log-gesture-pinch">pinch</li> | |
<li id="log-gesture-pinchin">pinchin</li> | |
<li id="log-gesture-pinchout">pinchout</li> | |
</ul> | |
<h4>EventData</h4> | |
<ul class="unstyled properties"> | |
<li class="property-gesture"><strong>gesture</strong> <span id="log-prop-gesture"></span></li> | |
<li><strong>touches</strong> <span id="log-prop-touches"></span></li> | |
<li><strong>pointerType</strong> <span id="log-prop-pointerType"></span></li> | |
<li><strong>center</strong> <span id="log-prop-center"></span></li> | |
<li><strong>angle</strong> <span id="log-prop-angle"></span>°</li> | |
<li><strong>direction</strong> <span id="log-prop-direction"></span></li> | |
<li><strong>distance</strong> <span id="log-prop-distance"></span>px</li> | |
<li><strong>deltaTime</strong> <span id="log-prop-deltaTime"></span>ms</li> | |
<li><strong>deltaX</strong> <span id="log-prop-deltaX"></span>px</li> | |
<li><strong>deltaY</strong> <span id="log-prop-deltaY"></span>px</li> | |
<li><strong>velocityX</strong> <span id="log-prop-velocityX"></span></li> | |
<li><strong>velocityY</strong> <span id="log-prop-velocityY"></span></li> | |
<li><strong>scale</strong> <span id="log-prop-scale"></span></li> | |
<li><strong>rotation</strong> <span id="log-prop-rotation"></span>°</li> | |
<li><strong>interimDirection</strong> <span id="log-prop-interimDirection"></span></li> | |
<li><strong>interimAngle</strong> <span id="log-prop-interimAngle"></span>°</li> | |
<li><strong>target</strong> <span id="log-prop-target"></span></li> | |
</ul> | |
</div> | |
<div id="hitarea" class="hitarea"></div> | |
</div> | |
<p> | |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod | |
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, | |
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo | |
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse | |
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non | |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | |
</p> | |
<p> | |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod | |
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, | |
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo | |
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse | |
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non | |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | |
</p> | |
<p> | |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod | |
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, | |
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo | |
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse | |
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non | |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | |
</p> | |
<p> | |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod | |
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, | |
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo | |
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse | |
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non | |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | |
</p> | |
<p> | |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod | |
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, | |
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo | |
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse | |
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non | |
proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | |
</p> | |
<script src="http://yui.yahooapis.com/3.17.2/build/yui/yui-min.js"></script> | |
<script src="http://rawgit.com/tilomitra/8663d5ff0a042a1df7c1/raw/5226dceb5d34a23ef14a757230f36ea9ce7722bb/hammer.js"></script> | |
<!-- <script src="../../../../bower_components/hammerjs/hammer.js"></script> | |
--><script src="http://rawgit.com/EightMedia/hammer.js/1.1.3/plugins/hammer.fakemultitouch.js"></script> | |
<script src="http://rawgit.com/EightMedia/hammer.js/1.1.3/plugins/hammer.showtouches.js"></script> | |
<!--[if !IE]> --> | |
<script> | |
Hammer.plugins.showTouches(); | |
</script> | |
<!-- <![endif]--> | |
<script> | |
YUI({ | |
filter: 'raw', | |
useBrowserConsole: true, | |
modules: { | |
'node-base': 'http://rawgit.com/tilomitra/8663d5ff0a042a1df7c1/raw/7b516773107f7f2a8aa9694452d0b34816d67912/node-base.js', | |
'event-hammer': 'http://rawgit.com/tilomitra/8663d5ff0a042a1df7c1/raw/dbddb15d15612cee2a7adab254a6267a6104ac63/event-hammer.js' | |
} | |
}).use('node', 'event-hammer', 'console', 'node-event-delegate', function (Y) { | |
Hammer.plugins.fakeMultitouch(); | |
function getEl(id) { | |
return document.getElementById(id); | |
} | |
var hitArea = Y.one('#hitarea'); | |
var log_elements = {}; | |
//var console = new Y.Console().render(); | |
Y.one('#prevent-default').on('click', function() { | |
hitArea.set('gestureOpts', {prevent_default: this.checked}); | |
}); | |
function getLogElement(type, name) { | |
var el = log_elements[type + name]; | |
if(!el) { | |
return log_elements[type + name] = getEl("log-"+ type +"-"+ name); | |
} | |
return el; | |
} | |
// log properties | |
var properties = ['gesture','center','deltaTime','angle','direction', | |
'distance','deltaX','deltaY','velocityX','velocityY', 'pointerType', | |
'interimDirection','interimAngle', | |
'scale','rotation','touches','target']; | |
function logEvent(ev) { | |
if(!ev.gesture) { | |
return; | |
} | |
// highlight gesture | |
var event_el = getLogElement('gesture', ev.type); | |
event_el.className = "active"; | |
for(var i= 0,len=properties.length; i<len; i++) { | |
var prop = properties[i]; | |
var value = ev.gesture[prop]; | |
switch(prop) { | |
case 'center': | |
value = value.pageX +"x"+ value.pageY; | |
break; | |
case 'gesture': | |
value = ev.type; | |
break; | |
case 'target': | |
value = ev.gesture.target.nodeName || undefined; | |
break; | |
case 'touches': | |
value = ev.gesture.touches.length; | |
break; | |
} | |
getLogElement('prop', prop).innerHTML = value; | |
} | |
Y.log('firing ' + ev.type); | |
} | |
// get all the events | |
var all_events = []; | |
Y.all('#events-list li').each(function (node, index, list) { | |
var li = node; | |
var type = node.get('text'); | |
li.setAttribute("id", "log-gesture-"+type); | |
all_events.push(type); | |
}); | |
// hitArea.set('gestureOpts', {drag: false, swipe: false, release: false, touch: false}); | |
//EVERYTHING | |
var handle1 = hitArea.on(all_events, function (e) { | |
logEvent(e); | |
}); | |
//STANDARD USAGE | |
// var handle1 = hitArea.on('tap', function (e) { | |
// logEvent(e); | |
// }); | |
//EVENT DELEGATION | |
// var handle1 = Y.one('body').delegate('touch', function (e) { | |
// logEvent(e); | |
// }, 'div'); | |
//USING .after() | |
//subscribe to after() before subscribing to on() | |
// var handle1 = hitArea.after('click', function (e) { | |
// console.log('after: ' + e.type); | |
// }); | |
// var handle2 = hitArea.on('click', function (e) { | |
// console.log('on: ' + e.type); | |
// }); | |
//SETTING GESTURE OPTIONS | |
// hitArea.set('gestureOpts', {hold: false}); | |
// var handle1 = hitArea.on('hold', function (e) { | |
// console.log('handle1: ' + e.type); | |
// logEvent(e); | |
// }); | |
//If you comment out the line below, the `hold` callback will not get called. | |
// hitArea.set('gestureOpts', {hold: true}); | |
// var handle2 = hitArea.on('tap', function (e) { | |
// console.log('handle2: ' + e.type); | |
// }); | |
Y.one('#removeActiveBtn').on('tap', function (e) { | |
Y.all('.active').removeClass('active'); | |
//console.clearConsole(); | |
}); | |
Y.one('#detachBtn').on('tap', function (e) { | |
handle1.detach(); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
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
/*! Hammer.JS - v1.0.9 - 2014-03-18 | |
* http://eightmedia.github.com/hammer.js | |
* | |
* Copyright (c) 2014 Jorik Tangelder <[email protected]>; | |
* Licensed under the MIT license */ | |
(function(window, undefined) { | |
'use strict'; | |
/** | |
* Hammer | |
* use this to create instances | |
* @param {HTMLElement} element | |
* @param {Object} options | |
* @returns {Hammer.Instance} | |
* @constructor | |
*/ | |
var Hammer = function(element, options) { | |
return new Hammer.Instance(element, options || {}); | |
}; | |
// default settings | |
Hammer.defaults = { | |
// add styles and attributes to the element to prevent the browser from doing | |
// its native behavior. this doesnt prevent the scrolling, but cancels | |
// the contextmenu, tap highlighting etc | |
// set to false to disable this | |
stop_browser_behavior: { | |
// this also triggers onselectstart=false for IE | |
userSelect : 'none', | |
// this makes the element blocking in IE10 >, you could experiment with the value | |
// see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241 | |
touchAction : 'none', | |
touchCallout : 'none', | |
contentZooming : 'none', | |
userDrag : 'none', | |
tapHighlightColor: 'rgba(0,0,0,0)' | |
} | |
// | |
// more settings are defined per gesture at gestures.js | |
// | |
}; | |
// detect touchevents | |
Hammer.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled; | |
Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); | |
// dont use mouseevents on mobile devices | |
Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i; | |
Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && window.navigator.userAgent.match(Hammer.MOBILE_REGEX); | |
// eventtypes per touchevent (start, move, end) | |
// are filled by Event.determineEventTypes on setup | |
Hammer.EVENT_TYPES = {}; | |
// interval in which Hammer recalculates current velocity in ms | |
Hammer.UPDATE_VELOCITY_INTERVAL = 16; | |
// hammer document where the base events are added at | |
Hammer.DOCUMENT = window.document; | |
// define these also as vars, for internal usage. | |
// direction defines | |
var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down'; | |
var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left'; | |
var DIRECTION_UP = Hammer.DIRECTION_UP = 'up'; | |
var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right'; | |
// pointer type | |
var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse'; | |
var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch'; | |
var POINTER_PEN = Hammer.POINTER_PEN = 'pen'; | |
// touch event defines | |
var EVENT_START = Hammer.EVENT_START = 'start'; | |
var EVENT_MOVE = Hammer.EVENT_MOVE = 'move'; | |
var EVENT_END = Hammer.EVENT_END = 'end'; | |
// plugins and gestures namespaces | |
Hammer.plugins = Hammer.plugins || {}; | |
Hammer.gestures = Hammer.gestures || {}; | |
// if the window events are set... | |
Hammer.READY = false; | |
/** | |
* setup events to detect gestures on the document | |
*/ | |
function setup() { | |
if(Hammer.READY) { | |
return; | |
} | |
// find what eventtypes we add listeners to | |
Event.determineEventTypes(); | |
// Register all gestures inside Hammer.gestures | |
Utils.each(Hammer.gestures, function(gesture){ | |
Detection.register(gesture); | |
}); | |
// Add touch events on the document | |
Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect); | |
Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect); | |
// Hammer is ready...! | |
Hammer.READY = true; | |
} | |
var Utils = Hammer.utils = { | |
/** | |
* extend method, | |
* also used for cloning when dest is an empty object | |
* @param {Object} dest | |
* @param {Object} src | |
* @parm {Boolean} merge do a merge | |
* @returns {Object} dest | |
*/ | |
extend: function extend(dest, src, merge) { | |
for(var key in src) { | |
if(dest[key] !== undefined && merge) { | |
continue; | |
} | |
dest[key] = src[key]; | |
} | |
return dest; | |
}, | |
/** | |
* for each | |
* @param obj | |
* @param iterator | |
*/ | |
each: function(obj, iterator, context) { | |
var i, o; | |
// native forEach on arrays | |
if ('forEach' in obj) { | |
obj.forEach(iterator, context); | |
} | |
// arrays | |
else if(obj.length !== undefined) { | |
for(i=-1; (o=obj[++i]);) { | |
if (iterator.call(context, o, i, obj) === false) { | |
return; | |
} | |
} | |
} | |
// objects | |
else { | |
for(i in obj) { | |
if(obj.hasOwnProperty(i) && | |
iterator.call(context, obj[i], i, obj) === false) { | |
return; | |
} | |
} | |
} | |
}, | |
/** | |
* find if a node is in the given parent | |
* used for event delegation tricks | |
* @param {HTMLElement} node | |
* @param {HTMLElement} parent | |
* @returns {boolean} has_parent | |
*/ | |
hasParent: function(node, parent) { | |
while(node) { | |
if(node == parent) { | |
return true; | |
} | |
node = node.parentNode; | |
} | |
return false; | |
}, | |
/** | |
* get the center of all the touches | |
* @param {Array} touches | |
* @returns {Object} center | |
*/ | |
getCenter: function getCenter(touches) { | |
var valuesX = [], valuesY = []; | |
Utils.each(touches, function(touch) { | |
// I prefer clientX because it ignore the scrolling position | |
valuesX.push(typeof touch.clientX !== 'undefined' ? touch.clientX : touch.pageX); | |
valuesY.push(typeof touch.clientY !== 'undefined' ? touch.clientY : touch.pageY); | |
}); | |
return { | |
pageX: (Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2, | |
pageY: (Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2 | |
}; | |
}, | |
/** | |
* calculate the velocity between two points | |
* @param {Number} delta_time | |
* @param {Number} delta_x | |
* @param {Number} delta_y | |
* @returns {Object} velocity | |
*/ | |
getVelocity: function getVelocity(delta_time, delta_x, delta_y) { | |
return { | |
x: Math.abs(delta_x / delta_time) || 0, | |
y: Math.abs(delta_y / delta_time) || 0 | |
}; | |
}, | |
/** | |
* calculate the angle between two coordinates | |
* @param {Touch} touch1 | |
* @param {Touch} touch2 | |
* @returns {Number} angle | |
*/ | |
getAngle: function getAngle(touch1, touch2) { | |
var y = touch2.pageY - touch1.pageY | |
, x = touch2.pageX - touch1.pageX; | |
return Math.atan2(y, x) * 180 / Math.PI; | |
}, | |
/** | |
* angle to direction define | |
* @param {Touch} touch1 | |
* @param {Touch} touch2 | |
* @returns {String} direction constant, like DIRECTION_LEFT | |
*/ | |
getDirection: function getDirection(touch1, touch2) { | |
var x = Math.abs(touch1.pageX - touch2.pageX) | |
, y = Math.abs(touch1.pageY - touch2.pageY); | |
if(x >= y) { | |
return touch1.pageX - touch2.pageX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; | |
} | |
return touch1.pageY - touch2.pageY > 0 ? DIRECTION_UP : DIRECTION_DOWN; | |
}, | |
/** | |
* calculate the distance between two touches | |
* @param {Touch} touch1 | |
* @param {Touch} touch2 | |
* @returns {Number} distance | |
*/ | |
getDistance: function getDistance(touch1, touch2) { | |
var x = touch2.pageX - touch1.pageX | |
, y = touch2.pageY - touch1.pageY; | |
return Math.sqrt((x * x) + (y * y)); | |
}, | |
/** | |
* calculate the scale factor between two touchLists (fingers) | |
* no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out | |
* @param {Array} start | |
* @param {Array} end | |
* @returns {Number} scale | |
*/ | |
getScale: function getScale(start, end) { | |
// need two fingers... | |
if(start.length >= 2 && end.length >= 2) { | |
return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]); | |
} | |
return 1; | |
}, | |
/** | |
* calculate the rotation degrees between two touchLists (fingers) | |
* @param {Array} start | |
* @param {Array} end | |
* @returns {Number} rotation | |
*/ | |
getRotation: function getRotation(start, end) { | |
// need two fingers | |
if(start.length >= 2 && end.length >= 2) { | |
return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]); | |
} | |
return 0; | |
}, | |
/** | |
* boolean if the direction is vertical | |
* @param {String} direction | |
* @returns {Boolean} is_vertical | |
*/ | |
isVertical: function isVertical(direction) { | |
return direction == DIRECTION_UP || direction == DIRECTION_DOWN; | |
}, | |
/** | |
* toggle browser default behavior with css props | |
* @param {HtmlElement} element | |
* @param {Object} css_props | |
* @param {Boolean} toggle | |
*/ | |
toggleDefaultBehavior: function toggleDefaultBehavior(element, css_props, toggle) { | |
if(!css_props || !element || !element.style) { | |
return; | |
} | |
// with css properties for modern browsers | |
Utils.each(['webkit', 'moz', 'Moz', 'ms', 'o', ''], function(vendor) { | |
Utils.each(css_props, function(value, prop) { | |
// vender prefix at the property | |
if(vendor) { | |
prop = vendor + prop.substring(0, 1).toUpperCase() + prop.substring(1); | |
} | |
// set the style | |
if(prop in element.style) { | |
element.style[prop] = !toggle && value; | |
} | |
}); | |
}); | |
var false_fn = function(){ return false; }; | |
// also the disable onselectstart | |
if(css_props.userSelect == 'none') { | |
element.onselectstart = !toggle && false_fn; | |
} | |
// and disable ondragstart | |
if(css_props.userDrag == 'none') { | |
element.ondragstart = !toggle && false_fn; | |
} | |
} | |
}; | |
/** | |
* create new hammer instance | |
* all methods should return the instance itself, so it is chainable. | |
* @param {HTMLElement} element | |
* @param {Object} [options={}] | |
* @returns {Hammer.Instance} | |
* @constructor | |
*/ | |
Hammer.Instance = function(element, options) { | |
var self = this; | |
// setup HammerJS window events and register all gestures | |
// this also sets up the default options | |
setup(); | |
this.element = element; | |
// start/stop detection option | |
this.enabled = true; | |
// merge options | |
this.options = Utils.extend( | |
Utils.extend({}, Hammer.defaults), | |
options || {}); | |
// add some css to the element to prevent the browser from doing its native behavoir | |
if(this.options.stop_browser_behavior) { | |
Utils.toggleDefaultBehavior(this.element, this.options.stop_browser_behavior, false); | |
} | |
// start detection on touchstart | |
this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) { | |
if(self.enabled) { | |
Detection.startDetect(self, ev); | |
} | |
}); | |
// keep a list of user event handlers which needs to be removed when calling 'dispose' | |
this.eventHandlers = []; | |
// return instance | |
return this; | |
}; | |
Hammer.Instance.prototype = { | |
/** | |
* bind events to the instance | |
* @param {String} gesture | |
* @param {Function} handler | |
* @returns {Hammer.Instance} | |
*/ | |
on: function onEvent(gesture, handler) { | |
var gestures = gesture.split(' '); | |
Utils.each(gestures, function(gesture) { | |
this.element.addEventListener(gesture, handler, false); | |
this.eventHandlers.push({ gesture: gesture, handler: handler }); | |
}, this); | |
return this; | |
}, | |
/** | |
* unbind events to the instance | |
* @param {String} gesture | |
* @param {Function} handler | |
* @returns {Hammer.Instance} | |
*/ | |
off: function offEvent(gesture, handler) { | |
var gestures = gesture.split(' ') | |
, i, eh; | |
Utils.each(gestures, function(gesture) { | |
this.element.removeEventListener(gesture, handler, false); | |
// remove the event handler from the internal list | |
for(i=-1; (eh=this.eventHandlers[++i]);) { | |
if(eh.gesture === gesture && eh.handler === handler) { | |
this.eventHandlers.splice(i, 1); | |
} | |
} | |
}, this); | |
return this; | |
}, | |
/** | |
* trigger gesture event | |
* @param {String} gesture | |
* @param {Object} [eventData] | |
* @returns {Hammer.Instance} | |
*/ | |
trigger: function triggerEvent(gesture, eventData) { | |
// optional | |
if(!eventData) { | |
eventData = {}; | |
} | |
// create DOM event | |
var event = Hammer.DOCUMENT.createEvent('Event'); | |
event.initEvent(gesture, true, true); | |
event.gesture = eventData; | |
// trigger on the target if it is in the instance element, | |
// this is for event delegation tricks | |
var element = this.element; | |
if(Utils.hasParent(eventData.target, element)) { | |
element = eventData.target; | |
} | |
element.dispatchEvent(event); | |
return this; | |
}, | |
/** | |
* enable of disable hammer.js detection | |
* @param {Boolean} state | |
* @returns {Hammer.Instance} | |
*/ | |
enable: function enable(state) { | |
this.enabled = state; | |
return this; | |
}, | |
/** | |
* dispose this hammer instance | |
* @returns {Hammer.Instance} | |
*/ | |
dispose: function dispose() { | |
var i, eh; | |
// undo all changes made by stop_browser_behavior | |
if(this.options.stop_browser_behavior) { | |
Utils.toggleDefaultBehavior(this.element, this.options.stop_browser_behavior, true); | |
} | |
// unbind all custom event handlers | |
for(i=-1; (eh=this.eventHandlers[++i]);) { | |
this.element.removeEventListener(eh.gesture, eh.handler, false); | |
} | |
this.eventHandlers = []; | |
// unbind the start event listener | |
Event.unbindDom(this.element, Hammer.EVENT_TYPES[EVENT_START], this.eventStartHandler); | |
return null; | |
} | |
}; | |
/** | |
* this holds the last move event, | |
* used to fix empty touchend issue | |
* see the onTouch event for an explanation | |
* @type {Object} | |
*/ | |
var last_move_event = null; | |
/** | |
* when the mouse is hold down, this is true | |
* @type {Boolean} | |
*/ | |
var enable_detect = false; | |
/** | |
* when touch events have been fired, this is true | |
* @type {Boolean} | |
*/ | |
var touch_triggered = false; | |
var Event = Hammer.event = { | |
/** | |
* simple addEventListener | |
* @param {HTMLElement} element | |
* @param {String} type | |
* @param {Function} handler | |
*/ | |
bindDom: function(element, type, handler) { | |
var types = type.split(' '); | |
Utils.each(types, function(type){ | |
element.addEventListener(type, handler, false); | |
}); | |
}, | |
/** | |
* simple removeEventListener | |
* @param {HTMLElement} element | |
* @param {String} type | |
* @param {Function} handler | |
*/ | |
unbindDom: function(element, type, handler) { | |
var types = type.split(' '); | |
Utils.each(types, function(type){ | |
element.removeEventListener(type, handler, false); | |
}); | |
}, | |
/** | |
* touch events with mouse fallback | |
* @param {HTMLElement} element | |
* @param {String} eventType like EVENT_MOVE | |
* @param {Function} handler | |
*/ | |
onTouch: function onTouch(element, eventType, handler) { | |
var self = this; | |
var bindDomOnTouch = function(ev) { | |
var srcEventType = ev.type.toLowerCase(); | |
// onmouseup, but when touchend has been fired we do nothing. | |
// this is for touchdevices which also fire a mouseup on touchend | |
if(srcEventType.match(/mouse/) && touch_triggered) { | |
return; | |
} | |
// mousebutton must be down or a touch event | |
else if(srcEventType.match(/touch/) || // touch events are always on screen | |
srcEventType.match(/pointerdown/) || // pointerevents touch | |
(srcEventType.match(/mouse/) && ev.which === 1) // mouse is pressed | |
) { | |
enable_detect = true; | |
} | |
// mouse isn't pressed | |
else if(srcEventType.match(/mouse/) && !ev.which) { | |
enable_detect = false; | |
} | |
// we are in a touch event, set the touch triggered bool to true, | |
// this for the conflicts that may occur on ios and android | |
if(srcEventType.match(/touch|pointer/)) { | |
touch_triggered = true; | |
} | |
// count the total touches on the screen | |
var count_touches = 0; | |
// when touch has been triggered in this detection session | |
// and we are now handling a mouse event, we stop that to prevent conflicts | |
if(enable_detect) { | |
// update pointerevent | |
if(Hammer.HAS_POINTEREVENTS && eventType != EVENT_END) { | |
count_touches = PointerEvent.updatePointer(eventType, ev); | |
} | |
// touch | |
else if(srcEventType.match(/touch/)) { | |
count_touches = ev.touches.length; | |
} | |
// mouse | |
else if(!touch_triggered) { | |
count_touches = srcEventType.match(/up/) ? 0 : 1; | |
} | |
// if we are in a end event, but when we remove one touch and | |
// we still have enough, set eventType to move | |
if(count_touches > 0 && eventType == EVENT_END) { | |
eventType = EVENT_MOVE; | |
} | |
// no touches, force the end event | |
else if(!count_touches) { | |
eventType = EVENT_END; | |
} | |
// store the last move event | |
if(count_touches || last_move_event === null) { | |
last_move_event = ev; | |
} | |
// trigger the handler | |
handler.call(Detection, self.collectEventData(element, eventType, | |
self.getTouchList(last_move_event, eventType), | |
ev)); | |
// remove pointerevent from list | |
if(Hammer.HAS_POINTEREVENTS && eventType == EVENT_END) { | |
count_touches = PointerEvent.updatePointer(eventType, ev); | |
} | |
} | |
// on the end we reset everything | |
if(!count_touches) { | |
last_move_event = null; | |
enable_detect = false; | |
touch_triggered = false; | |
PointerEvent.reset(); | |
} | |
}; | |
this.bindDom(element, Hammer.EVENT_TYPES[eventType], bindDomOnTouch); | |
// return the bound function to be able to unbind it later | |
return bindDomOnTouch; | |
}, | |
/** | |
* we have different events for each device/browser | |
* determine what we need and set them in the Hammer.EVENT_TYPES constant | |
*/ | |
determineEventTypes: function determineEventTypes() { | |
// determine the eventtype we want to set | |
var types; | |
// pointerEvents magic | |
if(Hammer.HAS_POINTEREVENTS) { | |
types = PointerEvent.getEvents(); | |
} | |
// on Android, iOS, blackberry, windows mobile we dont want any mouseevents | |
else if(Hammer.NO_MOUSEEVENTS) { | |
types = [ | |
'touchstart', | |
'touchmove', | |
'touchend touchcancel']; | |
} | |
// for non pointer events browsers and mixed browsers, | |
// like chrome on windows8 touch laptop | |
else { | |
types = [ | |
'touchstart mousedown', | |
'touchmove mousemove', | |
'touchend touchcancel mouseup']; | |
} | |
Hammer.EVENT_TYPES[EVENT_START] = types[0]; | |
Hammer.EVENT_TYPES[EVENT_MOVE] = types[1]; | |
Hammer.EVENT_TYPES[EVENT_END] = types[2]; | |
}, | |
/** | |
* create touchlist depending on the event | |
* @param {Object} ev | |
* @param {String} eventType used by the fakemultitouch plugin | |
*/ | |
getTouchList: function getTouchList(ev/*, eventType*/) { | |
// get the fake pointerEvent touchlist | |
if(Hammer.HAS_POINTEREVENTS) { | |
return PointerEvent.getTouchList(); | |
} | |
// get the touchlist | |
if(ev.touches) { | |
return ev.touches; | |
} | |
// make fake touchlist from mouse position | |
ev.identifier = 1; | |
return [ev]; | |
}, | |
/** | |
* collect event data for Hammer js | |
* @param {HTMLElement} element | |
* @param {String} eventType like EVENT_MOVE | |
* @param {Object} eventData | |
*/ | |
collectEventData: function collectEventData(element, eventType, touches, ev) { | |
// find out pointerType | |
var pointerType = POINTER_TOUCH; | |
if(ev.type.match(/mouse/) || PointerEvent.matchType(POINTER_MOUSE, ev)) { | |
pointerType = POINTER_MOUSE; | |
} | |
return { | |
center : Utils.getCenter(touches), | |
timeStamp : new Date().getTime(), | |
target : ev.target, | |
touches : touches, | |
eventType : eventType, | |
pointerType: pointerType, | |
srcEvent : ev, | |
/** | |
* prevent the browser default actions | |
* mostly used to disable scrolling of the browser | |
*/ | |
preventDefault: function() { | |
if(this.srcEvent.preventManipulation) { | |
this.srcEvent.preventManipulation(); | |
} | |
if(this.srcEvent.preventDefault) { | |
this.srcEvent.preventDefault(); | |
} | |
}, | |
/** | |
* stop bubbling the event up to its parents | |
*/ | |
stopPropagation: function() { | |
this.srcEvent.stopPropagation(); | |
}, | |
/** | |
* immediately stop gesture detection | |
* might be useful after a swipe was detected | |
* @return {*} | |
*/ | |
stopDetect: function() { | |
return Detection.stopDetect(); | |
} | |
}; | |
} | |
}; | |
var PointerEvent = Hammer.PointerEvent = { | |
/** | |
* holds all pointers | |
* @type {Object} | |
*/ | |
pointers: {}, | |
/** | |
* get a list of pointers | |
* @returns {Array} touchlist | |
*/ | |
getTouchList: function() { | |
var touchlist = []; | |
// we can use forEach since pointerEvents only is in IE10 | |
Utils.each(this.pointers, function(pointer){ | |
touchlist.push(pointer); | |
}); | |
return touchlist; | |
}, | |
/** | |
* update the position of a pointer | |
* @param {String} type EVENT_END | |
* @param {Object} pointerEvent | |
*/ | |
updatePointer: function(type, pointerEvent) { | |
if(type == EVENT_END) { | |
delete this.pointers[pointerEvent.pointerId]; | |
} | |
else { | |
pointerEvent.identifier = pointerEvent.pointerId; | |
this.pointers[pointerEvent.pointerId] = pointerEvent; | |
} | |
// it's save to use Object.keys, since pointerEvents are only in newer browsers | |
return Object.keys(this.pointers).length; | |
}, | |
/** | |
* check if ev matches pointertype | |
* @param {String} pointerType POINTER_MOUSE | |
* @param {PointerEvent} ev | |
*/ | |
matchType: function(pointerType, ev) { | |
if(!ev.pointerType) { | |
return false; | |
} | |
var pt = ev.pointerType | |
, types = {}; | |
types[POINTER_MOUSE] = (pt === POINTER_MOUSE); | |
types[POINTER_TOUCH] = (pt === POINTER_TOUCH); | |
types[POINTER_PEN] = (pt === POINTER_PEN); | |
return types[pointerType]; | |
}, | |
/** | |
* get events | |
*/ | |
getEvents: function() { | |
return [ | |
'pointerdown MSPointerDown', | |
'pointermove MSPointerMove', | |
'pointerup pointercancel MSPointerUp MSPointerCancel' | |
]; | |
}, | |
/** | |
* reset the list | |
*/ | |
reset: function() { | |
this.pointers = {}; | |
} | |
}; | |
var Detection = Hammer.detection = { | |
// contains all registred Hammer.gestures in the correct order | |
gestures: [], | |
// data of the current Hammer.gesture detection session | |
current : null, | |
// the previous Hammer.gesture session data | |
// is a full clone of the previous gesture.current object | |
previous: null, | |
// when this becomes true, no gestures are fired | |
stopped : false, | |
/** | |
* start Hammer.gesture detection | |
* @param {Hammer.Instance} inst | |
* @param {Object} eventData | |
*/ | |
startDetect: function startDetect(inst, eventData) { | |
// already busy with a Hammer.gesture detection on an element | |
if(this.current) { | |
return; | |
} | |
this.stopped = false; | |
this.current = { | |
inst : inst, // reference to HammerInstance we're working for | |
startEvent : Utils.extend({}, eventData), // start eventData for distances, timing etc | |
lastEvent : false, // last eventData | |
lastVelocityEvent : false, // last eventData for velocity. | |
velocity : false, // current velocity | |
name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc | |
}; | |
this.detect(eventData); | |
}, | |
/** | |
* Hammer.gesture detection | |
* @param {Object} eventData | |
*/ | |
detect: function detect(eventData) { | |
if(!this.current || this.stopped) { | |
return; | |
} | |
// extend event data with calculations about scale, distance etc | |
eventData = this.extendEventData(eventData); | |
// instance options | |
var inst_options = this.current.inst.options; | |
// call Hammer.gesture handlers | |
Utils.each(this.gestures, function(gesture) { | |
// only when the instance options have enabled this gesture | |
if(!this.stopped && inst_options[gesture.name] !== false) { | |
// if a handler returns false, we stop with the detection | |
if(gesture.handler.call(gesture, eventData, this.current.inst) === false) { | |
this.stopDetect(); | |
return false; | |
} | |
} | |
}, this); | |
// store as previous event event | |
if(this.current) { | |
this.current.lastEvent = eventData; | |
} | |
// endevent, but not the last touch, so dont stop | |
if(eventData.eventType == EVENT_END && !eventData.touches.length - 1) { | |
this.stopDetect(); | |
} | |
return eventData; | |
}, | |
/** | |
* clear the Hammer.gesture vars | |
* this is called on endDetect, but can also be used when a final Hammer.gesture has been detected | |
* to stop other Hammer.gestures from being fired | |
*/ | |
stopDetect: function stopDetect() { | |
// clone current data to the store as the previous gesture | |
// used for the double tap gesture, since this is an other gesture detect session | |
this.previous = Utils.extend({}, this.current); | |
// reset the current | |
this.current = null; | |
// stopped! | |
this.stopped = true; | |
}, | |
/** | |
* extend eventData for Hammer.gestures | |
* @param {Object} ev | |
* @returns {Object} ev | |
*/ | |
extendEventData: function extendEventData(ev) { | |
var cur = this.current | |
, startEv = cur.startEvent; | |
// if the touches change, set the new touches over the startEvent touches | |
// this because touchevents don't have all the touches on touchstart, or the | |
// user must place his fingers at the EXACT same time on the screen, which is not realistic | |
// but, sometimes it happens that both fingers are touching at the EXACT same time | |
if(ev.touches.length != startEv.touches.length || ev.touches === startEv.touches) { | |
// extend 1 level deep to get the touchlist with the touch objects | |
startEv.touches = []; | |
Utils.each(ev.touches, function(touch) { | |
startEv.touches.push(Utils.extend({}, touch)); | |
}); | |
} | |
var delta_time = ev.timeStamp - startEv.timeStamp | |
, delta_x = ev.center.pageX - startEv.center.pageX | |
, delta_y = ev.center.pageY - startEv.center.pageY | |
, interimAngle | |
, interimDirection | |
, velocityEv = cur.lastVelocityEvent | |
, velocity = cur.velocity; | |
// calculate velocity every x ms | |
if (velocityEv && ev.timeStamp - velocityEv.timeStamp > Hammer.UPDATE_VELOCITY_INTERVAL) { | |
velocity = Utils.getVelocity(ev.timeStamp - velocityEv.timeStamp, | |
ev.center.pageX - velocityEv.center.pageX, | |
ev.center.pageY - velocityEv.center.pageY); | |
cur.lastVelocityEvent = ev; | |
cur.velocity = velocity; | |
} | |
else if(!cur.velocity) { | |
velocity = Utils.getVelocity(delta_time, delta_x, delta_y); | |
cur.lastVelocityEvent = ev; | |
cur.velocity = velocity; | |
} | |
// end events (e.g. dragend) don't have useful values for interimDirection & interimAngle | |
// because the previous event has exactly the same coordinates | |
// so for end events, take the previous values of interimDirection & interimAngle | |
// instead of recalculating them and getting a spurious '0' | |
if(ev.eventType == EVENT_END) { | |
interimAngle = cur.lastEvent && cur.lastEvent.interimAngle; | |
interimDirection = cur.lastEvent && cur.lastEvent.interimDirection; | |
} | |
else { | |
interimAngle = cur.lastEvent && | |
Utils.getAngle(cur.lastEvent.center, ev.center); | |
interimDirection = cur.lastEvent && | |
Utils.getDirection(cur.lastEvent.center, ev.center); | |
} | |
Utils.extend(ev, { | |
deltaTime: delta_time, | |
deltaX: delta_x, | |
deltaY: delta_y, | |
velocityX: velocity.x, | |
velocityY: velocity.y, | |
distance: Utils.getDistance(startEv.center, ev.center), | |
angle: Utils.getAngle(startEv.center, ev.center), | |
interimAngle: interimAngle, | |
direction: Utils.getDirection(startEv.center, ev.center), | |
interimDirection: interimDirection, | |
scale: Utils.getScale(startEv.touches, ev.touches), | |
rotation: Utils.getRotation(startEv.touches, ev.touches), | |
startEvent: startEv | |
}); | |
return ev; | |
}, | |
/** | |
* register new gesture | |
* @param {Object} gesture object, see gestures.js for documentation | |
* @returns {Array} gestures | |
*/ | |
register: function register(gesture) { | |
// add an enable gesture options if there is no given | |
var options = gesture.defaults || {}; | |
if(options[gesture.name] === undefined) { | |
options[gesture.name] = true; | |
} | |
// extend Hammer default options with the Hammer.gesture options | |
Utils.extend(Hammer.defaults, options, true); | |
// set its index | |
gesture.index = gesture.index || 1000; | |
// add Hammer.gesture to the list | |
this.gestures.push(gesture); | |
// sort the list by index | |
this.gestures.sort(function(a, b) { | |
if(a.index < b.index) { return -1; } | |
if(a.index > b.index) { return 1; } | |
return 0; | |
}); | |
return this.gestures; | |
} | |
}; | |
/** | |
* Drag | |
* Move with x fingers (default 1) around on the page. Blocking the scrolling when | |
* moving left and right is a good practice. When all the drag events are blocking | |
* you disable scrolling on that area. | |
* @events drag, drapleft, dragright, dragup, dragdown | |
*/ | |
Hammer.gestures.Drag = { | |
name : 'drag', | |
index : 50, | |
defaults : { | |
drag_min_distance : 10, | |
// Set correct_for_drag_min_distance to true to make the starting point of the drag | |
// be calculated from where the drag was triggered, not from where the touch started. | |
// Useful to avoid a jerk-starting drag, which can make fine-adjustments | |
// through dragging difficult, and be visually unappealing. | |
correct_for_drag_min_distance: true, | |
// set 0 for unlimited, but this can conflict with transform | |
drag_max_touches : 1, | |
// prevent default browser behavior when dragging occurs | |
// be careful with it, it makes the element a blocking element | |
// when you are using the drag gesture, it is a good practice to set this true | |
drag_block_horizontal : false, | |
drag_block_vertical : false, | |
// drag_lock_to_axis keeps the drag gesture on the axis that it started on, | |
// It disallows vertical directions if the initial direction was horizontal, and vice versa. | |
drag_lock_to_axis : false, | |
// drag lock only kicks in when distance > drag_lock_min_distance | |
// This way, locking occurs only when the distance has become large enough to reliably determine the direction | |
drag_lock_min_distance : 25 | |
}, | |
triggered: false, | |
handler : function dragGesture(ev, inst) { | |
// current gesture isnt drag, but dragged is true | |
// this means an other gesture is busy. now call dragend | |
if(Detection.current.name != this.name && this.triggered) { | |
inst.trigger(this.name + 'end', ev); | |
this.triggered = false; | |
return; | |
} | |
// max touches | |
if(inst.options.drag_max_touches > 0 && | |
ev.touches.length > inst.options.drag_max_touches) { | |
return; | |
} | |
switch(ev.eventType) { | |
case EVENT_START: | |
this.triggered = false; | |
break; | |
case EVENT_MOVE: | |
// when the distance we moved is too small we skip this gesture | |
// or we can be already in dragging | |
if(ev.distance < inst.options.drag_min_distance && | |
Detection.current.name != this.name) { | |
return; | |
} | |
// we are dragging! | |
if(Detection.current.name != this.name) { | |
Detection.current.name = this.name; | |
if(inst.options.correct_for_drag_min_distance && ev.distance > 0) { | |
// When a drag is triggered, set the event center to drag_min_distance pixels from the original event center. | |
// Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0. | |
// It might be useful to save the original start point somewhere | |
var factor = Math.abs(inst.options.drag_min_distance / ev.distance); | |
Detection.current.startEvent.center.pageX += ev.deltaX * factor; | |
Detection.current.startEvent.center.pageY += ev.deltaY * factor; | |
// recalculate event data using new start point | |
ev = Detection.extendEventData(ev); | |
} | |
} | |
// lock drag to axis? | |
if(Detection.current.lastEvent.drag_locked_to_axis || | |
( inst.options.drag_lock_to_axis && | |
inst.options.drag_lock_min_distance <= ev.distance | |
)) { | |
ev.drag_locked_to_axis = true; | |
} | |
var last_direction = Detection.current.lastEvent.direction; | |
if(ev.drag_locked_to_axis && last_direction !== ev.direction) { | |
// keep direction on the axis that the drag gesture started on | |
if(Utils.isVertical(last_direction)) { | |
ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN; | |
} | |
else { | |
ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; | |
} | |
} | |
// first time, trigger dragstart event | |
if(!this.triggered) { | |
inst.trigger(this.name + 'start', ev); | |
this.triggered = true; | |
} | |
// trigger events | |
inst.trigger(this.name, ev); | |
inst.trigger(this.name + ev.direction, ev); | |
var is_vertical = Utils.isVertical(ev.direction); | |
// block the browser events | |
if((inst.options.drag_block_vertical && is_vertical) || | |
(inst.options.drag_block_horizontal && !is_vertical)) { | |
ev.preventDefault(); | |
} | |
break; | |
case EVENT_END: | |
// trigger dragend | |
if(this.triggered) { | |
inst.trigger(this.name + 'end', ev); | |
} | |
this.triggered = false; | |
break; | |
} | |
} | |
}; | |
/** | |
* Hold | |
* Touch stays at the same place for x time | |
* @events hold | |
*/ | |
Hammer.gestures.Hold = { | |
name : 'hold', | |
index : 10, | |
defaults: { | |
hold_timeout : 500, | |
hold_threshold: 1 | |
}, | |
timer : null, | |
handler : function holdGesture(ev, inst) { | |
switch(ev.eventType) { | |
case EVENT_START: | |
// clear any running timers | |
clearTimeout(this.timer); | |
// set the gesture so we can check in the timeout if it still is | |
Detection.current.name = this.name; | |
// set timer and if after the timeout it still is hold, | |
// we trigger the hold event | |
this.timer = setTimeout(function() { | |
if(Detection.current.name == 'hold') { | |
inst.trigger('hold', ev); | |
} | |
}, inst.options.hold_timeout); | |
break; | |
// when you move or end we clear the timer | |
case EVENT_MOVE: | |
if(ev.distance > inst.options.hold_threshold) { | |
clearTimeout(this.timer); | |
} | |
break; | |
case EVENT_END: | |
clearTimeout(this.timer); | |
break; | |
} | |
} | |
}; | |
/** | |
* Release | |
* Called as last, tells the user has released the screen | |
* @events release | |
*/ | |
Hammer.gestures.Release = { | |
name : 'release', | |
index : Infinity, | |
handler: function releaseGesture(ev, inst) { | |
if(ev.eventType == EVENT_END) { | |
inst.trigger(this.name, ev); | |
} | |
} | |
}; | |
/** | |
* Swipe | |
* triggers swipe events when the end velocity is above the threshold | |
* for best usage, set prevent_default (on the drag gesture) to true | |
* @events swipe, swipeleft, swiperight, swipeup, swipedown | |
*/ | |
Hammer.gestures.Swipe = { | |
name : 'swipe', | |
index : 40, | |
defaults: { | |
swipe_min_touches: 1, | |
swipe_max_touches: 1, | |
swipe_velocity : 0.7 | |
}, | |
handler : function swipeGesture(ev, inst) { | |
if(ev.eventType == EVENT_END) { | |
// max touches | |
if(ev.touches.length < inst.options.swipe_min_touches || | |
ev.touches.length > inst.options.swipe_max_touches) { | |
return; | |
} | |
// when the distance we moved is too small we skip this gesture | |
// or we can be already in dragging | |
if(ev.velocityX > inst.options.swipe_velocity || | |
ev.velocityY > inst.options.swipe_velocity) { | |
// trigger swipe events | |
inst.trigger(this.name, ev); | |
inst.trigger(this.name + ev.direction, ev); | |
} | |
} | |
} | |
}; | |
/** | |
* Tap/DoubleTap | |
* Quick touch at a place or double at the same place | |
* @events tap, doubletap | |
*/ | |
Hammer.gestures.Tap = { | |
name : 'tap', | |
index : 100, | |
defaults: { | |
tap_max_touchtime : 250, | |
tap_max_distance : 10, | |
tap_always : true, | |
doubletap_distance: 20, | |
doubletap_interval: 300 | |
}, | |
has_moved: false, | |
handler : function tapGesture(ev, inst) { | |
var prev, since_prev, did_doubletap; | |
// reset moved state | |
if(ev.eventType == EVENT_START) { | |
this.has_moved = false; | |
} | |
// Track the distance we've moved. If it's above the max ONCE, remember that (fixes #406). | |
else if(ev.eventType == EVENT_MOVE && !this.moved) { | |
this.has_moved = (ev.distance > inst.options.tap_max_distance); | |
} | |
else if(ev.eventType == EVENT_END && | |
ev.srcEvent.type != 'touchcancel' && | |
ev.deltaTime < inst.options.tap_max_touchtime && !this.has_moved) { | |
// previous gesture, for the double tap since these are two different gesture detections | |
prev = Detection.previous; | |
since_prev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp; | |
did_doubletap = false; | |
// check if double tap | |
if(prev && prev.name == 'tap' && | |
(since_prev && since_prev < inst.options.doubletap_interval) && | |
ev.distance < inst.options.doubletap_distance) { | |
inst.trigger('doubletap', ev); | |
did_doubletap = true; | |
} | |
// do a single tap | |
if(!did_doubletap || inst.options.tap_always) { | |
Detection.current.name = 'tap'; | |
inst.trigger(Detection.current.name, ev); | |
} | |
} | |
} | |
}; | |
/** | |
* Touch | |
* Called as first, tells the user has touched the screen | |
* @events touch | |
*/ | |
Hammer.gestures.Touch = { | |
name : 'touch', | |
index : -Infinity, | |
defaults: { | |
// call preventDefault at touchstart, and makes the element blocking by | |
// disabling the scrolling of the page, but it improves gestures like | |
// transforming and dragging. | |
// be careful with using this, it can be very annoying for users to be stuck | |
// on the page | |
prevent_default : false, | |
// disable mouse events, so only touch (or pen!) input triggers events | |
prevent_mouseevents: false | |
}, | |
handler : function touchGesture(ev, inst) { | |
if(inst.options.prevent_mouseevents && | |
ev.pointerType == POINTER_MOUSE) { | |
ev.stopDetect(); | |
return; | |
} | |
if(inst.options.prevent_default) { | |
ev.preventDefault(); | |
} | |
if(ev.eventType == EVENT_START) { | |
inst.trigger(this.name, ev); | |
} | |
} | |
}; | |
/** | |
* Transform | |
* User want to scale or rotate with 2 fingers | |
* @events transform, pinch, pinchin, pinchout, rotate | |
*/ | |
Hammer.gestures.Transform = { | |
name : 'transform', | |
index : 45, | |
defaults : { | |
// factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 | |
transform_min_scale : 0.01, | |
// rotation in degrees | |
transform_min_rotation : 1, | |
// prevent default browser behavior when two touches are on the screen | |
// but it makes the element a blocking element | |
// when you are using the transform gesture, it is a good practice to set this true | |
transform_always_block : false, | |
// ensures that all touches occurred within the instance element | |
transform_within_instance: false | |
}, | |
triggered: false, | |
handler : function transformGesture(ev, inst) { | |
// current gesture isnt drag, but dragged is true | |
// this means an other gesture is busy. now call dragend | |
if(Detection.current.name != this.name && this.triggered) { | |
inst.trigger(this.name + 'end', ev); | |
this.triggered = false; | |
return; | |
} | |
// at least multitouch | |
if(ev.touches.length < 2) { | |
return; | |
} | |
// prevent default when two fingers are on the screen | |
if(inst.options.transform_always_block) { | |
ev.preventDefault(); | |
} | |
// check if all touches occurred within the instance element | |
if(inst.options.transform_within_instance) { | |
for(var i=-1; ev.touches[++i];) { | |
if(!Utils.hasParent(ev.touches[i].target, inst.element)) { | |
return; | |
} | |
} | |
} | |
switch(ev.eventType) { | |
case EVENT_START: | |
this.triggered = false; | |
break; | |
case EVENT_MOVE: | |
var scale_threshold = Math.abs(1 - ev.scale); | |
var rotation_threshold = Math.abs(ev.rotation); | |
// when the distance we moved is too small we skip this gesture | |
// or we can be already in dragging | |
if(scale_threshold < inst.options.transform_min_scale && | |
rotation_threshold < inst.options.transform_min_rotation) { | |
return; | |
} | |
// we are transforming! | |
Detection.current.name = this.name; | |
// first time, trigger dragstart event | |
if(!this.triggered) { | |
inst.trigger(this.name + 'start', ev); | |
this.triggered = true; | |
} | |
inst.trigger(this.name, ev); // basic transform event | |
// trigger rotate event | |
if(rotation_threshold > inst.options.transform_min_rotation) { | |
inst.trigger('rotate', ev); | |
} | |
// trigger pinch event | |
if(scale_threshold > inst.options.transform_min_scale) { | |
inst.trigger('pinch', ev); | |
inst.trigger('pinch' + (ev.scale<1 ? 'in' : 'out'), ev); | |
} | |
break; | |
case EVENT_END: | |
// trigger dragend | |
if(this.triggered) { | |
inst.trigger(this.name + 'end', ev); | |
} | |
this.triggered = false; | |
break; | |
} | |
} | |
}; | |
// AMD export | |
if(typeof define == 'function' && define.amd) { | |
define(function(){ | |
return Hammer; | |
}); | |
} | |
// commonjs export | |
else if(typeof module == 'object' && module.exports) { | |
module.exports = Hammer; | |
} | |
// browser export | |
else { | |
window.Hammer = Hammer; | |
} | |
})(window); |
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('node-base', function (Y, NAME) { | |
/** | |
* @module node | |
* @submodule node-base | |
*/ | |
var methods = [ | |
/** | |
* Determines whether each node has the given className. | |
* @method hasClass | |
* @for Node | |
* @param {String} className the class name to search for | |
* @return {Boolean} Whether or not the element has the specified class | |
*/ | |
'hasClass', | |
/** | |
* Adds a class name to each node. | |
* @method addClass | |
* @param {String} className the class name to add to the node's class attribute | |
* @chainable | |
*/ | |
'addClass', | |
/** | |
* Removes a class name from each node. | |
* @method removeClass | |
* @param {String} className the class name to remove from the node's class attribute | |
* @chainable | |
*/ | |
'removeClass', | |
/** | |
* Replace a class with another class for each node. | |
* If no oldClassName is present, the newClassName is simply added. | |
* @method replaceClass | |
* @param {String} oldClassName the class name to be replaced | |
* @param {String} newClassName the class name that will be replacing the old class name | |
* @chainable | |
*/ | |
'replaceClass', | |
/** | |
* If the className exists on the node it is removed, if it doesn't exist it is added. | |
* @method toggleClass | |
* @param {String} className the class name to be toggled | |
* @param {Boolean} force Option to force adding or removing the class. | |
* @chainable | |
*/ | |
'toggleClass' | |
]; | |
Y.Node.importMethod(Y.DOM, methods); | |
/** | |
* Determines whether each node has the given className. | |
* @method hasClass | |
* @see Node.hasClass | |
* @for NodeList | |
* @param {String} className the class name to search for | |
* @return {Array} An array of booleans for each node bound to the NodeList. | |
*/ | |
/** | |
* Adds a class name to each node. | |
* @method addClass | |
* @see Node.addClass | |
* @param {String} className the class name to add to the node's class attribute | |
* @chainable | |
*/ | |
/** | |
* Removes a class name from each node. | |
* @method removeClass | |
* @see Node.removeClass | |
* @param {String} className the class name to remove from the node's class attribute | |
* @chainable | |
*/ | |
/** | |
* Replace a class with another class for each node. | |
* If no oldClassName is present, the newClassName is simply added. | |
* @method replaceClass | |
* @see Node.replaceClass | |
* @param {String} oldClassName the class name to be replaced | |
* @param {String} newClassName the class name that will be replacing the old class name | |
* @chainable | |
*/ | |
/** | |
* If the className exists on the node it is removed, if it doesn't exist it is added. | |
* @method toggleClass | |
* @see Node.toggleClass | |
* @param {String} className the class name to be toggled | |
* @chainable | |
*/ | |
Y.NodeList.importMethod(Y.Node.prototype, methods); | |
/** | |
* @module node | |
* @submodule node-base | |
*/ | |
var Y_Node = Y.Node, | |
Y_DOM = Y.DOM; | |
/** | |
* Returns a new dom node using the provided markup string. | |
* @method create | |
* @static | |
* @param {String} html The markup used to create the element. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @param {HTMLDocument} doc An optional document context | |
* @return {Node} A Node instance bound to a DOM node or fragment | |
* @for Node | |
*/ | |
Y_Node.create = function(html, doc) { | |
if (doc && doc._node) { | |
doc = doc._node; | |
} | |
return Y.one(Y_DOM.create(html, doc)); | |
}; | |
Y.mix(Y_Node.prototype, { | |
/** | |
* Creates a new Node using the provided markup string. | |
* @method create | |
* @param {String} html The markup used to create the element. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @param {HTMLDocument} doc An optional document context | |
* @return {Node} A Node instance bound to a DOM node or fragment | |
*/ | |
create: Y_Node.create, | |
/** | |
* Inserts the content before the reference node. | |
* @method insert | |
* @param {String | Node | HTMLElement | NodeList | HTMLCollection} content The content to insert. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @param {Int | Node | HTMLElement | String} where The position to insert at. | |
* Possible "where" arguments | |
* <dl> | |
* <dt>Y.Node</dt> | |
* <dd>The Node to insert before</dd> | |
* <dt>HTMLElement</dt> | |
* <dd>The element to insert before</dd> | |
* <dt>Int</dt> | |
* <dd>The index of the child element to insert before</dd> | |
* <dt>"replace"</dt> | |
* <dd>Replaces the existing HTML</dd> | |
* <dt>"before"</dt> | |
* <dd>Inserts before the existing HTML</dd> | |
* <dt>"before"</dt> | |
* <dd>Inserts content before the node</dd> | |
* <dt>"after"</dt> | |
* <dd>Inserts content after the node</dd> | |
* </dl> | |
* @chainable | |
*/ | |
insert: function(content, where) { | |
this._insert(content, where); | |
return this; | |
}, | |
_insert: function(content, where) { | |
var node = this._node, | |
ret = null; | |
if (typeof where == 'number') { // allow index | |
where = this._node.childNodes[where]; | |
} else if (where && where._node) { // Node | |
where = where._node; | |
} | |
if (content && typeof content != 'string') { // allow Node or NodeList/Array instances | |
content = content._node || content._nodes || content; | |
} | |
ret = Y_DOM.addHTML(node, content, where); | |
return ret; | |
}, | |
/** | |
* Inserts the content as the firstChild of the node. | |
* @method prepend | |
* @param {String | Node | HTMLElement} content The content to insert. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @chainable | |
*/ | |
prepend: function(content) { | |
return this.insert(content, 0); | |
}, | |
/** | |
* Inserts the content as the lastChild of the node. | |
* @method append | |
* @param {String | Node | HTMLElement} content The content to insert. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @chainable | |
*/ | |
append: function(content) { | |
return this.insert(content, null); | |
}, | |
/** | |
* @method appendChild | |
* @param {String | HTMLElement | Node} node Node to be appended. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @return {Node} The appended node | |
*/ | |
appendChild: function(node) { | |
return Y_Node.scrubVal(this._insert(node)); | |
}, | |
/** | |
* @method insertBefore | |
* @param {String | HTMLElement | Node} newNode Node to be appended | |
* @param {HTMLElement | Node} refNode Node to be inserted before. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content. | |
* @return {Node} The inserted node | |
*/ | |
insertBefore: function(newNode, refNode) { | |
return Y.Node.scrubVal(this._insert(newNode, refNode)); | |
}, | |
/** | |
* Appends the node to the given node. | |
* @example | |
* // appendTo returns the node that has been created beforehand | |
* Y.Node.create('<p></p>').appendTo('body').set('text', 'hello world!'); | |
* @method appendTo | |
* @param {Node | HTMLElement | String} node The node to append to. | |
* If `node` is a string it will be considered as a css selector and only the first matching node will be used. | |
* @chainable | |
*/ | |
appendTo: function(node) { | |
Y.one(node).append(this); | |
return this; | |
}, | |
// This method is deprecated, and is intentionally left undocumented. | |
// Use `setHTML` instead. | |
setContent: function(content) { | |
this._insert(content, 'replace'); | |
return this; | |
}, | |
// This method is deprecated, and is intentionally left undocumented. | |
// Use `getHTML` instead. | |
getContent: function() { | |
var node = this; | |
if (node._node.nodeType === 11) { // 11 === Node.DOCUMENT_FRAGMENT_NODE | |
// "this", when it is a document fragment, must be cloned because | |
// the nodes contained in the fragment actually disappear once | |
// the fragment is appended anywhere | |
node = node.create("<div/>").append(node.cloneNode(true)); | |
} | |
return node.get("innerHTML"); | |
} | |
}); | |
/** | |
* Replaces the node's current html content with the content provided. | |
* Note that this passes to innerHTML and is not escaped. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content or `set('text')` to add as text. | |
* @method setHTML | |
* @param {String | Node | HTMLElement | NodeList | HTMLCollection} content The content to insert | |
* @chainable | |
*/ | |
Y.Node.prototype.setHTML = Y.Node.prototype.setContent; | |
/** | |
* Returns the node's current html content (e.g. innerHTML) | |
* @method getHTML | |
* @return {String} The html content | |
*/ | |
Y.Node.prototype.getHTML = Y.Node.prototype.getContent; | |
Y.NodeList.importMethod(Y.Node.prototype, [ | |
/** | |
* Called on each Node instance | |
* @for NodeList | |
* @method append | |
* @see Node.append | |
*/ | |
'append', | |
/** | |
* Called on each Node instance | |
* @for NodeList | |
* @method insert | |
* @see Node.insert | |
*/ | |
'insert', | |
/** | |
* Called on each Node instance | |
* @for NodeList | |
* @method appendChild | |
* @see Node.appendChild | |
*/ | |
'appendChild', | |
/** | |
* Called on each Node instance | |
* @for NodeList | |
* @method insertBefore | |
* @see Node.insertBefore | |
*/ | |
'insertBefore', | |
/** | |
* Called on each Node instance | |
* @for NodeList | |
* @method prepend | |
* @see Node.prepend | |
*/ | |
'prepend', | |
'setContent', | |
'getContent', | |
/** | |
* Called on each Node instance | |
* Note that this passes to innerHTML and is not escaped. | |
* Use <a href="../classes/Escape.html#method_html">`Y.Escape.html()`</a> | |
* to escape html content or `set('text')` to add as text. | |
* @for NodeList | |
* @method setHTML | |
* @see Node.setHTML | |
*/ | |
'setHTML', | |
/** | |
* Called on each Node instance | |
* @for NodeList | |
* @method getHTML | |
* @see Node.getHTML | |
*/ | |
'getHTML' | |
]); | |
/** | |
* @module node | |
* @submodule node-base | |
*/ | |
var Y_Node = Y.Node, | |
Y_DOM = Y.DOM; | |
/** | |
* Static collection of configuration attributes for special handling | |
* @property ATTRS | |
* @static | |
* @type object | |
*/ | |
Y_Node.ATTRS = { | |
/** | |
* Allows for getting and setting the text of an element. | |
* Formatting is preserved and special characters are treated literally. | |
* @config text | |
* @type String | |
*/ | |
text: { | |
getter: function() { | |
return Y_DOM.getText(this._node); | |
}, | |
setter: function(content) { | |
Y_DOM.setText(this._node, content); | |
return content; | |
} | |
}, | |
/** | |
* Allows for getting and setting the text of an element. | |
* Formatting is preserved and special characters are treated literally. | |
* @config for | |
* @type String | |
*/ | |
'for': { | |
getter: function() { | |
return Y_DOM.getAttribute(this._node, 'for'); | |
}, | |
setter: function(val) { | |
Y_DOM.setAttribute(this._node, 'for', val); | |
return val; | |
} | |
}, | |
'options': { | |
getter: function() { | |
return this._node.getElementsByTagName('option'); | |
} | |
}, | |
/** | |
* Returns a NodeList instance of all HTMLElement children. | |
* @readOnly | |
* @config children | |
* @type NodeList | |
*/ | |
'children': { | |
getter: function() { | |
var node = this._node, | |
children = node.children, | |
childNodes, i, len; | |
if (!children) { | |
childNodes = node.childNodes; | |
children = []; | |
for (i = 0, len = childNodes.length; i < len; ++i) { | |
if (childNodes[i].tagName) { | |
children[children.length] = childNodes[i]; | |
} | |
} | |
} | |
return Y.all(children); | |
} | |
}, | |
value: { | |
getter: function() { | |
return Y_DOM.getValue(this._node); | |
}, | |
setter: function(val) { | |
Y_DOM.setValue(this._node, val); | |
return val; | |
} | |
}, | |
gestureOpts: { | |
getter: function () { | |
// Create an empty gestureOpts if one does not exist. | |
var opts = this.getData('gestureOpts'); | |
if (!opts) { | |
this.setData('gestureOpts', {}); | |
return {}; | |
} | |
return opts; | |
}, | |
setter: function (opts) { | |
var currentOpts = this.get('gestureOpts'); | |
Y.mix(currentOpts, opts || {}, true); | |
this.setData('gestureOpts', currentOpts); | |
return currentOpts; | |
} | |
} | |
}; | |
Y.Node.importMethod(Y.DOM, [ | |
/** | |
* Allows setting attributes on DOM nodes, normalizing in some cases. | |
* This passes through to the DOM node, allowing for custom attributes. | |
* @method setAttribute | |
* @for Node | |
* @for NodeList | |
* @chainable | |
* @param {string} name The attribute name | |
* @param {string} value The value to set | |
*/ | |
'setAttribute', | |
/** | |
* Allows getting attributes on DOM nodes, normalizing in some cases. | |
* This passes through to the DOM node, allowing for custom attributes. | |
* @method getAttribute | |
* @for Node | |
* @for NodeList | |
* @param {string} name The attribute name | |
* @return {string} The attribute value | |
*/ | |
'getAttribute' | |
]); | |
/** | |
* @module node | |
* @submodule node-base | |
*/ | |
var Y_Node = Y.Node; | |
var Y_NodeList = Y.NodeList; | |
/** | |
* List of events that route to DOM events | |
* @static | |
* @property DOM_EVENTS | |
* @for Node | |
*/ | |
Y_Node.DOM_EVENTS = { | |
abort: 1, | |
beforeunload: 1, | |
blur: 1, | |
change: 1, | |
click: 1, | |
close: 1, | |
command: 1, | |
contextmenu: 1, | |
copy: 1, | |
cut: 1, | |
dblclick: 1, | |
DOMMouseScroll: 1, | |
drag: 1, | |
dragstart: 1, | |
dragenter: 1, | |
dragover: 1, | |
dragleave: 1, | |
dragend: 1, | |
drop: 1, | |
error: 1, | |
focus: 1, | |
key: 1, | |
keydown: 1, | |
keypress: 1, | |
keyup: 1, | |
load: 1, | |
message: 1, | |
mousedown: 1, | |
mouseenter: 1, | |
mouseleave: 1, | |
mousemove: 1, | |
mousemultiwheel: 1, | |
mouseout: 1, | |
mouseover: 1, | |
mouseup: 1, | |
mousewheel: 1, | |
orientationchange: 1, | |
paste: 1, | |
reset: 1, | |
resize: 1, | |
select: 1, | |
selectstart: 1, | |
submit: 1, | |
scroll: 1, | |
textInput: 1, | |
unload: 1 | |
}; | |
// Add custom event adaptors to this list. This will make it so | |
// that delegate, key, available, contentready, etc all will | |
// be available through Node.on | |
Y.mix(Y_Node.DOM_EVENTS, Y.Env.evt.plugins); | |
Y.augment(Y_Node, Y.EventTarget); | |
Y.mix(Y_Node.prototype, { | |
/** | |
* Removes event listeners from the node and (optionally) its subtree | |
* @method purge | |
* @param {Boolean} recurse (optional) Whether or not to remove listeners from the | |
* node's subtree | |
* @param {String} type (optional) Only remove listeners of the specified type | |
* @chainable | |
* | |
*/ | |
purge: function(recurse, type) { | |
Y.Event.purgeElement(this._node, recurse, type); | |
return this; | |
} | |
}); | |
Y.mix(Y.NodeList.prototype, { | |
_prepEvtArgs: function(type, fn, context) { | |
// map to Y.on/after signature (type, fn, nodes, context, arg1, arg2, etc) | |
var args = Y.Array(arguments, 0, true); | |
if (args.length < 2) { // type only (event hash) just add nodes | |
args[2] = this._nodes; | |
} else { | |
args.splice(2, 0, this._nodes); | |
} | |
args[3] = context || this; // default to NodeList instance as context | |
return args; | |
}, | |
/** | |
Subscribe a callback function for each `Node` in the collection to execute | |
in response to a DOM event. | |
NOTE: Generally, the `on()` method should be avoided on `NodeLists`, in | |
favor of using event delegation from a parent Node. See the Event user | |
guide for details. | |
Most DOM events are associated with a preventable default behavior, such as | |
link clicks navigating to a new page. Callbacks are passed a | |
`DOMEventFacade` object as their first argument (usually called `e`) that | |
can be used to prevent this default behavior with `e.preventDefault()`. See | |
the `DOMEventFacade` API for all available properties and methods on the | |
object. | |
By default, the `this` object will be the `NodeList` that the subscription | |
came from, <em>not the `Node` that received the event</em>. Use | |
`e.currentTarget` to refer to the `Node`. | |
Returning `false` from a callback is supported as an alternative to calling | |
`e.preventDefault(); e.stopPropagation();`. However, it is recommended to | |
use the event methods. | |
@example | |
Y.all(".sku").on("keydown", function (e) { | |
if (e.keyCode === 13) { | |
e.preventDefault(); | |
// Use e.currentTarget to refer to the individual Node | |
var item = Y.MyApp.searchInventory( e.currentTarget.get('value') ); | |
// etc ... | |
} | |
}); | |
@method on | |
@param {String} type The name of the event | |
@param {Function} fn The callback to execute in response to the event | |
@param {Object} [context] Override `this` object in callback | |
@param {Any} [arg*] 0..n additional arguments to supply to the subscriber | |
@return {EventHandle} A subscription handle capable of detaching that | |
subscription | |
@for NodeList | |
**/ | |
on: function(type, fn, context) { | |
return Y.on.apply(Y, this._prepEvtArgs.apply(this, arguments)); | |
}, | |
/** | |
* Applies an one-time event listener to each Node bound to the NodeList. | |
* @method once | |
* @param {String} type The event being listened for | |
* @param {Function} fn The handler to call when the event fires | |
* @param {Object} context The context to call the handler with. | |
* Default is the NodeList instance. | |
* @return {EventHandle} A subscription handle capable of detaching that | |
* subscription | |
* @for NodeList | |
*/ | |
once: function(type, fn, context) { | |
return Y.once.apply(Y, this._prepEvtArgs.apply(this, arguments)); | |
}, | |
/** | |
* Applies an event listener to each Node bound to the NodeList. | |
* The handler is called only after all on() handlers are called | |
* and the event is not prevented. | |
* @method after | |
* @param {String} type The event being listened for | |
* @param {Function} fn The handler to call when the event fires | |
* @param {Object} context The context to call the handler with. | |
* Default is the NodeList instance. | |
* @return {EventHandle} A subscription handle capable of detaching that | |
* subscription | |
* @for NodeList | |
*/ | |
after: function(type, fn, context) { | |
return Y.after.apply(Y, this._prepEvtArgs.apply(this, arguments)); | |
}, | |
/** | |
* Applies an one-time event listener to each Node bound to the NodeList | |
* that will be called only after all on() handlers are called and the | |
* event is not prevented. | |
* | |
* @method onceAfter | |
* @param {String} type The event being listened for | |
* @param {Function} fn The handler to call when the event fires | |
* @param {Object} context The context to call the handler with. | |
* Default is the NodeList instance. | |
* @return {EventHandle} A subscription handle capable of detaching that | |
* subscription | |
* @for NodeList | |
*/ | |
onceAfter: function(type, fn, context) { | |
return Y.onceAfter.apply(Y, this._prepEvtArgs.apply(this, arguments)); | |
} | |
}); | |
Y_NodeList.importMethod(Y.Node.prototype, [ | |
/** | |
* Called on each Node instance | |
* @method detach | |
* @see Node.detach | |
* @for NodeList | |
*/ | |
'detach', | |
/** Called on each Node instance | |
* @method detachAll | |
* @see Node.detachAll | |
* @for NodeList | |
*/ | |
'detachAll' | |
]); | |
/** | |
Subscribe a callback function to execute in response to a DOM event or custom | |
event. | |
Most DOM events are associated with a preventable default behavior such as | |
link clicks navigating to a new page. Callbacks are passed a `DOMEventFacade` | |
object as their first argument (usually called `e`) that can be used to | |
prevent this default behavior with `e.preventDefault()`. See the | |
`DOMEventFacade` API for all available properties and methods on the object. | |
If the event name passed as the first parameter is not a whitelisted DOM event, | |
it will be treated as a custom event subscriptions, allowing | |
`node.fire('customEventName')` later in the code. Refer to the Event user guide | |
for the full DOM event whitelist. | |
By default, the `this` object in the callback will refer to the subscribed | |
`Node`. | |
Returning `false` from a callback is supported as an alternative to calling | |
`e.preventDefault(); e.stopPropagation();`. However, it is recommended to use | |
the event methods. | |
@example | |
Y.one("#my-form").on("submit", function (e) { | |
e.preventDefault(); | |
// proceed with ajax form submission instead... | |
}); | |
@method on | |
@param {String} type The name of the event | |
@param {Function} fn The callback to execute in response to the event | |
@param {Object} [context] Override `this` object in callback | |
@param {Any} [arg*] 0..n additional arguments to supply to the subscriber | |
@return {EventHandle} A subscription handle capable of detaching that | |
subscription | |
@for Node | |
**/ | |
Y.mix(Y.Node.ATTRS, { | |
offsetHeight: { | |
setter: function(h) { | |
Y.DOM.setHeight(this._node, h); | |
return h; | |
}, | |
getter: function() { | |
return this._node.offsetHeight; | |
} | |
}, | |
offsetWidth: { | |
setter: function(w) { | |
Y.DOM.setWidth(this._node, w); | |
return w; | |
}, | |
getter: function() { | |
return this._node.offsetWidth; | |
} | |
} | |
}); | |
Y.mix(Y.Node.prototype, { | |
sizeTo: function(w, h) { | |
var node; | |
if (arguments.length < 2) { | |
node = Y.one(w); | |
w = node.get('offsetWidth'); | |
h = node.get('offsetHeight'); | |
} | |
this.setAttrs({ | |
offsetWidth: w, | |
offsetHeight: h | |
}); | |
} | |
}); | |
if (!Y.config.doc.documentElement.hasAttribute) { // IE < 8 | |
Y.Node.prototype.hasAttribute = function(attr) { | |
if (attr === 'value') { | |
if (this.get('value') !== "") { // IE < 8 fails to populate specified when set in HTML | |
return true; | |
} | |
} | |
return !!(this._node.attributes[attr] && | |
this._node.attributes[attr].specified); | |
}; | |
} | |
// IE throws an error when calling focus() on an element that's invisible, not | |
// displayed, or disabled. | |
Y.Node.prototype.focus = function () { | |
try { | |
this._node.focus(); | |
} catch (e) { | |
} | |
return this; | |
}; | |
// IE throws error when setting input.type = 'hidden', | |
// input.setAttribute('type', 'hidden') and input.attributes.type.value = 'hidden' | |
Y.Node.ATTRS.type = { | |
setter: function(val) { | |
if (val === 'hidden') { | |
try { | |
this._node.type = 'hidden'; | |
} catch(e) { | |
this._node.style.display = 'none'; | |
this._inputType = 'hidden'; | |
} | |
} else { | |
try { // IE errors when changing the type from "hidden' | |
this._node.type = val; | |
} catch (e) { | |
} | |
} | |
return val; | |
}, | |
getter: function() { | |
return this._inputType || this._node.type; | |
}, | |
_bypassProxy: true // don't update DOM when using with Attribute | |
}; | |
if (Y.config.doc.createElement('form').elements.nodeType) { | |
// IE: elements collection is also FORM node which trips up scrubVal. | |
Y.Node.ATTRS.elements = { | |
getter: function() { | |
return this.all('input, textarea, button, select'); | |
} | |
}; | |
} | |
/** | |
* Provides methods for managing custom Node data. | |
* | |
* @module node | |
* @main node | |
* @submodule node-data | |
*/ | |
Y.mix(Y.Node.prototype, { | |
_initData: function() { | |
if (! ('_data' in this)) { | |
this._data = {}; | |
} | |
}, | |
/** | |
* @method getData | |
* @for Node | |
* @description Retrieves arbitrary data stored on a Node instance. | |
* If no data is associated with the Node, it will attempt to retrieve | |
* a value from the corresponding HTML data attribute. (e.g. node.getData('foo') | |
* will check node.getAttribute('data-foo')). | |
* @param {string} name Optional name of the data field to retrieve. | |
* If no name is given, all data is returned. | |
* @return {any | Object} Whatever is stored at the given field, | |
* or an object hash of all fields. | |
*/ | |
getData: function(name) { | |
this._initData(); | |
var data = this._data, | |
ret = data; | |
if (arguments.length) { // single field | |
if (name in data) { | |
ret = data[name]; | |
} else { // initialize from HTML attribute | |
ret = this._getDataAttribute(name); | |
} | |
} else if (typeof data == 'object' && data !== null) { // all fields | |
ret = {}; | |
Y.Object.each(data, function(v, n) { | |
ret[n] = v; | |
}); | |
ret = this._getDataAttributes(ret); | |
} | |
return ret; | |
}, | |
_getDataAttributes: function(ret) { | |
ret = ret || {}; | |
var i = 0, | |
attrs = this._node.attributes, | |
len = attrs.length, | |
prefix = this.DATA_PREFIX, | |
prefixLength = prefix.length, | |
name; | |
while (i < len) { | |
name = attrs[i].name; | |
if (name.indexOf(prefix) === 0) { | |
name = name.substr(prefixLength); | |
if (!(name in ret)) { // only merge if not already stored | |
ret[name] = this._getDataAttribute(name); | |
} | |
} | |
i += 1; | |
} | |
return ret; | |
}, | |
_getDataAttribute: function(name) { | |
name = this.DATA_PREFIX + name; | |
var node = this._node, | |
attrs = node.attributes, | |
data = attrs && attrs[name] && attrs[name].value; | |
return data; | |
}, | |
/** | |
* @method setData | |
* @for Node | |
* @description Stores arbitrary data on a Node instance. | |
* This is not stored with the DOM node. | |
* @param {string} name The name of the field to set. If no val | |
* is given, name is treated as the data and overrides any existing data. | |
* @param {any} val The value to be assigned to the field. | |
* @chainable | |
*/ | |
setData: function(name, val) { | |
this._initData(); | |
if (arguments.length > 1) { | |
this._data[name] = val; | |
} else { | |
this._data = name; | |
} | |
return this; | |
}, | |
/** | |
* @method clearData | |
* @for Node | |
* @description Clears internally stored data. | |
* @param {string} name The name of the field to clear. If no name | |
* is given, all data is cleared. | |
* @chainable | |
*/ | |
clearData: function(name) { | |
if ('_data' in this) { | |
if (typeof name != 'undefined') { | |
delete this._data[name]; | |
} else { | |
delete this._data; | |
} | |
} | |
return this; | |
} | |
}); | |
Y.mix(Y.NodeList.prototype, { | |
/** | |
* @method getData | |
* @for NodeList | |
* @description Retrieves arbitrary data stored on each Node instance | |
* bound to the NodeList. | |
* @see Node | |
* @param {string} name Optional name of the data field to retrieve. | |
* If no name is given, all data is returned. | |
* @return {Array} An array containing all of the data for each Node instance. | |
* or an object hash of all fields. | |
*/ | |
getData: function(name) { | |
var args = (arguments.length) ? [name] : []; | |
return this._invoke('getData', args, true); | |
}, | |
/** | |
* @method setData | |
* @for NodeList | |
* @description Stores arbitrary data on each Node instance bound to the | |
* NodeList. This is not stored with the DOM node. | |
* @param {string} name The name of the field to set. If no name | |
* is given, name is treated as the data and overrides any existing data. | |
* @param {any} val The value to be assigned to the field. | |
* @chainable | |
*/ | |
setData: function(name, val) { | |
var args = (arguments.length > 1) ? [name, val] : [name]; | |
return this._invoke('setData', args); | |
}, | |
/** | |
* @method clearData | |
* @for NodeList | |
* @description Clears data on all Node instances bound to the NodeList. | |
* @param {string} name The name of the field to clear. If no name | |
* is given, all data is cleared. | |
* @chainable | |
*/ | |
clearData: function(name) { | |
var args = (arguments.length) ? [name] : []; | |
return this._invoke('clearData', [name]); | |
} | |
}); | |
}, '@VERSION@', {"requires": ["event-base", "node-core", "dom-base", "dom-style"]}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment