123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- define(["./_base/kernel", "./aspect", "./dom", "./dom-class", "./_base/lang", "./on", "./has", "./mouse", "./domReady", "./_base/window"],
- function(dojo, aspect, dom, domClass, lang, on, has, mouse, domReady, win){
- // module:
- // dojo/touch
- var ios4 = has("ios") < 5;
- // Detect if platform supports Pointer Events, and if so, the names of the events (pointerdown vs. MSPointerDown).
- var hasPointer = has("pointer-events") || has("MSPointer"),
- pointer = (function () {
- var pointer = {};
- for (var type in { down: 1, move: 1, up: 1, cancel: 1, over: 1, out: 1 }) {
- pointer[type] = has("MSPointer") ?
- "MSPointer" + type.charAt(0).toUpperCase() + type.slice(1) :
- "pointer" + type;
- }
- return pointer;
- })();
- // Detect if platform supports the webkit touchstart/touchend/... events
- var hasTouch = has("touch-events");
- // Click generation variables
- var clicksInited, clickTracker, useTarget = false, clickTarget, clickX, clickY, clickDx, clickDy, clickTime;
- // Time of most recent touchstart, touchmove, or touchend event
- var lastTouch;
- function dualEvent(mouseType, touchType, pointerType){
- // Returns synthetic event that listens for both the specified mouse event and specified touch event.
- // But ignore fake mouse events that were generated due to the user touching the screen.
- if(hasPointer && pointerType){
- // IE10+: MSPointer* events are designed to handle both mouse and touch in a uniform way,
- // so just use that regardless of hasTouch.
- return function(node, listener){
- return on(node, pointerType, listener);
- }
- }else if(hasTouch){
- return function(node, listener){
- var handle1 = on(node, touchType, function(evt){
- listener.call(this, evt);
- // On slow mobile browsers (see https://bugs.dojotoolkit.org/ticket/17634),
- // a handler for a touch event may take >1s to run. That time shouldn't
- // be included in the calculation for lastTouch.
- lastTouch = (new Date()).getTime();
- }),
- handle2 = on(node, mouseType, function(evt){
- if(!lastTouch || (new Date()).getTime() > lastTouch + 1000){
- listener.call(this, evt);
- }
- });
- return {
- remove: function(){
- handle1.remove();
- handle2.remove();
- }
- };
- };
- }else{
- // Avoid creating listeners for touch events on performance sensitive older browsers like IE6
- return function(node, listener){
- return on(node, mouseType, listener);
- }
- }
- }
- function marked(/*DOMNode*/ node){
- // Search for node ancestor has been marked with the dojoClick property to indicate special processing.
- // Returns marked ancestor.
- do{
- if(node.dojoClick !== undefined){ return node; }
- }while(node = node.parentNode);
- }
-
- function doClicks(e, moveType, endType){
- // summary:
- // Setup touch listeners to generate synthetic clicks immediately (rather than waiting for the browser
- // to generate clicks after the double-tap delay) and consistently (regardless of whether event.preventDefault()
- // was called in an event listener. Synthetic clicks are generated only if a node or one of its ancestors has
- // its dojoClick property set to truthy. If a node receives synthetic clicks because one of its ancestors has its
- // dojoClick property set to truthy, you can disable synthetic clicks on this node by setting its own dojoClick property
- // to falsy.
-
- var markedNode = marked(e.target);
- clickTracker = !e.target.disabled && markedNode && markedNode.dojoClick; // click threshold = true, number, x/y object, or "useTarget"
- if(clickTracker){
- useTarget = (clickTracker == "useTarget");
- clickTarget = (useTarget?markedNode:e.target);
- if(useTarget){
- // We expect a click, so prevent any other
- // default action on "touchpress"
- e.preventDefault();
- }
- clickX = e.changedTouches ? e.changedTouches[0].pageX - win.global.pageXOffset : e.clientX;
- clickY = e.changedTouches ? e.changedTouches[0].pageY - win.global.pageYOffset : e.clientY;
- clickDx = (typeof clickTracker == "object" ? clickTracker.x : (typeof clickTracker == "number" ? clickTracker : 0)) || 4;
- clickDy = (typeof clickTracker == "object" ? clickTracker.y : (typeof clickTracker == "number" ? clickTracker : 0)) || 4;
- // add move/end handlers only the first time a node with dojoClick is seen,
- // so we don't add too much overhead when dojoClick is never set.
- if(!clicksInited){
- clicksInited = true;
- function updateClickTracker(e){
- if(useTarget){
- clickTracker = dom.isDescendant(
- win.doc.elementFromPoint(
- (e.changedTouches ? e.changedTouches[0].pageX - win.global.pageXOffset : e.clientX),
- (e.changedTouches ? e.changedTouches[0].pageY - win.global.pageYOffset : e.clientY)),
- clickTarget);
- }else{
- clickTracker = clickTracker &&
- (e.changedTouches ? e.changedTouches[0].target : e.target) == clickTarget &&
- Math.abs((e.changedTouches ? e.changedTouches[0].pageX - win.global.pageXOffset : e.clientX) - clickX) <= clickDx &&
- Math.abs((e.changedTouches ? e.changedTouches[0].pageY - win.global.pageYOffset : e.clientY) - clickY) <= clickDy;
- }
- }
- win.doc.addEventListener(moveType, function(e){
- updateClickTracker(e);
- if(useTarget){
- // prevent native scroll event and ensure touchend is
- // fire after touch moves between press and release.
- e.preventDefault();
- }
- }, true);
- win.doc.addEventListener(endType, function(e){
- updateClickTracker(e);
- if(clickTracker){
- clickTime = (new Date()).getTime();
- var target = (useTarget?clickTarget:e.target);
- if(target.tagName === "LABEL"){
- // when clicking on a label, forward click to its associated input if any
- target = dom.byId(target.getAttribute("for")) || target;
- }
- //some attributes can be on the Touch object, not on the Event:
- //http://www.w3.org/TR/touch-events/#touch-interface
- var src = (e.changedTouches) ? e.changedTouches[0] : e;
- //create the synthetic event.
- //http://www.w3.org/TR/DOM-Level-3-Events/#widl-MouseEvent-initMouseEvent
- var clickEvt = document.createEvent("MouseEvents");
- clickEvt._dojo_click = true;
- clickEvt.initMouseEvent("click",
- true, //bubbles
- true, //cancelable
- e.view,
- e.detail,
- src.screenX,
- src.screenY,
- src.clientX,
- src.clientY,
- e.ctrlKey,
- e.altKey,
- e.shiftKey,
- e.metaKey,
- 0, //button
- null //related target
- );
- setTimeout(function(){
- on.emit(target, "click", clickEvt);
- // refresh clickTime in case app-defined click handler took a long time to run
- clickTime = (new Date()).getTime();
- }, 0);
- }
- }, true);
- function stopNativeEvents(type){
- win.doc.addEventListener(type, function(e){
- // Stop native events when we emitted our own click event. Note that the native click may occur
- // on a different node than the synthetic click event was generated on. For example,
- // click on a menu item, causing the menu to disappear, and then (~300ms later) the browser
- // sends a click event to the node that was *underneath* the menu. So stop all native events
- // sent shortly after ours, similar to what is done in dualEvent.
- // The INPUT.dijitOffScreen test is for offscreen inputs used in dijit/form/Button, on which
- // we call click() explicitly, we don't want to stop this event.
- if(!e._dojo_click &&
- (new Date()).getTime() <= clickTime + 1000 &&
- !(e.target.tagName == "INPUT" && domClass.contains(e.target, "dijitOffScreen"))){
- e.stopPropagation();
- e.stopImmediatePropagation && e.stopImmediatePropagation();
- if(type == "click" && (e.target.tagName != "INPUT" || e.target.type == "radio" || e.target.type == "checkbox")
- && e.target.tagName != "TEXTAREA" && e.target.tagName != "AUDIO" && e.target.tagName != "VIDEO"){
- // preventDefault() breaks textual <input>s on android, keyboard doesn't popup,
- // but it is still needed for checkboxes and radio buttons, otherwise in some cases
- // the checked state becomes inconsistent with the widget's state
- e.preventDefault();
- }
- }
- }, true);
- }
- stopNativeEvents("click");
- // We also stop mousedown/up since these would be sent well after with our "fast" click (300ms),
- // which can confuse some dijit widgets.
- stopNativeEvents("mousedown");
- stopNativeEvents("mouseup");
- }
- }
- }
- var hoveredNode;
- if(hasPointer){
- // MSPointer (IE10+) already has support for over and out, so we just need to init click support
- domReady(function(){
- win.doc.addEventListener(pointer.down, function(evt){
- doClicks(evt, pointer.move, pointer.up);
- }, true);
- });
- }else if(hasTouch){
- domReady(function(){
- // Keep track of currently hovered node
- hoveredNode = win.body(); // currently hovered node
- win.doc.addEventListener("touchstart", function(evt){
- lastTouch = (new Date()).getTime();
- // Precede touchstart event with touch.over event. DnD depends on this.
- // Use addEventListener(cb, true) to run cb before any touchstart handlers on node run,
- // and to ensure this code runs even if the listener on the node does event.stop().
- var oldNode = hoveredNode;
- hoveredNode = evt.target;
- on.emit(oldNode, "dojotouchout", {
- relatedTarget: hoveredNode,
- bubbles: true
- });
- on.emit(hoveredNode, "dojotouchover", {
- relatedTarget: oldNode,
- bubbles: true
- });
- doClicks(evt, "touchmove", "touchend"); // init click generation
- }, true);
- function copyEventProps(evt){
- // Make copy of event object and also set bubbles:true. Used when calling on.emit().
- var props = lang.delegate(evt, {
- bubbles: true
- });
- if(has("ios") >= 6){
- // On iOS6 "touches" became a non-enumerable property, which
- // is not hit by for...in. Ditto for the other properties below.
- props.touches = evt.touches;
- props.altKey = evt.altKey;
- props.changedTouches = evt.changedTouches;
- props.ctrlKey = evt.ctrlKey;
- props.metaKey = evt.metaKey;
- props.shiftKey = evt.shiftKey;
- props.targetTouches = evt.targetTouches;
- }
- return props;
- }
- on(win.doc, "touchmove", function(evt){
- lastTouch = (new Date()).getTime();
- var newNode = win.doc.elementFromPoint(
- evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
- evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
- );
- if(newNode){
- // Fire synthetic touchover and touchout events on nodes since the browser won't do it natively.
- if(hoveredNode !== newNode){
- // touch out on the old node
- on.emit(hoveredNode, "dojotouchout", {
- relatedTarget: newNode,
- bubbles: true
- });
- // touchover on the new node
- on.emit(newNode, "dojotouchover", {
- relatedTarget: hoveredNode,
- bubbles: true
- });
- hoveredNode = newNode;
- }
- // Unlike a listener on "touchmove", on(node, "dojotouchmove", listener) fires when the finger
- // drags over the specified node, regardless of which node the touch started on.
- if(!on.emit(newNode, "dojotouchmove", copyEventProps(evt))){
- // emit returns false when synthetic event "dojotouchmove" is cancelled, so we prevent the
- // default behavior of the underlying native event "touchmove".
- evt.preventDefault();
- }
- }
- });
- // Fire a dojotouchend event on the node where the finger was before it was removed from the screen.
- // This is different than the native touchend, which fires on the node where the drag started.
- on(win.doc, "touchend", function(evt){
- lastTouch = (new Date()).getTime();
- var node = win.doc.elementFromPoint(
- evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
- evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
- ) || win.body(); // if out of the screen
- on.emit(node, "dojotouchend", copyEventProps(evt));
- });
- });
- }
- //device neutral events - touch.press|move|release|cancel/over/out
- var touch = {
- press: dualEvent("mousedown", "touchstart", pointer.down),
- move: dualEvent("mousemove", "dojotouchmove", pointer.move),
- release: dualEvent("mouseup", "dojotouchend", pointer.up),
- cancel: dualEvent(mouse.leave, "touchcancel", hasPointer ? pointer.cancel : null),
- over: dualEvent("mouseover", "dojotouchover", pointer.over),
- out: dualEvent("mouseout", "dojotouchout", pointer.out),
- enter: mouse._eventHandler(dualEvent("mouseover","dojotouchover", pointer.over)),
- leave: mouse._eventHandler(dualEvent("mouseout", "dojotouchout", pointer.out))
- };
- /*=====
- touch = {
- // summary:
- // This module provides unified touch event handlers by exporting
- // press, move, release and cancel which can also run well on desktop.
- // Based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
- // Also, if the dojoClick property is set to truthy on a DOM node, dojo/touch generates
- // click events immediately for this node and its descendants (except for descendants that
- // have a dojoClick property set to falsy), to avoid the delay before native browser click events,
- // and regardless of whether evt.preventDefault() was called in a touch.press event listener.
- //
- // example:
- // Used with dojo/on
- // | define(["dojo/on", "dojo/touch"], function(on, touch){
- // | on(node, touch.press, function(e){});
- // | on(node, touch.move, function(e){});
- // | on(node, touch.release, function(e){});
- // | on(node, touch.cancel, function(e){});
- // example:
- // Used with touch.* directly
- // | touch.press(node, function(e){});
- // | touch.move(node, function(e){});
- // | touch.release(node, function(e){});
- // | touch.cancel(node, function(e){});
- // example:
- // Have dojo/touch generate clicks without delay, with a default move threshold of 4 pixels
- // | node.dojoClick = true;
- // example:
- // Have dojo/touch generate clicks without delay, with a move threshold of 10 pixels horizontally and vertically
- // | node.dojoClick = 10;
- // example:
- // Have dojo/touch generate clicks without delay, with a move threshold of 50 pixels horizontally and 10 pixels vertically
- // | node.dojoClick = {x:50, y:5};
- // example:
- // Disable clicks without delay generated by dojo/touch on a node that has an ancestor with property dojoClick set to truthy
- // | node.dojoClick = false;
- press: function(node, listener){
- // summary:
- // Register a listener to 'touchstart'|'mousedown' for the given node
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- move: function(node, listener){
- // summary:
- // Register a listener that fires when the mouse cursor or a finger is dragged over the given node.
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- release: function(node, listener){
- // summary:
- // Register a listener to releasing the mouse button while the cursor is over the given node
- // (i.e. "mouseup") or for removing the finger from the screen while touching the given node.
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- cancel: function(node, listener){
- // summary:
- // Register a listener to 'touchcancel'|'mouseleave' for the given node
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- over: function(node, listener){
- // summary:
- // Register a listener to 'mouseover' or touch equivalent for the given node
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- out: function(node, listener){
- // summary:
- // Register a listener to 'mouseout' or touch equivalent for the given node
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- enter: function(node, listener){
- // summary:
- // Register a listener to mouse.enter or touch equivalent for the given node
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- },
- leave: function(node, listener){
- // summary:
- // Register a listener to mouse.leave or touch equivalent for the given node
- // node: Dom
- // Target node to listen to
- // listener: Function
- // Callback function
- // returns:
- // A handle which will be used to remove the listener by handle.remove()
- }
- };
- =====*/
- has("extend-dojo") && (dojo.touch = touch);
- return touch;
- });
|