source: Dev/trunk/src/client/dojox/gesture/Base.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: 11.9 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/declare",
4        "dojo/_base/array",
5        "dojo/_base/lang",
6        "dojo/dom",
7        "dojo/on",
8        "dojo/touch",
9        "dojo/has",
10        "../main"
11], function(kernel, declare, array, lang, dom, on, touch, has, dojox){
12        // module:
13        //              dojox/gesture/Base
14        // summary:
15        //              This module provides an abstract parental class for various gesture implementations.
16       
17/*=====
18        dojox.gesture.Base = {
19                // summary:
20                //              An abstract parental class for various gesture implementations.
21                //
22                //              It's mainly responsible for:
23                //
24                //              1. Binding on() listening handlers for supported gesture events.
25                //
26                //              2. Monitoring underneath events and process different phases - 'press'|'move'|'release'|'cancel'.
27                //
28                //              3. Firing and bubbling gesture events with on() API.
29                //
30                //              A gesture implementation only needs to extend this class and overwrite appropriate phase handlers:
31                //
32                //              - press()|move()|release()|cancel for recognizing and firing gestures
33                //
34                // example:
35                //              1. A typical gesture implementation.
36                //
37                //              Suppose we have dojox/gesture/a which provides 3 gesture events:"a", "a.x", "a.y" to be used as:
38                //              |       dojo.connect(node, dojox.gesture.a, function(e){});
39                //              |       dojo.connect(node, dojox.gesture.a.x, function(e){});
40                //              |       dojo.connect(node, dojox.gesture.a.y, function(e){});
41                //
42                //              The definition of the gesture "a" may look like:
43                //              |       define([..., "./Base"], function(..., Base){
44                //              |               var clz = declare(Base, {
45                //              |                       defaultEvent: "a",
46                //              |
47                //              |                       subEvents: ["x", "y"],
48                //              |                       
49                //              |                       press: function(data, e){
50                //              |                               this.fire(node, {type: "a.x", ...});
51                //              |                       },
52                //              |                       move: function(data, e){
53                //              |                               this.fire(node, {type: "a.y", ...});
54                //              |                       },
55                //              |                       release: function(data, e){
56                //              |                               this.fire(node, {type: "a", ...});
57                //              |                       },
58                //              |                       cancel: function(data, e){
59                //              |                               // clean up
60                //              |                       }
61                //              |               });
62                //              |
63                //              |               // in order to have a default instance for handy use
64                //              |               dojox.gesture.a = new clz();
65                //              |
66                //              |               // so that we can create new instances like
67                //              |               // var mine = new dojox.gesture.a.A({...})
68                //              |               dojox.gesture.a.A = clz;
69                //              |
70                //              |               return dojox.gesture.a;
71                //              |       });
72                //
73                //              2. A gesture can be used in the following ways(taking dojox.gesture.tap for example):
74                //
75                //              A. Used with dojo.connect()
76                //              |       dojo.connect(node, dojox.gesture.tap, function(e){});
77                //              |       dojo.connect(node, dojox.gesture.tap.hold, function(e){});
78                //              |       dojo.connect(node, dojox.gesture.tap.doubletap, function(e){});         
79                //
80                //              B. Used with dojo.on
81                //              |       define(["dojo/on", "dojox/gesture/tap"], function(on, tap){
82                //              |               on(node, tap, function(e){});
83                //              |               on(node, tap.hold, function(e){});
84                //              |               on(node, tap.doubletap, function(e){});
85                //
86                //              C. Used with dojox.gesture.tap directly
87                //              |       dojox.gesture.tap(node, function(e){});
88                //              |       dojox.gesture.tap.hold(node, function(e){});
89                //              |       dojox.gesture.tap.doubletap(node, function(e){});
90                //
91                //              Though there is always a default gesture instance after being required, e.g
92                //              |       require(["dojox/gesture/tap"], function(){...});
93                //
94                //              It's possible to create a new one with different parameter setting:
95                //              |       var myTap = new dojox.gesture.tap.Tap({holdThreshold: 300});
96                //              |       dojo.connect(node, myTap, function(e){});
97                //              |       dojo.connect(node, myTap.hold, function(e){});
98                //              |       dojo.connect(node, myTap.doubletap, function(e){});
99                //             
100                //              Please refer to dojox/gesture/ for more gesture usages
101        };
102=====*/
103        kernel.experimental("dojox.gesture.Base");
104       
105        lang.getObject("gesture", true, dojox);
106
107        // Declare an internal anonymous class which will only be exported by module return value
108        return declare(/*===== "dojox.gesture.Base", =====*/null, {
109
110                // defaultEvent: [readonly] String
111                //              Default event e.g. 'tap' is a default event of dojox.gesture.tap
112                defaultEvent: " ",
113
114                // subEvents: [readonly] Array
115                //              A list of sub events e.g ['hold', 'doubletap'],
116                //              used by being combined with defaultEvent like 'tap.hold', 'tap.doubletap' etc.
117                subEvents: [],
118
119                // touchOnly: boolean
120                //              Whether the gesture is touch-device only
121                touchOnly : false,
122
123                // _elements: Array
124                //              List of elements that wraps target node and gesture data
125                _elements: null,
126
127                /*=====
128                // _lock: Dom
129                //              The dom node whose descendants are all locked for processing
130                _lock: null,
131               
132                // _events: [readonly] Array
133                //              The complete list of supported gesture events with full name space
134                //              e.g ['tap', 'tap.hold', 'tap.doubletap']
135                _events: null,
136                =====*/
137
138                constructor: function(args){
139                        lang.mixin(this, args);
140                        this.init();
141                },
142                init: function(){
143                        // summary:
144                        //              Initialization works
145                        this._elements = [];
146
147                        if(!has("touch") && this.touchOnly){
148                                console.warn("Gestures:[", this.defaultEvent, "] is only supported on touch devices!");
149                                return;
150                        }
151
152                        // bind on() handlers for various events
153                        var evt = this.defaultEvent;
154                        this.call = this._handle(evt);
155
156                        this._events = [evt];
157                        array.forEach(this.subEvents, function(subEvt){
158                                this[subEvt] = this._handle(evt + '.' + subEvt);
159                                this._events.push(evt + '.' + subEvt);
160                        }, this);
161                },
162                _handle: function(/*String*/eventType){
163                        // summary:
164                        //              Bind listen handler for the given gesture event(e.g. 'tap', 'tap.hold' etc.)
165                        //              the returned handle will be used internally by dojo/on
166                        var self = this;
167                        //called by dojo/on
168                        return function(node, listener){
169                                // normalize, arguments might be (null, node, listener)
170                                var a = arguments;
171                                if(a.length > 2){
172                                        node = a[1];
173                                        listener = a[2];
174                                }
175                                var isNode = node && (node.nodeType || node.attachEvent || node.addEventListener);
176                                if(!isNode){
177                                        return on(node, eventType, listener);
178                                }else{
179                                        var onHandle = self._add(node, eventType, listener);
180                                        // FIXME - users are supposed to explicitly call either
181                                        // disconnect(signal) or signal.remove() to release resources
182                                        var signal = {
183                                                remove: function(){
184                                                        onHandle.remove();
185                                                        self._remove(node, eventType);
186                                                }
187                                        };
188                                        return signal;
189                                }
190                        }; // dojo/on handle
191                },
192                _add: function(/*Dom*/node, /*String*/type, /*function*/listener){
193                        // summary:
194                        //              Bind dojo/on handlers for both gesture event(e.g 'tab.hold')
195                        //              and underneath 'press'|'move'|'release' events
196                        var element = this._getGestureElement(node);
197                        if(!element){
198                                // the first time listening to the node
199                                element = {
200                                        target: node,
201                                        data: {},
202                                        handles: {}
203                                };
204
205                                var _press = lang.hitch(this, "_process", element, "press");
206                                var _move = lang.hitch(this, "_process", element, "move");
207                                var _release = lang.hitch(this, "_process", element, "release");
208                                var _cancel = lang.hitch(this, "_process", element, "cancel");
209
210                                var handles = element.handles;
211                                if(this.touchOnly){
212                                        handles.press = on(node, 'touchstart', _press);
213                                        handles.move = on(node, 'touchmove', _move);
214                                        handles.release = on(node, 'touchend', _release);
215                                        handles.cancel = on(node, 'touchcancel', _cancel);
216                                }else{
217                                        handles.press = touch.press(node, _press);
218                                        handles.move = touch.move(node, _move);
219                                        handles.release = touch.release(node, _release);
220                                        handles.cancel = touch.cancel(node, _cancel);
221                                }
222                                this._elements.push(element);
223                        }
224                        // track num of listeners for the gesture event - type
225                        // so that we can release element if no more gestures being monitored
226                        element.handles[type] = !element.handles[type] ? 1 : ++element.handles[type];
227
228                        return on(node, type, listener); //handle
229                },
230                _getGestureElement: function(/*Dom*/node){
231                        // summary:
232                        //              Obtain a gesture element for the give node
233                        var i = 0, element;
234                        for(; i < this._elements.length; i++){
235                                element = this._elements[i];
236                                if(element.target === node){
237                                        return element;
238                                }
239                        }
240                },
241                _process: function(element, phase, e){
242                        // summary:
243                        //              Process and dispatch to appropriate phase handlers.
244                        //              Also provides the machinery for managing gesture bubbling.
245                        // description:
246                        //              1. e._locking is used to make sure only the most inner node
247                        //              will be processed for the same gesture, suppose we have:
248                        //      |       on(inner, dojox.gesture.tap, func1);
249                        //      |       on(outer, dojox.gesture.tap, func2);
250                        //              only the inner node will be processed by tap gesture, once matched,
251                        //              the 'tap' event will be bubbled up from inner to outer, dojo.StopEvent(e)
252                        //              can be used at any level to stop the 'tap' event.
253                        //
254                        //              2. Once a node starts being processed, all it's descendant nodes will be locked.
255                        //              The same gesture won't be processed on its descendant nodes until the lock is released.
256                        // element: Object
257                        //              Gesture element
258                        // phase: String
259                        //              Phase of a gesture to be processed, might be 'press'|'move'|'release'|'cancel'
260                        // e: Event
261                        //              Native event
262                        e._locking = e._locking || {};
263                        if(e._locking[this.defaultEvent] || this.isLocked(e.currentTarget)){
264                                return;
265                        }
266                        // invoking gesture.press()|move()|release()|cancel()
267                        // #16900: same condition as in dojo/touch, to avoid breaking the editing of input fields.
268                        if((e.target.tagName != "INPUT" || e.target.type == "radio" || e.target.type == "checkbox")
269                                && e.target.tagName != "TEXTAREA"){
270                                e.preventDefault();
271                        }
272                        e._locking[this.defaultEvent] = true;
273                        this[phase](element.data, e);
274                },
275                press: function(data, e){
276                        // summary:
277                        //              Process the 'press' phase of a gesture
278                },
279                move: function(data, e){
280                        // summary:
281                        //              Process the 'move' phase of a gesture
282                },
283                release: function(data, e){
284                        // summary:
285                        //              Process the 'release' phase of a gesture
286                },
287                cancel: function(data, e){
288                        // summary:
289                        //              Process the 'cancel' phase of a gesture
290                },
291                fire: function(node, event){
292                        // summary:
293                        //              Fire a gesture event and invoke registered listeners
294                        //              a simulated GestureEvent will also be sent along
295                        // node: DomNode
296                        //              Target node to fire the gesture
297                        // event: Object
298                        //              An object containing specific gesture info e.g {type: 'tap.hold'|'swipe.left'), ...}
299                        //              all these properties will be put into a simulated GestureEvent when fired.
300                        //              Note - Default properties in a native Event won't be overwritten, see on.emit() for more details.
301                        if(!node || !event){
302                                return;
303                        }
304                        event.bubbles = true;
305                        event.cancelable = true;
306                        on.emit(node, event.type, event);
307                },
308                _remove: function(/*Dom*/node, /*String*/type){
309                        // summary:
310                        //              Check and remove underneath handlers if node
311                        //              is not being listened for 'this' gesture anymore,
312                        //              this happens when user removed all previous on() handlers.
313                        var element = this._getGestureElement(node);
314                        if(!element || !element.handles){ return; }
315                       
316                        element.handles[type]--;
317
318                        var handles = element.handles;
319                        if(!array.some(this._events, function(evt){
320                                return handles[evt] > 0;
321                        })){
322                                // clean up if node is not being listened anymore
323                                this._cleanHandles(handles);
324                                var i = array.indexOf(this._elements, element);
325                                if(i >= 0){
326                                        this._elements.splice(i, 1);
327                                }
328                        }
329                },
330                _cleanHandles: function(/*Object*/handles){
331                        // summary:
332                        //              Clean up on handles
333                        for(var x in handles){
334                                //remove handles for "press"|"move"|"release"|"cancel"
335                                if(handles[x].remove){
336                                        handles[x].remove();
337                                }
338                                delete handles[x];
339                        }
340                },
341                lock: function(/*Dom*/node){
342                        // summary:
343                        //              Lock all descendants of the node.
344                        // tags:
345                        //              protected
346                        this._lock = node;
347                },
348                unLock: function(){
349                        // summary:
350                        //              Release the lock
351                        // tags:
352                        //              protected
353                        this._lock = null;
354                },
355                isLocked: function(node){
356                        // summary:
357                        //              Check if the node is locked, isLocked(node) means
358                        //              whether it's a descendant of the currently locked node.
359                        // tags:
360                        //              protected
361                        if(!this._lock || !node){
362                                return false;
363                        }
364                        return this._lock !== node && dom.isDescendant(node, this._lock);
365                },
366                destroy: function(){
367                        // summary:
368                        //              Release all handlers and resources
369                        array.forEach(this._elements, function(element){
370                                this._cleanHandles(element.handles);
371                        }, this);
372                        this._elements = null;
373                }
374        });
375});
Note: See TracBrowser for help on using the repository browser.