UNPKG

dojox

Version:

Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.

456 lines (343 loc) 12.9 kB
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"], function(arr, lang, declare, dom, domGeometry, win, on, event, keys){ return declare("dojox.calendar.Touch", null, { // summary: // This plugin is managing the touch interactions on item renderers displayed by a calendar view. // touchStartEditingTimer: Integer // The delay of one touch over the renderer before setting the item in editing mode. touchStartEditingTimer: 500, // touchEndEditingTimer: Integer // The delay after which the item is leaving the editing mode after the previous editing gesture, in touch context. touchEndEditingTimer: 10000, postMixInProperties: function(){ this.on("rendererCreated", lang.hitch(this, function(irEvent){ var renderer = irEvent.renderer.renderer; this.own(on(renderer.domNode, "touchstart", lang.hitch(this, function(e){ this._onRendererTouchStart(e, renderer); }))); })); }, _onRendererTouchStart: function(e, renderer){ // tags: // private var p = this._edProps; if(p && p.endEditingTimer){ clearTimeout(p.endEditingTimer); p.endEditingTimer = null; } var theItem = renderer.item.item; if(p && p.endEditingTimer){ clearTimeout(p.endEditingTimer); p.endEditingTimer = null; } if(p != null && p.item != theItem){ // another item is edited. // stop previous item if(p.startEditingTimer){ clearTimeout(p.startEditingTimer); } this._endItemEditing("touch", false); p = null; } // initialize editing properties if(!p){ // register event listeners to manage gestures. var handles = []; handles.push(on(win.doc, "touchend", lang.hitch(this, this._docEditingTouchEndHandler))); handles.push(on(this.itemContainer, "touchmove", lang.hitch(this, this._docEditingTouchMoveHandler))); this._setEditingProperties({ touchMoved: false, item: theItem, renderer: renderer, rendererKind: renderer.rendererKind, event: e, handles: handles, liveLayout: this.liveLayout }); p = this._edProps; } if(this._isEditing){ // get info on touches lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem)); // start an editing gesture. this._startTouchItemEditingGesture(e); } else { // initial touch that will trigger or not the editing if(e.touches.length > 1){ event.stop(e); return; } // set the selection state without dispatching (on touch end) after a short amount of time. // to allow a bit of time to scroll without selecting (graphically at least) this._touchSelectionTimer = setTimeout(lang.hitch(this, function(){ this._saveSelectedItems = this.get("selectedItems"); var changed = this.selectFromEvent(e, theItem._item, renderer, false); if(changed){ this._pendingSelectedItem = theItem; }else{ delete this._saveSelectedItems; } this._touchSelectionTimer = null; }), 200); p.start = {x: e.touches[0].screenX, y: e.touches[0].screenY}; if(this.isItemEditable(p.item, p.rendererKind)){ // editing gesture timer this._edProps.startEditingTimer = setTimeout(lang.hitch(this, function(){ // we are editing, so the item *must* be selected. if(this._touchSelectionTimer){ clearTimeout(this._touchSelectionTimer); delete this._touchSelectionTime; } if(this._pendingSelectedItem){ this.dispatchChange(this._saveSelectedItems == null ? null : this._saveSelectedItems[0], this._pendingSelectedItem, null, e); delete this._saveSelectedItems; delete this._pendingSelectedItem; }else{ this.selectFromEvent(e, theItem._item, renderer); } this._startItemEditing(p.item, "touch", e); p.moveTouchIndex = 0; // A move gesture is initiated even if we don't move this._startItemEditingGesture([this.getTime(e)], "move", "touch", e); }), this.touchStartEditingTimer); } } }, _docEditingTouchMoveHandler: function(e){ // tags: // private var p = this._edProps; // When the screen is touched, it can dispatch move events if the // user press the finger a little more... var touch = {x: e.touches[0].screenX, y: e.touches[0].screenY}; if(p.startEditingTimer && (Math.abs(touch.x - p.start.x) > 25 || Math.abs(touch.y - p.start.y) > 25)) { // scroll use case, do not edit clearTimeout(p.startEditingTimer); p.startEditingTimer = null; clearTimeout(this._touchSelectionTimer); this._touchSelectionTimer = null; if(this._pendingSelectedItem){ delete this._pendingSelectedItem; this.selectFromEvent(e, null, null, false); } } // 10px tolerance to select the item (#105) if(!p.touchMoved && (Math.abs(touch.x - p.start.x) > 10 || Math.abs(touch.y - p.start.y) > 10)) { p.touchMoved = true; } if(this._editingGesture){ event.stop(e); if(p.itemBeginDispatched){ var times = []; var d = p.editKind == "resizeEnd" ? p.editedItem.endTime : p.editedItem.startTime; var subColumn = p.editedItem.subColumn; switch(p.editKind){ case "move": var touchIndex = p.moveTouchIndex == null || p.moveTouchIndex < 0 ? 0 : p.moveTouchIndex; times[0] = this.getTime(e, -1, -1, touchIndex); subColumn = this.getSubColumn(e, -1, -1, touchIndex); break; case "resizeStart": times[0] = this.getTime(e, -1, -1, p.resizeStartTouchIndex); break; case "resizeEnd": times[0] = this.getTime(e, -1, -1, p.resizeEndTouchIndex); break; case "resizeBoth": times[0] = this.getTime(e, -1, -1, p.resizeStartTouchIndex); times[1] = this.getTime(e, -1, -1, p.resizeEndTouchIndex); break; } this._moveOrResizeItemGesture(times, "touch", e, subColumn); if(p.editKind == "move"){ if(this.renderData.dateModule.compare(p.editedItem.startTime, d) == -1){ this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "start", this.autoScrollTouchMargin); }else{ this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "end", this.autoScrollTouchMargin); } }else if(e.editKind == "resizeStart" || e.editKind == "resizeBoth"){ this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "start", this.autoScrollTouchMargin); }else{ this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "end", this.autoScrollTouchMargin); } } } // else scroll, if any, is delegated to sub class }, // autoScrollTouchMargin: Integer // The minimum number of minutes of margin around the edited event. autoScrollTouchMargin: 10, _docEditingTouchEndHandler: function(e){ // tags: // private event.stop(e); var p = this._edProps; if(p.startEditingTimer){ clearTimeout(p.startEditingTimer); p.startEditingTimer = null; } if(this._isEditing){ lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem)); if(this._editingGesture){ if(p.touchesLen == 0){ // all touches were removed => end of editing gesture this._endItemEditingGesture("touch", e); if(this.touchEndEditingTimer > 0){ // Timer that trigger the end of the item editing mode. p.endEditingTimer = setTimeout(lang.hitch(this, function(){ this._endItemEditing("touch", false); }), this.touchEndEditingTimer); } // else validation must be explicit }else{ if(this._editingGesture){ this._endItemEditingGesture("touch", e); } // there touches of interest on item, process them. this._startTouchItemEditingGesture(e); } } }else if(!p.touchMoved){ event.stop(e); arr.forEach(p.handles, function(handle){ handle.remove(); }); if(this._touchSelectionTimer){ // selection timer was not reached to a proper selection. clearTimeout(this._touchSelectionTimer); this.selectFromEvent(e, p.item._item, p.renderer, true); }else if(this._pendingSelectedItem){ // selection timer was reached, dispatch change event this.dispatchChange(this._saveSelectedItems.length == 0 ? null : this._saveSelectedItems[0], this._pendingSelectedItem, null, e); // todo renderer ? delete this._saveSelectedItems; delete this._pendingSelectedItem; } if(this._pendingDoubleTap && this._pendingDoubleTap.item == p.item){ this._onItemDoubleClick({ triggerEvent: e, renderer: p.renderer, item: p.item._item }); clearTimeout(this._pendingDoubleTap.timer); delete this._pendingDoubleTap; }else{ this._pendingDoubleTap = { item: p.item, timer: setTimeout(lang.hitch(this, function(){ delete this._pendingDoubleTap; }), this.doubleTapDelay) }; this._onItemClick({ triggerEvent: e, renderer: p.renderer, item: p.item._item }); } this._edProps = null; }else{ // scroll view has finished. if(this._saveSelectedItems){ // selection without dipatching was done, but the view scrolled, // so revert last selection this.set("selectedItems", this._saveSelectedItems); delete this._saveSelectedItems; delete this._pendingSelectedItem; } arr.forEach(p.handles, function(handle){ handle.remove(); }); this._edProps = null; } }, _startTouchItemEditingGesture: function(e){ // summary: // Determines if a editing gesture is starting according to touches. // tags: // private var p = this._edProps; var fromResizeStart = p.resizeStartTouchIndex != -1; var fromResizeEnd = p.resizeEndTouchIndex != -1; if(fromResizeStart && fromResizeEnd || // initial gesture using two touches this._editingGesture && p.touchesLen == 2 && (fromResizeEnd && p.editKind == "resizeStart" || fromResizeStart && p.editKind =="resizeEnd")){ // gesture one after the other touch if(this._editingGesture && p.editKind != "resizeBoth"){ // stop ongoing gesture this._endItemEditingGesture("touch", e); } p.editKind = "resizeBoth"; this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex), this.getTime(e, -1, -1, p.resizeEndTouchIndex)], p.editKind, "touch", e); }else if(fromResizeStart && p.touchesLen == 1 && !this._editingGesture){ this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex)], "resizeStart", "touch", e); }else if(fromResizeEnd && p.touchesLen == 1 && !this._editingGesture){ this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeEndTouchIndex)], "resizeEnd", "touch", e); } else { // A move gesture is initiated even if we don't move this._startItemEditingGesture([this.getTime(e)], "move", "touch", e); } }, _getTouchesOnRenderers: function(e, item){ // summary: // Returns the touch indices that are on a editing handles or body of the renderers // tags: // private // item: Object // The render item. // e: Event // The touch event. // tags: // private var irs = this._getStartEndRenderers(item); var resizeStartTouchIndex = -1; var resizeEndTouchIndex = -1; var moveTouchIndex = -1; var hasResizeStart = irs[0] != null && irs[0].resizeStartHandle != null; var hasResizeEnd = irs[1] != null && irs[1].resizeEndHandle != null; var len = 0; var touched = false; var list = this.rendererManager.itemToRenderer[item.id]; for(var i=0; i<e.touches.length; i++){ if(resizeStartTouchIndex == -1 && hasResizeStart){ touched = dom.isDescendant(e.touches[i].target, irs[0].resizeStartHandle); if(touched){ resizeStartTouchIndex = i; len++; } } if(resizeEndTouchIndex == -1 && hasResizeEnd){ touched = dom.isDescendant(e.touches[i].target, irs[1].resizeEndHandle); if(touched){ resizeEndTouchIndex = i; len++; } } if(resizeStartTouchIndex == -1 && resizeEndTouchIndex == -1){ for (var j=0; j<list.length; j++){ touched = dom.isDescendant(e.touches[i].target, list[j].container); if(touched){ moveTouchIndex = i; len++; break; } } } if(resizeStartTouchIndex != -1 && resizeEndTouchIndex != -1 && moveTouchIndex != -1){ // all touches of interest were found, ignore other ones. break; } } return { touchesLen: len, resizeStartTouchIndex: resizeStartTouchIndex, resizeEndTouchIndex: resizeEndTouchIndex, moveTouchIndex: moveTouchIndex }; } }); });