[483] | 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 | }); |
---|