You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
12 KiB
455 lines
12 KiB
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 |
|
}; |
|
} |
|
|
|
}); |
|
|
|
});
|
|
|