source: Dev/trunk/src/client/dojo/touch.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 14.8 KB
Line 
1define(["./_base/kernel", "./aspect", "./dom", "./dom-class", "./_base/lang", "./on", "./has", "./mouse", "./domReady", "./_base/window"],
2function(dojo, aspect, dom, domClass, lang, on, has, mouse, domReady, win){
3
4        // module:
5        //              dojo/touch
6
7        var hasTouch = has("touch");
8
9        var ios4 = has("ios") < 5;
10       
11        var msPointer = navigator.pointerEnabled || navigator.msPointerEnabled,
12                pointer = (function () {
13                        var pointer = {};
14                        for (var type in { down: 1, move: 1, up: 1, cancel: 1, over: 1, out: 1 }) {
15                                pointer[type] = !navigator.pointerEnabled ?
16                                        "MSPointer" + type.charAt(0).toUpperCase() + type.slice(1) :
17                                        "pointer" + type;
18                        }
19                        return pointer;
20                })();
21
22        // Click generation variables
23        var clicksInited, clickTracker, clickTarget, clickX, clickY, clickDx, clickDy, clickTime;
24
25        // Time of most recent touchstart, touchmove, or touchend event
26        var lastTouch;
27
28        function dualEvent(mouseType, touchType, msPointerType){
29                // Returns synthetic event that listens for both the specified mouse event and specified touch event.
30                // But ignore fake mouse events that were generated due to the user touching the screen.
31                if(msPointer && msPointerType){
32                        // IE10+: MSPointer* events are designed to handle both mouse and touch in a uniform way,
33                        // so just use that regardless of hasTouch.
34                        return function(node, listener){
35                                return on(node, msPointerType, listener);
36                        }
37                }else if(hasTouch){
38                        return function(node, listener){
39                                var handle1 = on(node, touchType, listener),
40                                        handle2 = on(node, mouseType, function(evt){
41                                                if(!lastTouch || (new Date()).getTime() > lastTouch + 1000){
42                                                        listener.call(this, evt);
43                                                }
44                                        });
45                                return {
46                                        remove: function(){
47                                                handle1.remove();
48                                                handle2.remove();
49                                        }
50                                };
51                        };
52                }else{
53                        // Avoid creating listeners for touch events on performance sensitive older browsers like IE6
54                        return function(node, listener){
55                                return on(node, mouseType, listener);
56                        }
57                }
58        }
59
60        function marked(/*DOMNode*/ node){
61                // Test if a node or its ancestor has been marked with the dojoClick property to indicate special processing,
62                do{
63                        if(node.dojoClick !== undefined){ return node.dojoClick; }
64                }while(node = node.parentNode);
65        }
66       
67        function doClicks(e, moveType, endType){
68                // summary:
69                //              Setup touch listeners to generate synthetic clicks immediately (rather than waiting for the browser
70                //              to generate clicks after the double-tap delay) and consistently (regardless of whether event.preventDefault()
71                //              was called in an event listener. Synthetic clicks are generated only if a node or one of its ancestors has
72                //      its dojoClick property set to truthy. If a node receives synthetic clicks because one of its ancestors has its
73                //      dojoClick property set to truthy, you can disable synthetic clicks on this node by setting its own dojoClick property
74                //      to falsy.
75               
76                clickTracker  = !e.target.disabled && marked(e.target); // click threshold = true, number or x/y object
77                if(clickTracker){
78                        clickTarget = e.target;
79                        clickX = e.touches ? e.touches[0].pageX : e.clientX;
80                        clickY = e.touches ? e.touches[0].pageY : e.clientY;
81                        clickDx = (typeof clickTracker == "object" ? clickTracker.x : (typeof clickTracker == "number" ? clickTracker : 0)) || 4;
82                        clickDy = (typeof clickTracker == "object" ? clickTracker.y : (typeof clickTracker == "number" ? clickTracker : 0)) || 4;
83
84                        // add move/end handlers only the first time a node with dojoClick is seen,
85                        // so we don't add too much overhead when dojoClick is never set.
86                        if(!clicksInited){
87                                clicksInited = true;
88
89                                win.doc.addEventListener(moveType, function(e){
90                                        clickTracker = clickTracker &&
91                                                e.target == clickTarget &&
92                                                Math.abs((e.touches ? e.touches[0].pageX : e.clientX) - clickX) <= clickDx &&
93                                                Math.abs((e.touches ? e.touches[0].pageY : e.clientY) - clickY) <= clickDy;
94                                }, true);
95
96                                win.doc.addEventListener(endType, function(e){
97                                        if(clickTracker){
98                                                clickTime = (new Date()).getTime();
99                                                var target = e.target;
100                                                if(target.tagName === "LABEL"){
101                                                        // when clicking on a label, forward click to its associated input if any
102                                                        target = dom.byId(target.getAttribute("for")) || target;
103                                                }
104                                                setTimeout(function(){
105                                                        on.emit(target, "click", {
106                                                                bubbles : true,
107                                                                cancelable : true,
108                                                                _dojo_click : true
109                                                        });
110                                                });
111                                        }
112                                }, true);
113
114                                function stopNativeEvents(type){
115                                        win.doc.addEventListener(type, function(e){
116                                                // Stop native events when we emitted our own click event.  Note that the native click may occur
117                                                // on a different node than the synthetic click event was generated on.  For example,
118                                                // click on a menu item, causing the menu to disappear, and then (~300ms later) the browser
119                                                // sends a click event to the node that was *underneath* the menu.  So stop all native events
120                                                // sent shortly after ours, similar to what is done in dualEvent.
121                                                // The INPUT.dijitOffScreen test is for offscreen inputs used in dijit/form/Button, on which
122                                                // we call click() explicitly, we don't want to stop this event.
123                                                if(!e._dojo_click &&
124                                                                (new Date()).getTime() <= clickTime + 1000 &&
125                                                                !(e.target.tagName == "INPUT" && domClass.contains(e.target, "dijitOffScreen"))){
126                                                        e.stopPropagation();
127                                                        e.stopImmediatePropagation && e.stopImmediatePropagation();
128                                                        if(type == "click" && (e.target.tagName != "INPUT" || e.target.type == "radio" || e.target.type == "checkbox")
129                                                                && e.target.tagName != "TEXTAREA" && e.target.tagName != "AUDIO" && e.target.tagName != "VIDEO"){
130                                                                 // preventDefault() breaks textual <input>s on android, keyboard doesn't popup,
131                                                                 // but it is still needed for checkboxes and radio buttons, otherwise in some cases
132                                                                 // the checked state becomes inconsistent with the widget's state
133                                                                e.preventDefault();
134                                                        }
135                                                }
136                                        }, true);
137                                }
138
139                                stopNativeEvents("click");
140
141                                // We also stop mousedown/up since these would be sent well after with our "fast" click (300ms),
142                                // which can confuse some dijit widgets.
143                                stopNativeEvents("mousedown");
144                                stopNativeEvents("mouseup");
145                        }
146                }
147        }
148
149        var hoveredNode;
150
151        if(hasTouch){
152                if(msPointer){
153                         // MSPointer (IE10+) already has support for over and out, so we just need to init click support
154                        domReady(function(){
155                                win.doc.addEventListener(pointer.down, function(evt){
156                                        doClicks(evt, pointer.move, pointer.up);
157                                }, true);
158                        });             
159                }else{
160                        domReady(function(){
161                                // Keep track of currently hovered node
162                                hoveredNode = win.body();       // currently hovered node
163
164                                win.doc.addEventListener("touchstart", function(evt){
165                                        lastTouch = (new Date()).getTime();
166
167                                        // Precede touchstart event with touch.over event.  DnD depends on this.
168                                        // Use addEventListener(cb, true) to run cb before any touchstart handlers on node run,
169                                        // and to ensure this code runs even if the listener on the node does event.stop().
170                                        var oldNode = hoveredNode;
171                                        hoveredNode = evt.target;
172                                        on.emit(oldNode, "dojotouchout", {
173                                                relatedTarget: hoveredNode,
174                                                bubbles: true
175                                        });
176                                        on.emit(hoveredNode, "dojotouchover", {
177                                                relatedTarget: oldNode,
178                                                bubbles: true
179                                        });
180                               
181                                        doClicks(evt, "touchmove", "touchend"); // init click generation
182                                }, true);
183
184                                function copyEventProps(evt){
185                                        // Make copy of event object and also set bubbles:true.  Used when calling on.emit().
186                                        var props = lang.delegate(evt, {
187                                                bubbles: true
188                                        });
189
190                                        if(has("ios") >= 6){
191                                                // On iOS6 "touches" became a non-enumerable property, which
192                                                // is not hit by for...in.  Ditto for the other properties below.
193                                                props.touches = evt.touches;
194                                                props.altKey = evt.altKey;
195                                                props.changedTouches = evt.changedTouches;
196                                                props.ctrlKey = evt.ctrlKey;
197                                                props.metaKey = evt.metaKey;
198                                                props.shiftKey = evt.shiftKey;
199                                                props.targetTouches = evt.targetTouches;
200                                        }
201
202                                        return props;
203                                }
204                               
205                                on(win.doc, "touchmove", function(evt){
206                                        lastTouch = (new Date()).getTime();
207
208                                        var newNode = win.doc.elementFromPoint(
209                                                evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
210                                                evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
211                                        );
212
213                                        if(newNode){
214                                                // Fire synthetic touchover and touchout events on nodes since the browser won't do it natively.
215                                                if(hoveredNode !== newNode){
216                                                        // touch out on the old node
217                                                        on.emit(hoveredNode, "dojotouchout", {
218                                                                relatedTarget: newNode,
219                                                                bubbles: true
220                                                        });
221
222                                                        // touchover on the new node
223                                                        on.emit(newNode, "dojotouchover", {
224                                                                relatedTarget: hoveredNode,
225                                                                bubbles: true
226                                                        });
227
228                                                        hoveredNode = newNode;
229                                                }
230
231                                                // Unlike a listener on "touchmove", on(node, "dojotouchmove", listener) fires when the finger
232                                                // drags over the specified node, regardless of which node the touch started on.
233                                                if(!on.emit(newNode, "dojotouchmove", copyEventProps(evt))){
234                                                        // emit returns false when synthetic event "dojotouchmove" is cancelled, so we prevent the
235                                                        // default behavior of the underlying native event "touchmove".
236                                                        evt.preventDefault();
237                                                }
238                                        }
239                                });
240
241                                // Fire a dojotouchend event on the node where the finger was before it was removed from the screen.
242                                // This is different than the native touchend, which fires on the node where the drag started.
243                                on(win.doc, "touchend", function(evt){
244                                        lastTouch = (new Date()).getTime();
245                                        var node = win.doc.elementFromPoint(
246                                                evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
247                                                evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
248                                        ) || win.body(); // if out of the screen
249
250                                        on.emit(node, "dojotouchend", copyEventProps(evt));
251                                });
252                        });
253                }
254        }
255
256        //device neutral events - touch.press|move|release|cancel/over/out
257        var touch = {
258                press: dualEvent("mousedown", "touchstart", pointer.down),
259                move: dualEvent("mousemove", "dojotouchmove", pointer.move),
260                release: dualEvent("mouseup", "dojotouchend", pointer.up),
261                cancel: dualEvent(mouse.leave, "touchcancel", hasTouch ? pointer.cancel : null),
262                over: dualEvent("mouseover", "dojotouchover", pointer.over),
263                out: dualEvent("mouseout", "dojotouchout", pointer.out),
264                enter: mouse._eventHandler(dualEvent("mouseover","dojotouchover", pointer.over)),
265                leave: mouse._eventHandler(dualEvent("mouseout", "dojotouchout", pointer.out))
266        };
267
268        /*=====
269        touch = {
270                // summary:
271                //              This module provides unified touch event handlers by exporting
272                //              press, move, release and cancel which can also run well on desktop.
273                //              Based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
274                //      Also, if the dojoClick property is set to truthy on a DOM node, dojo/touch generates
275                //      click events immediately for this node and its descendants (except for descendants that
276                //      have a dojoClick property set to falsy), to avoid the delay before native browser click events,
277                //      and regardless of whether evt.preventDefault() was called in a touch.press event listener.
278                //
279                // example:
280                //              Used with dojo/on
281                //              |       define(["dojo/on", "dojo/touch"], function(on, touch){
282                //              |               on(node, touch.press, function(e){});
283                //              |               on(node, touch.move, function(e){});
284                //              |               on(node, touch.release, function(e){});
285                //              |               on(node, touch.cancel, function(e){});
286                // example:
287                //              Used with touch.* directly
288                //              |       touch.press(node, function(e){});
289                //              |       touch.move(node, function(e){});
290                //              |       touch.release(node, function(e){});
291                //              |       touch.cancel(node, function(e){});
292                // example:
293                //              Have dojo/touch generate clicks without delay, with a default move threshold of 4 pixels
294                //              |       node.dojoClick = true;
295                // example:
296                //              Have dojo/touch generate clicks without delay, with a move threshold of 10 pixels horizontally and vertically
297                //              |       node.dojoClick = 10;
298                // example:
299                //              Have dojo/touch generate clicks without delay, with a move threshold of 50 pixels horizontally and 10 pixels vertically
300                //              |       node.dojoClick = {x:50, y:5};
301                // example:
302                //    Disable clicks without delay generated by dojo/touch on a node that has an ancestor with property dojoClick set to truthy
303                //    |  node.dojoClick = false;               
304
305                press: function(node, listener){
306                        // summary:
307                        //              Register a listener to 'touchstart'|'mousedown' for the given node
308                        // node: Dom
309                        //              Target node to listen to
310                        // listener: Function
311                        //              Callback function
312                        // returns:
313                        //              A handle which will be used to remove the listener by handle.remove()
314                },
315                move: function(node, listener){
316                        // summary:
317                        //              Register a listener that fires when the mouse cursor or a finger is dragged over the given node.
318                        // node: Dom
319                        //              Target node to listen to
320                        // listener: Function
321                        //              Callback function
322                        // returns:
323                        //              A handle which will be used to remove the listener by handle.remove()
324                },
325                release: function(node, listener){
326                        // summary:
327                        //              Register a listener to releasing the mouse button while the cursor is over the given node
328                        //              (i.e. "mouseup") or for removing the finger from the screen while touching the given node.
329                        // node: Dom
330                        //              Target node to listen to
331                        // listener: Function
332                        //              Callback function
333                        // returns:
334                        //              A handle which will be used to remove the listener by handle.remove()
335                },
336                cancel: function(node, listener){
337                        // summary:
338                        //              Register a listener to 'touchcancel'|'mouseleave' for the given node
339                        // node: Dom
340                        //              Target node to listen to
341                        // listener: Function
342                        //              Callback function
343                        // returns:
344                        //              A handle which will be used to remove the listener by handle.remove()
345                },
346                over: function(node, listener){
347                        // summary:
348                        //              Register a listener to 'mouseover' or touch equivalent for the given node
349                        // node: Dom
350                        //              Target node to listen to
351                        // listener: Function
352                        //              Callback function
353                        // returns:
354                        //              A handle which will be used to remove the listener by handle.remove()
355                },
356                out: function(node, listener){
357                        // summary:
358                        //              Register a listener to 'mouseout' or touch equivalent for the given node
359                        // node: Dom
360                        //              Target node to listen to
361                        // listener: Function
362                        //              Callback function
363                        // returns:
364                        //              A handle which will be used to remove the listener by handle.remove()
365                },
366                enter: function(node, listener){
367                        // summary:
368                        //              Register a listener to mouse.enter or touch equivalent for the given node
369                        // node: Dom
370                        //              Target node to listen to
371                        // listener: Function
372                        //              Callback function
373                        // returns:
374                        //              A handle which will be used to remove the listener by handle.remove()
375                },
376                leave: function(node, listener){
377                        // summary:
378                        //              Register a listener to mouse.leave or touch equivalent for the given node
379                        // node: Dom
380                        //              Target node to listen to
381                        // listener: Function
382                        //              Callback function
383                        // returns:
384                        //              A handle which will be used to remove the listener by handle.remove()
385                }
386        };
387        =====*/
388
389        has("extend-dojo") && (dojo.touch = touch);
390
391        return touch;
392});
Note: See TracBrowser for help on using the repository browser.