1 | define(["dojo/_base/array", "dojo/_base/lang", "dojo/_base/declare", "dojo/dom", "dojo/dom-geometry", "dojo/_base/window", "dojo/on", "dojo/_base/event", "dojo/keys"], |
---|
2 | |
---|
3 | function(arr, lang, declare, dom, domGeometry, win, on, event, keys){ |
---|
4 | |
---|
5 | return declare("dojox.calendar.Touch", null, { |
---|
6 | |
---|
7 | // summary: |
---|
8 | // This plugin is managing the touch interactions on item renderers displayed by a calendar view. |
---|
9 | |
---|
10 | // touchStartEditingTimer: Integer |
---|
11 | // The delay of one touch over the renderer before setting the item in editing mode. |
---|
12 | touchStartEditingTimer: 500, |
---|
13 | |
---|
14 | // touchEndEditingTimer: Integer |
---|
15 | // The delay after which the item is leaving the editing mode after the previous editing gesture, in touch context. |
---|
16 | touchEndEditingTimer: 10000, |
---|
17 | |
---|
18 | postMixInProperties: function(){ |
---|
19 | |
---|
20 | this.on("rendererCreated", lang.hitch(this, function(irEvent){ |
---|
21 | |
---|
22 | var renderer = irEvent.renderer.renderer; |
---|
23 | |
---|
24 | this.own(on(renderer.domNode, "touchstart", lang.hitch(this, function(e){ |
---|
25 | this._onRendererTouchStart(e, renderer); |
---|
26 | }))); |
---|
27 | |
---|
28 | })); |
---|
29 | }, |
---|
30 | |
---|
31 | _onRendererTouchStart: function(e, renderer){ |
---|
32 | // tags: |
---|
33 | // private |
---|
34 | var p = this._edProps; |
---|
35 | |
---|
36 | if(p && p.endEditingTimer){ |
---|
37 | clearTimeout(p.endEditingTimer); |
---|
38 | p.endEditingTimer = null; |
---|
39 | } |
---|
40 | |
---|
41 | var theItem = renderer.item.item; |
---|
42 | |
---|
43 | if(p && p.endEditingTimer){ |
---|
44 | clearTimeout(p.endEditingTimer); |
---|
45 | p.endEditingTimer = null; |
---|
46 | } |
---|
47 | |
---|
48 | if(p != null && p.item != theItem){ |
---|
49 | // another item is edited. |
---|
50 | // stop previous item |
---|
51 | if(p.startEditingTimer){ |
---|
52 | clearTimeout(p.startEditingTimer); |
---|
53 | } |
---|
54 | |
---|
55 | this._endItemEditing("touch", false); |
---|
56 | p = null; |
---|
57 | |
---|
58 | } |
---|
59 | |
---|
60 | // initialize editing properties |
---|
61 | if(!p){ |
---|
62 | |
---|
63 | // register event listeners to manage gestures. |
---|
64 | var handles = []; |
---|
65 | |
---|
66 | handles.push(on(win.doc, "touchend", lang.hitch(this, this._docEditingTouchEndHandler))); |
---|
67 | handles.push(on(this.itemContainer, "touchmove", lang.hitch(this, this._docEditingTouchMoveHandler))); |
---|
68 | |
---|
69 | this._setEditingProperties({ |
---|
70 | touchMoved: false, |
---|
71 | item: theItem, |
---|
72 | renderer: renderer, |
---|
73 | rendererKind: renderer.rendererKind, |
---|
74 | event: e, |
---|
75 | handles: handles, |
---|
76 | liveLayout: this.liveLayout |
---|
77 | }); |
---|
78 | |
---|
79 | p = this._edProps; |
---|
80 | } |
---|
81 | |
---|
82 | if(this._isEditing){ |
---|
83 | |
---|
84 | // get info on touches |
---|
85 | lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem)); |
---|
86 | |
---|
87 | // start an editing gesture. |
---|
88 | this._startTouchItemEditingGesture(e); |
---|
89 | |
---|
90 | } else { |
---|
91 | |
---|
92 | // initial touch that will trigger or not the editing |
---|
93 | |
---|
94 | if(e.touches.length > 1){ |
---|
95 | event.stop(e); |
---|
96 | return; |
---|
97 | } |
---|
98 | |
---|
99 | // set the selection state without dispatching (on touch end) after a short amount of time. |
---|
100 | // to allow a bit of time to scroll without selecting (graphically at least) |
---|
101 | this._touchSelectionTimer = setTimeout(lang.hitch(this, function(){ |
---|
102 | |
---|
103 | this._saveSelectedItems = this.get("selectedItems"); |
---|
104 | |
---|
105 | var changed = this.selectFromEvent(e, theItem._item, renderer, false); |
---|
106 | |
---|
107 | if(changed){ |
---|
108 | this._pendingSelectedItem = theItem; |
---|
109 | }else{ |
---|
110 | delete this._saveSelectedItems; |
---|
111 | } |
---|
112 | this._touchSelectionTimer = null; |
---|
113 | }), 200); |
---|
114 | |
---|
115 | p.start = {x: e.touches[0].screenX, y: e.touches[0].screenY}; |
---|
116 | |
---|
117 | if(this.isItemEditable(p.item, p.rendererKind)){ |
---|
118 | |
---|
119 | // editing gesture timer |
---|
120 | this._edProps.startEditingTimer = setTimeout(lang.hitch(this, function(){ |
---|
121 | |
---|
122 | // we are editing, so the item *must* be selected. |
---|
123 | if(this._touchSelectionTimer){ |
---|
124 | clearTimeout(this._touchSelectionTimer); |
---|
125 | delete this._touchSelectionTime; |
---|
126 | } |
---|
127 | if(this._pendingSelectedItem){ |
---|
128 | this.dispatchChange(this._saveSelectedItems == null ? null : this._saveSelectedItems[0], this._pendingSelectedItem, null, e); |
---|
129 | delete this._saveSelectedItems; |
---|
130 | delete this._pendingSelectedItem; |
---|
131 | }else{ |
---|
132 | this.selectFromEvent(e, theItem._item, renderer); |
---|
133 | } |
---|
134 | |
---|
135 | this._startItemEditing(p.item, "touch", e); |
---|
136 | |
---|
137 | p.moveTouchIndex = 0; |
---|
138 | |
---|
139 | // A move gesture is initiated even if we don't move |
---|
140 | this._startItemEditingGesture([this.getTime(e)], "move", "touch", e); |
---|
141 | |
---|
142 | }), this.touchStartEditingTimer); |
---|
143 | |
---|
144 | } |
---|
145 | } |
---|
146 | }, |
---|
147 | |
---|
148 | _docEditingTouchMoveHandler: function(e){ |
---|
149 | // tags: |
---|
150 | // private |
---|
151 | var p = this._edProps; |
---|
152 | |
---|
153 | // When the screen is touched, it can dispatch move events if the |
---|
154 | // user press the finger a little more... |
---|
155 | var touch = {x: e.touches[0].screenX, y: e.touches[0].screenY}; |
---|
156 | if(p.startEditingTimer && |
---|
157 | (Math.abs(touch.x - p.start.x) > 25 || |
---|
158 | Math.abs(touch.y - p.start.y) > 25)) { |
---|
159 | |
---|
160 | // scroll use case, do not edit |
---|
161 | clearTimeout(p.startEditingTimer); |
---|
162 | p.startEditingTimer = null; |
---|
163 | |
---|
164 | clearTimeout(this._touchSelectionTimer); |
---|
165 | this._touchSelectionTimer = null; |
---|
166 | |
---|
167 | if(this._pendingSelectedItem){ |
---|
168 | delete this._pendingSelectedItem; |
---|
169 | this.selectFromEvent(e, null, null, false); |
---|
170 | } |
---|
171 | } |
---|
172 | |
---|
173 | p.touchMoved = true; |
---|
174 | |
---|
175 | if(this._editingGesture){ |
---|
176 | |
---|
177 | event.stop(e); |
---|
178 | |
---|
179 | if(p.itemBeginDispatched){ |
---|
180 | |
---|
181 | var times = []; |
---|
182 | var d = p.editKind == "resizeEnd" ? p.editedItem.endTime : p.editedItem.startTime; |
---|
183 | |
---|
184 | switch(p.editKind){ |
---|
185 | case "move": |
---|
186 | var touchIndex = p.moveTouchIndex == null || p.moveTouchIndex < 0 ? 0 : p.moveTouchIndex; |
---|
187 | times[0] = this.getTime(e, -1, -1, touchIndex); |
---|
188 | break; |
---|
189 | case "resizeStart": |
---|
190 | times[0] = this.getTime(e, -1, -1, p.resizeStartTouchIndex); |
---|
191 | break; |
---|
192 | case "resizeEnd": |
---|
193 | times[0] = this.getTime(e, -1, -1, p.resizeEndTouchIndex); |
---|
194 | break; |
---|
195 | case "resizeBoth": |
---|
196 | times[0] = this.getTime(e, -1, -1, p.resizeStartTouchIndex); |
---|
197 | times[1] = this.getTime(e, -1, -1, p.resizeEndTouchIndex); |
---|
198 | break; |
---|
199 | } |
---|
200 | |
---|
201 | this._moveOrResizeItemGesture(times, "touch", e); |
---|
202 | |
---|
203 | if(p.editKind == "move"){ |
---|
204 | if(this.renderData.dateModule.compare(p.editedItem.startTime, d) == -1){ |
---|
205 | this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "start", this.autoScrollTouchMargin); |
---|
206 | }else{ |
---|
207 | this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "end", this.autoScrollTouchMargin); |
---|
208 | } |
---|
209 | }else if(e.editKind == "resizeStart" || e.editKind == "resizeBoth"){ |
---|
210 | this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "start", this.autoScrollTouchMargin); |
---|
211 | }else{ |
---|
212 | this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "end", this.autoScrollTouchMargin); |
---|
213 | } |
---|
214 | |
---|
215 | } |
---|
216 | } // else scroll, if any, is delegated to sub class |
---|
217 | |
---|
218 | }, |
---|
219 | |
---|
220 | // autoScrollTouchMargin: Integer |
---|
221 | // The minimum number of minutes of margin around the edited event. |
---|
222 | autoScrollTouchMargin: 10, |
---|
223 | |
---|
224 | _docEditingTouchEndHandler: function(e){ |
---|
225 | // tags: |
---|
226 | // private |
---|
227 | event.stop(e); |
---|
228 | |
---|
229 | var p = this._edProps; |
---|
230 | |
---|
231 | if(p.startEditingTimer){ |
---|
232 | clearTimeout(p.startEditingTimer); |
---|
233 | p.startEditingTimer = null; |
---|
234 | } |
---|
235 | |
---|
236 | if(this._isEditing){ |
---|
237 | |
---|
238 | lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem)); |
---|
239 | |
---|
240 | if(this._editingGesture){ |
---|
241 | |
---|
242 | if(p.touchesLen == 0){ |
---|
243 | |
---|
244 | // all touches were removed => end of editing gesture |
---|
245 | this._endItemEditingGesture("touch", e); |
---|
246 | |
---|
247 | if(this.touchEndEditingTimer > 0){ |
---|
248 | |
---|
249 | // Timer that trigger the end of the item editing mode. |
---|
250 | p.endEditingTimer = setTimeout(lang.hitch(this, function(){ |
---|
251 | |
---|
252 | this._endItemEditing("touch", false); |
---|
253 | |
---|
254 | }), this.touchEndEditingTimer); |
---|
255 | } // else validation must be explicit |
---|
256 | |
---|
257 | }else{ |
---|
258 | |
---|
259 | if(this._editingGesture){ |
---|
260 | this._endItemEditingGesture("touch", e); |
---|
261 | } |
---|
262 | // there touches of interest on item, process them. |
---|
263 | this._startTouchItemEditingGesture(e); |
---|
264 | } |
---|
265 | } |
---|
266 | |
---|
267 | }else if(!p.touchMoved){ |
---|
268 | |
---|
269 | event.stop(e); |
---|
270 | |
---|
271 | arr.forEach(p.handles, function(handle){ |
---|
272 | handle.remove(); |
---|
273 | }); |
---|
274 | |
---|
275 | if(this._touchSelectionTimer){ |
---|
276 | // selection timer was not reached to a proper selection. |
---|
277 | clearTimeout(this._touchSelectionTimer); |
---|
278 | this.selectFromEvent(e, p.item._item, p.renderer, true); |
---|
279 | |
---|
280 | }else if(this._pendingSelectedItem){ |
---|
281 | // selection timer was reached, dispatch change event |
---|
282 | this.dispatchChange(this._saveSelectedItems.length == 0 ? null : this._saveSelectedItems[0], |
---|
283 | this._pendingSelectedItem, null, e); // todo renderer ? |
---|
284 | delete this._saveSelectedItems; |
---|
285 | delete this._pendingSelectedItem; |
---|
286 | } |
---|
287 | |
---|
288 | if(this._pendingDoubleTap && this._pendingDoubleTap.item == p.item){ |
---|
289 | this._onItemDoubleClick({ |
---|
290 | triggerEvent: e, |
---|
291 | renderer: p.renderer, |
---|
292 | item: p.item._item |
---|
293 | }); |
---|
294 | |
---|
295 | clearTimeout(this._pendingDoubleTap.timer); |
---|
296 | |
---|
297 | delete this._pendingDoubleTap; |
---|
298 | |
---|
299 | }else{ |
---|
300 | |
---|
301 | this._pendingDoubleTap = { |
---|
302 | item: p.item, |
---|
303 | timer: setTimeout(lang.hitch(this, function(){ |
---|
304 | delete this._pendingDoubleTap; |
---|
305 | }), this.doubleTapDelay) |
---|
306 | }; |
---|
307 | |
---|
308 | this._onItemClick({ |
---|
309 | triggerEvent: e, |
---|
310 | renderer: p.renderer, |
---|
311 | item: p.item._item |
---|
312 | }); |
---|
313 | } |
---|
314 | |
---|
315 | this._edProps = null; |
---|
316 | |
---|
317 | }else{ |
---|
318 | // scroll view has finished. |
---|
319 | |
---|
320 | if(this._saveSelectedItems){ |
---|
321 | |
---|
322 | // selection without dipatching was done, but the view scrolled, |
---|
323 | // so revert last selection |
---|
324 | this.set("selectedItems", this._saveSelectedItems); |
---|
325 | delete this._saveSelectedItems; |
---|
326 | delete this._pendingSelectedItem; |
---|
327 | } |
---|
328 | |
---|
329 | arr.forEach(p.handles, function(handle){ |
---|
330 | handle.remove(); |
---|
331 | }); |
---|
332 | |
---|
333 | this._edProps = null; |
---|
334 | } |
---|
335 | }, |
---|
336 | |
---|
337 | _startTouchItemEditingGesture: function(e){ |
---|
338 | // summary: |
---|
339 | // Determines if a editing gesture is starting according to touches. |
---|
340 | // tags: |
---|
341 | // private |
---|
342 | |
---|
343 | var p = this._edProps; |
---|
344 | |
---|
345 | var fromResizeStart = p.resizeStartTouchIndex != -1; |
---|
346 | var fromResizeEnd = p.resizeEndTouchIndex != -1; |
---|
347 | |
---|
348 | if(fromResizeStart && fromResizeEnd || // initial gesture using two touches |
---|
349 | this._editingGesture && p.touchesLen == 2 && |
---|
350 | (fromResizeEnd && p.editKind == "resizeStart" || |
---|
351 | fromResizeStart && p.editKind =="resizeEnd")){ // gesture one after the other touch |
---|
352 | |
---|
353 | if(this._editingGesture && p.editKind != "resizeBoth"){ // stop ongoing gesture |
---|
354 | this._endItemEditingGesture("touch", e); |
---|
355 | } |
---|
356 | |
---|
357 | p.editKind = "resizeBoth"; |
---|
358 | |
---|
359 | this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex), |
---|
360 | this.getTime(e, -1, -1, p.resizeEndTouchIndex)], |
---|
361 | p.editKind, "touch", e); |
---|
362 | |
---|
363 | }else if(fromResizeStart && p.touchesLen == 1 && !this._editingGesture){ |
---|
364 | |
---|
365 | this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex)], |
---|
366 | "resizeStart", "touch", e); |
---|
367 | |
---|
368 | }else if(fromResizeEnd && p.touchesLen == 1 && !this._editingGesture){ |
---|
369 | |
---|
370 | this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeEndTouchIndex)], |
---|
371 | "resizeEnd", "touch", e); |
---|
372 | |
---|
373 | } else { |
---|
374 | // A move gesture is initiated even if we don't move |
---|
375 | this._startItemEditingGesture([this.getTime(e)], "move", "touch", e); |
---|
376 | } |
---|
377 | }, |
---|
378 | |
---|
379 | _getTouchesOnRenderers: function(e, item){ |
---|
380 | // summary: |
---|
381 | // Returns the touch indices that are on a editing handles or body of the renderers |
---|
382 | // tags: |
---|
383 | // private |
---|
384 | // item: Object |
---|
385 | // The render item. |
---|
386 | // e: Event |
---|
387 | // The touch event. |
---|
388 | // tags: |
---|
389 | // private |
---|
390 | |
---|
391 | var irs = this._getStartEndRenderers(item); |
---|
392 | |
---|
393 | var resizeStartTouchIndex = -1; |
---|
394 | var resizeEndTouchIndex = -1; |
---|
395 | var moveTouchIndex = -1; |
---|
396 | var hasResizeStart = irs[0] != null && irs[0].resizeStartHandle != null; |
---|
397 | var hasResizeEnd = irs[1] != null && irs[1].resizeEndHandle != null; |
---|
398 | var len = 0; |
---|
399 | var touched = false; |
---|
400 | var list = this.itemToRenderer[item.id]; |
---|
401 | |
---|
402 | for(var i=0; i<e.touches.length; i++){ |
---|
403 | |
---|
404 | if(resizeStartTouchIndex == -1 && hasResizeStart){ |
---|
405 | touched = dom.isDescendant(e.touches[i].target, irs[0].resizeStartHandle); |
---|
406 | if(touched){ |
---|
407 | resizeStartTouchIndex = i; |
---|
408 | len++; |
---|
409 | } |
---|
410 | } |
---|
411 | |
---|
412 | if(resizeEndTouchIndex == -1 && hasResizeEnd){ |
---|
413 | touched = dom.isDescendant(e.touches[i].target, irs[1].resizeEndHandle); |
---|
414 | if(touched){ |
---|
415 | resizeEndTouchIndex = i; |
---|
416 | len++; |
---|
417 | } |
---|
418 | } |
---|
419 | |
---|
420 | if(resizeStartTouchIndex == -1 && resizeEndTouchIndex == -1){ |
---|
421 | |
---|
422 | for (var j=0; j<list.length; j++){ |
---|
423 | touched = dom.isDescendant(e.touches[i].target, list[j].container); |
---|
424 | if(touched){ |
---|
425 | moveTouchIndex = i; |
---|
426 | len++; |
---|
427 | break; |
---|
428 | } |
---|
429 | } |
---|
430 | } |
---|
431 | |
---|
432 | if(resizeStartTouchIndex != -1 && resizeEndTouchIndex != -1 && moveTouchIndex != -1){ |
---|
433 | // all touches of interest were found, ignore other ones. |
---|
434 | break; |
---|
435 | } |
---|
436 | } |
---|
437 | |
---|
438 | return { |
---|
439 | touchesLen: len, |
---|
440 | resizeStartTouchIndex: resizeStartTouchIndex, |
---|
441 | resizeEndTouchIndex: resizeEndTouchIndex, |
---|
442 | moveTouchIndex: moveTouchIndex |
---|
443 | }; |
---|
444 | } |
---|
445 | |
---|
446 | }); |
---|
447 | |
---|
448 | }); |
---|