source: Dev/branches/rest-dojo-ui/client/dojox/gesture/Base.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 11.7 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.gestre.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                        e.preventDefault();
268                        e._locking[this.defaultEvent] = true;
269                        this[phase](element.data, e);
270                },
271                press: function(data, e){
272                        // summary:
273                        //              Process the 'press' phase of a gesture
274                },
275                move: function(data, e){
276                        // summary:
277                        //              Process the 'move' phase of a gesture
278                },
279                release: function(data, e){
280                        // summary:
281                        //              Process the 'release' phase of a gesture
282                },
283                cancel: function(data, e){
284                        // summary:
285                        //              Process the 'cancel' phase of a gesture
286                },
287                fire: function(node, event){
288                        // summary:
289                        //              Fire a gesture event and invoke registered listeners
290                        //              a simulated GestureEvent will also be sent along
291                        // node: DomNode
292                        //              Target node to fire the gesture
293                        // event: Object
294                        //              An object containing specific gesture info e.g {type: 'tap.hold'|'swipe.left'), ...}
295                        //              all these properties will be put into a simulated GestureEvent when fired.
296                        //              Note - Default properties in a native Event won't be overwritten, see on.emit() for more details.
297                        if(!node || !event){
298                                return;
299                        }
300                        event.bubbles = true;
301                        event.cancelable = true;
302                        on.emit(node, event.type, event);
303                },
304                _remove: function(/*Dom*/node, /*String*/type){
305                        // summary:
306                        //              Check and remove underneath handlers if node
307                        //              is not being listened for 'this' gesture anymore,
308                        //              this happens when user removed all previous on() handlers.
309                        var element = this._getGestureElement(node);
310                        if(!element || !element.handles){ return; }
311                       
312                        element.handles[type]--;
313
314                        var handles = element.handles;
315                        if(!array.some(this._events, function(evt){
316                                return handles[evt] > 0;
317                        })){
318                                // clean up if node is not being listened anymore
319                                this._cleanHandles(handles);
320                                var i = array.indexOf(this._elements, element);
321                                if(i >= 0){
322                                        this._elements.splice(i, 1);
323                                }
324                        }
325                },
326                _cleanHandles: function(/*Object*/handles){
327                        // summary:
328                        //              Clean up on handles
329                        for(var x in handles){
330                                //remove handles for "press"|"move"|"release"|"cancel"
331                                if(handles[x].remove){
332                                        handles[x].remove();
333                                }
334                                delete handles[x];
335                        }
336                },
337                lock: function(/*Dom*/node){
338                        // summary:
339                        //              Lock all descendants of the node.
340                        // tags:
341                        //              protected
342                        this._lock = node;
343                },
344                unLock: function(){
345                        // summary:
346                        //              Release the lock
347                        // tags:
348                        //              protected
349                        this._lock = null;
350                },
351                isLocked: function(node){
352                        // summary:
353                        //              Check if the node is locked, isLocked(node) means
354                        //              whether it's a descendant of the currently locked node.
355                        // tags:
356                        //              protected
357                        if(!this._lock || !node){
358                                return false;
359                        }
360                        return this._lock !== node && dom.isDescendant(node, this._lock);
361                },
362                destroy: function(){
363                        // summary:
364                        //              Release all handlers and resources
365                        array.forEach(this._elements, function(element){
366                                this._cleanHandles(element.handles);
367                        }, this);
368                        this._elements = null;
369                }
370        });
371});
Note: See TracBrowser for help on using the repository browser.