462 lines
13 KiB
JavaScript
462 lines
13 KiB
JavaScript
define("dojox/calendar/Touch", ["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(ir){
|
|
|
|
var renderer = ir.renderer;
|
|
|
|
|
|
var h;
|
|
if(!renderer.__handles){
|
|
renderer.__handles = [];
|
|
}
|
|
|
|
h = on(renderer.domNode, "touchstart", lang.hitch(this, function(e){
|
|
this._onRendererTouchStart(e, renderer);
|
|
}));
|
|
|
|
renderer.__handles.push(h);
|
|
}));
|
|
},
|
|
|
|
_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, this.renderItemToItem(theItem, this.get("store")), 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, this.renderItemToItem(theItem, this.get("store")), 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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
switch(p.editKind){
|
|
case "move":
|
|
var touchIndex = p.moveTouchIndex == null || p.moveTouchIndex < 0 ? 0 : p.moveTouchIndex;
|
|
times[0] = this.getTime(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);
|
|
|
|
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, this.renderItemToItem(p.item, this.get("store")), 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: this.renderItemToItem(p.item, this.get("store"))
|
|
});
|
|
|
|
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: this.renderItemToItem(p.item, this.get("store"))
|
|
});
|
|
}
|
|
|
|
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);
|
|
|
|
return;
|
|
|
|
}else if(fromResizeStart && p.touchesLen == 1 && !this._editingGesture){
|
|
|
|
this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex)],
|
|
"resizeStart", "touch", e);
|
|
|
|
return;
|
|
|
|
}else if(fromResizeEnd && p.touchesLen == 1 && !this._editingGesture){
|
|
|
|
this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeEndTouchIndex)],
|
|
"resizeEnd", "touch", e);
|
|
|
|
return;
|
|
|
|
} 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.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
|
|
};
|
|
}
|
|
|
|
});
|
|
|
|
});
|