define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/window", "dojo/_base/event", "dojo/_base/html", "dojo/sniff", "dojo/query", "dojo/dom", "dojo/dom-style", "dojo/dom-class", "dojo/dom-construct", "dojo/dom-geometry", "dojo/on", "dojo/date", "dojo/date/locale", "dojo/when", "dijit/_WidgetBase", "dojox/widget/_Invalidating", "dojox/widget/Selection", "./time", "./StoreMixin", "./StoreManager", "./RendererManager"], function( declare, lang, arr, win, event, html, has, query, dom, domStyle, domClass, domConstruct, domGeometry, on, date, locale, when, _WidgetBase, _Invalidating, Selection, timeUtil, StoreMixin, StoreManager, RendererManager){ /*===== var __GridClickEventArgs = { // summary: // The event dispatched when the grid is clicked or double-clicked. // date: Date // The start of the previously displayed time interval, if any. // triggerEvent: Event // The event at the origin of this event. }; =====*/ /*===== var __ItemMouseEventArgs = { // summary: // The event dispatched when an item is clicked, double-clicked or context-clicked. // item: Object // The item clicked. // renderer: dojox/calendar/_RendererMixin // The item renderer clicked. // triggerEvent: Event // The event at the origin of this event. }; =====*/ /*===== var __itemEditingEventArgs = { // summary: // An item editing event. // item: Object // The render item that is being edited. Set/get the startTime and/or endTime properties to customize editing behavior. // storeItem: Object // The real data from the store. DO NOT change properties, but you may use properties of this item in the editing behavior logic. // editKind: String // Kind of edit: "resizeBoth", "resizeStart", "resizeEnd" or "move". // dates: Date[] // The computed date/time of the during the event editing. One entry per edited date (touch use case). // startTime: Date? // The start time of data item. // endTime: Date? // The end time of data item. // sheet: String // For views with several sheets (columns view for example), the sheet when the event occurred. // source: dojox/calendar/ViewBase // The view where the event occurred. // eventSource: String // The device that triggered the event. This property can take the following values: // // - "mouse", // - "keyboard", // - "touch" // triggerEvent: Event // The event at the origin of this event. }; =====*/ /*===== var __rendererLifecycleEventArgs = { // summary: // An renderer lifecycle event. // renderer: Object // The renderer. // source: dojox/calendar/ViewBase // The view where the event occurred. // item:Object? // The item that will be displayed by the renderer for the "rendererCreated" and "rendererReused" events. }; =====*/ return declare("dojox.calendar.ViewBase", [_WidgetBase, StoreMixin, _Invalidating, Selection], { // summary: // The dojox.calendar.ViewBase widget is the base of calendar view widgets // datePackage: Object // JavaScript namespace to find Calendar routines. Uses Gregorian Calendar routines at dojo.date by default. datePackage: date, _calendar: "gregorian", // viewKind: String // Kind of the view. Used by the calendar widget to determine how to configure the view. viewKind: null, // _layoutStep: [protected] Integer // The number of units displayed by a visual layout unit (i.e. a column or a row) _layoutStep: 1, // _layoutStep: [protected] Integer // The unit displayed by a visual layout unit (i.e. a column or a row) _layoutUnit: "day", // resizeCursor: String // CSS value to apply to the cursor while resizing an item renderer. resizeCursor: "n-resize", // formatItemTimeFunc: Function // Optional function to format the time of day of the item renderers. // The function takes the date, the render data object, the view and the data item as arguments and returns a String. formatItemTimeFunc: null, _cssDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], _getFormatItemTimeFuncAttr: function(){ if(this.formatItemTimeFunc){ return this.formatItemTimeFunc; } if(this.owner != null){ return this.owner.get("formatItemTimeFunc"); } }, // The listeners added by the view itself. _viewHandles: null, // doubleTapDelay: Integer // The maximum time amount in milliseconds between to touchstart events that trigger a double-tap event. doubleTapDelay: 300, constructor: function(/*Object*/ args){ args = args || {}; this._calendar = args.datePackage ? args.datePackage.substr(args.datePackage.lastIndexOf(".")+1) : this._calendar; this.dateModule = args.datePackage ? lang.getObject(args.datePackage, false) : date; this.dateClassObj = this.dateModule.Date || Date; this.dateLocaleModule = args.datePackage ? lang.getObject(args.datePackage+".locale", false) : locale; this._viewHandles = []; this.storeManager = new StoreManager({owner: this, _ownerItemsProperty: "items"}); this.storeManager.on("layoutInvalidated", lang.hitch(this, this._refreshItemsRendering)); this.storeManager.on("dataLoaded", lang.hitch(this, function(items){ this.set("items", items); })); this.storeManager.on("renderersInvalidated", lang.hitch(this, function(item){ this.updateRenderers(item); })); this.rendererManager = new RendererManager({owner: this}); this.rendererManager.on("rendererCreated", lang.hitch(this, this._onRendererCreated)); this.rendererManager.on("rendererReused", lang.hitch(this, this._onRendererReused)); this.rendererManager.on("rendererRecycled", lang.hitch(this, this._onRendererRecycled)); this.rendererManager.on("rendererDestroyed", lang.hitch(this, this._onRendererDestroyed)); this.decorationStoreManager = new StoreManager({owner: this, _ownerItemsProperty: "decorationItems"}); this.decorationStoreManager.on("layoutInvalidated", lang.hitch(this, this._refreshDecorationItemsRendering)); this.decorationStoreManager.on("dataLoaded", lang.hitch(this, function(items){ this.set("decorationItems", items); })); this.decorationRendererManager = new RendererManager({owner: this}); this._setupDayRefresh(); }, destroy: function(preserveDom){ this.rendererManager.destroy(); this.decorationRendererManager.destroy(); while(this._viewHandles.length > 0){ this._viewHandles.pop().remove(); } this.inherited(arguments); }, _setupDayRefresh: function(){ // Refresh the view when the current day changes. var now = this.newDate(new Date()); var d = timeUtil.floor(now, "day", 1, this.dateClassObj); var d = this.dateModule.add(d, "day", 1); // manages DST at 24h if(d.getHours() == 23){ d = this.dateModule.add(d, "hour", 2); // go to 1am }else{ d = timeUtil.floorToDay(d, true, this.dateClassObj); } setTimeout(lang.hitch(this, function(){ if(!this._isEditing){ this.refreshRendering(true); // recursive refresh } this._setupDayRefresh(); }), d.getTime()-now.getTime() + 5000); // add 5 seconds to be sure to be tomorrow }, resize: function(changeSize){ // summary: // Function to call when the view is resized. // If the view is in a Dijit container or in a Dojo mobile container, it will be automatically called. // On other use cases, this method must called when the window is resized and/or when the orientation has changed. if(changeSize){ domGeometry.setMarginBox(this.domNode, changeSize); } }, // view lifecycle methods beforeActivate: function(){ // summary: // Function invoked just before the view is displayed by the calendar. // tags: // protected }, afterActivate: function(){ // summary: // Function invoked just after the view is displayed by the calendar. // tags: // protected }, beforeDeactivate: function(){ // summary: // Function invoked just before the view is hidden or removed by the calendar. // tags: // protected }, afterDeactivate: function(){ // summary: // Function invoked just after the view is the view is hidden or removed by the calendar. // tags: // protected }, _getTopOwner: function(){ // summary: // Returns the top owner: the calendar or the parent view. var p = this; while(p.owner != undefined){ p = p.owner; } return p; }, _createRenderData: function(){ // summary: // Creates the object that contains all the data needed to render this widget. // tags: // protected }, _validateProperties: function(){ // summary: // Validates the widget properties before the rendering pass. // tags: // protected }, _setText: function(node, text, allowHTML){ // summary: // Creates a text node under the parent node after having removed children nodes if any. // node: Node // The node that will contain the text node. // text: String // The text to set to the text node. if(text != null){ if(!allowHTML && node.hasChildNodes()){ // span > textNode node.childNodes[0].childNodes[0].nodeValue = text; }else{ while(node.hasChildNodes()){ node.removeChild(node.lastChild); } var tNode = win.doc.createElement("span"); if(has("dojo-bidi")){ this.applyTextDir(tNode, text); } if(allowHTML){ tNode.innerHTML = text; }else{ tNode.appendChild(win.doc.createTextNode(text)); } node.appendChild(tNode); } } }, isAscendantHasClass: function(node, ancestor, className){ // summary: // Determines if a node has an ascendant node that has the css class specified. // node: Node // The DOM node. // ancestor: Node // The ancestor node used to limit the search in hierarchy. // className: String // The css class name. // returns: Boolean while(node != ancestor && node != document){ if(domClass.contains(node, className)){ return true; } node = node.parentNode; } return false; }, isWeekEnd: function(date){ // summary: // Determines whether the specified date is a week-end. // This method is using dojo.date.locale.isWeekend() method as // dojox.date.XXXX calendars are not supporting this method. // date: Date // The date to test. return locale.isWeekend(date); }, getWeekNumberLabel: function(date){ // summary: // Returns the week number string from dojo.date.locale.format() method as // dojox.date.XXXX calendar are not supporting the "w" pattern. // date: Date // The date to format. if(date.toGregorian){ date = date.toGregorian(); } return locale.format(date, { selector: "date", datePattern: "w"}); }, addAndFloor: function(date, unit, steps){ // date must be floored!! // unit >= day var d = this.dateModule.add(date, unit, steps); if(d.getHours() == 23){ d = this.dateModule.add(d, "hour", 2); // go to 1am }else{ d = timeUtil.floorToDay(d, true, this.dateClassObj); } return d; }, floorToDay: function(date, reuse){ // summary: // Floors the specified date to the start of day. // date: Date // The date to floor. // reuse: Boolean // Whether use the specified instance or create a new one. Default is false. // returns: Date return timeUtil.floorToDay(date, reuse, this.dateClassObj); }, floorToMonth: function(date, reuse){ // summary: // Floors the specified date to the start of the date's month. // date: Date // The date to floor. // reuse: Boolean // Whether use the specified instance or create a new one. Default is false. // returns: Date return timeUtil.floorToMonth(date, reuse, this.dateClassObj); }, floorDate: function(date, unit, steps, reuse){ // summary: // floors the date to the unit. // date: Date // The date/time to floor. // unit: String // The unit. Valid values are "minute", "hour", "day". // steps: Integer // For "day" only 1 is valid. // reuse: Boolean // Whether use the specified instance or create a new one. Default is false. // returns: Date return timeUtil.floor(date, unit, steps, reuse, this.dateClassObj); }, isToday: function(date){ // summary: // Returns whether the specified date is in the current day. // date: Date // The date to test. // renderData: Object // The current renderData // returns: Boolean return timeUtil.isToday(date, this.dateClassObj); }, isStartOfDay: function(d){ // summary: // Tests if the specified date represents the starts of day. // d:Date // The date to test. // returns: Boolean return timeUtil.isStartOfDay(d, this.dateClassObj, this.dateModule); }, isOverlapping: function(renderData, start1, end1, start2, end2, includeLimits){ // summary: // Computes if the first time range defined by the start1 and end1 parameters // is overlapping the second time range defined by the start2 and end2 parameters. // renderData: Object // The render data. // start1: Date // The start time of the first time range. // end1: Date // The end time of the first time range. // start2: Date // The start time of the second time range. // end2: Date // The end time of the second time range. // includeLimits: Boolean // Whether include the end time or not. // returns: Boolean return timeUtil.isOverlapping(renderData, start1, end1, start2, end2, includeLimits); }, computeRangeOverlap: function(renderData, start1, end1, start2, end2, includeLimits){ // summary: // Computes the overlap time range of the time ranges. // Returns a vector of Date with at index 0 the start time and at index 1 the end time. // renderData: Object. // The render data. // start1: Date // The start time of the first time range. // end1: Date // The end time of the first time range. // start2: Date // The start time of the second time range. // end2: Date // The end time of the second time range. // includeLimits: Boolean // Whether include the end time or not. // returns: Date[] var cal = renderData.dateModule; if(start1 == null || start2 == null || end1 == null || end2 == null){ return null; } var comp1 = cal.compare(start1, end2); var comp2 = cal.compare(start2, end1); if(includeLimits){ if(comp1 == 0 || comp1 == 1 || comp2 == 0 || comp2 == 1){ return null; } } else if(comp1 == 1 || comp2 == 1){ return null; } return [ this.newDate(cal.compare(start1, start2)>0 ? start1: start2, renderData), this.newDate(cal.compare(end1, end2)>0 ? end2: end1, renderData) ]; }, isSameDay : function(date1, date2){ // summary: // Tests if the specified dates are in the same day. // date1: Date // The first date. // date2: Date // The second date. // returns: Boolean if(date1 == null || date2 == null){ return false; } return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate(); }, computeProjectionOnDate: function(renderData, refDate, date, max){ // summary: // Computes the time to pixel projection in a day. // renderData: Object // The render data. // refDate: Date // The reference date that defines the destination date. // date: Date // The date to project. // max: Integer // The size in pixels of the representation of a day. // tags: // protected // returns: Number var cal = renderData.dateModule; var minH = renderData.minHours; var maxH = renderData.maxHours; if(max <= 0 || cal.compare(date, refDate) == -1){ return 0; } var gt = function(d){ return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); }; var referenceDate = this.floorToDay(refDate, false, renderData); if(date.getDate() != referenceDate.getDate()){ if(date.getMonth() == referenceDate.getMonth()){ if(date.getDate() < referenceDate.getDate()){ return 0; } else if(date.getDate() > referenceDate.getDate() && maxH < 24){ return max; } }else{ if(date.getFullYear() == referenceDate.getFullYear()){ if(date.getMonth() < referenceDate.getMonth()){ return 0; } else if(date.getMonth() > referenceDate.getMonth()){ return max; } }else{ if(date.getFullYear() < referenceDate.getFullYear()){ return 0; } else if(date.getFullYear() > referenceDate.getFullYear()){ return max; } } } } var res; var ONE_DAY = 86400; // 24h x 60m x 60s if(this.isSameDay(refDate, date) || maxH > 24){ var d = lang.clone(refDate); var minTime = 0; if(minH != null && minH != 0){ d.setHours(minH); minTime = gt(d); } d = lang.clone(refDate); d.setHours(maxH); var maxTime; if(maxH == null || maxH == 24){ maxTime = ONE_DAY; }else if(maxH > 24){ maxTime = ONE_DAY + gt(d); }else{ maxTime = gt(d); } //precision is the second //use this API for daylight time issues. var delta = 0; if(maxH > 24 && refDate.getDate() != date.getDate()){ delta = ONE_DAY + gt(date); }else{ delta = gt(date); } if(delta < minTime){ return 0; } if(delta > maxTime){ return max; } delta -= minTime; res = (max * delta)/(maxTime - minTime); }else{ if(date.getDate() < refDate.getDate() && date.getMonth() == refDate.getMonth()){ return 0; } var d2 = this.floorToDay(date); var dp1 = renderData.dateModule.add(refDate, "day", 1); dp1 = this.floorToDay(dp1, false, renderData); if(cal.compare(d2, refDate) == 1 && cal.compare(d2, dp1) == 0 || cal.compare(d2, dp1) == 1){ res = max; }else{ res = 0; } } return res; }, getTime: function(e, x, y, touchIndex){ // summary: // Returns the time displayed at the specified point by this component. // e: Event // Optional mouse event. // x: Number // Position along the x-axis with respect to the sheet container used if event is not defined. // y: Number // Position along the y-axis with respect to the sheet container (scroll included) used if event is not defined. // touchIndex: Integer // If parameter 'e' is not null and a touch event, the index of the touch to use. // returns: Date return null; }, getSubColumn: function(e, x, y, touchIndex){ // summary: // Returns the sub column at the specified point by this component. // e: Event // Optional mouse event. // x: Number // Position along the x-axis with respect to the sheet container used if event is not defined. // y: Number // Position along the y-axis with respect to the sheet container (scroll included) used if event is not defined. // touchIndex: Integer // If parameter 'e' is not null and a touch event, the index of the touch to use. // returns: Object return null; }, getSubColumnIndex: function(value){ // summary: // Returns the sub column index that has the specified value, if any. -1 otherwise. // value: String // The sub column index. if(this.subColumns){ for(var i=0; i max){ pos = max; } if(vertical){ this._scrollPos = pos; }else{ this._hScrollPos = pos; } var rtl = !this.isLeftToRight(); if(this._domScroll){ if(vertical){ this.scrollContainer.scrollTop = pos; }else{ arr.forEach(this._hScrollNodes, function(elt){ domStyle.set(elt, "left", ((rtl?1:-1) * pos) + "px"); }, this); } }else{ if(!this._cssPrefix){ this._cssPrefix = this.getCSSPrefix(); } var cssProp = this._cssPrefix+"transform"; if(vertical){ domStyle.set(this.sheetContainer, cssProp, "translateY(-"+pos+"px)"); }else{ var css = "translateX("+(rtl?"":"-")+pos+"px)"; arr.forEach(this._hScrollNodes, function(elt){ domStyle.set(elt, cssProp, css); }, this); } } }, _setScrollPosition: function(pos){ // summary: // Sets the verical scroll position (if the view is scrollable), using the scroll method defined. // tags: // protected this._setScrollPositionBase(pos, true); }, _getScrollPosition: function(){ // summary: // Returns the vertical scroll position (if the view is scrollable), using the scroll method defined. // tags: // protected return this._scrollPos; }, _setHScrollPosition: function(pos){ // summary: // Sets the horizontal scroll position (if the view is scrollable), using the scroll method defined. // tags: // protected this._setScrollPositionBase(pos, false); }, _setHScrollPositionImpl: function(pos, useDom, cssProperty){ // summary: // Sets the horizontal scroll position on sub elements (if the view is scrollable), using the scroll method defined. // Important: must be implemented by sub classes and not called directly. Use _setHScrollPosition() method instead. // tags: // private var css = useDom ? null : "translateX(-"+pos+"px)"; arr.forEach(this._hScrollNodes, function(elt){ if(useDom){ elt.scrollLeft = pos; domStyle.set(elt, "left", (-pos) + "px"); }else{ domStyle.set(elt, cssProp, css); } }, this); }, _hScrollPos: 0, _getHScrollPosition: function(){ // summary: // Returns the horizontal scroll position (if the view is scrollable), using the scroll method defined. // tags: // protected return this._hScrollPos; }, scrollView: function(dir){ // summary: // If the view is scrollable, scrolls it vertically to the specified direction. // dir: Integer // Direction of the scroll. Valid values are -1 and 1. // tags: // extension }, ensureVisibility: function(start, end, margin, visibilityTarget, duration){ // summary: // Scrolls the view if the [start, end] time range is not visible or only partially visible. // start: Date // Start time of the range of interest. // end: Date // End time of the range of interest. // margin: int // Margin in minutes around the time range. // visibilityTarget: String // The end(s) of the time range to make visible. // Valid values are: "start", "end", "both". // duration: Number // Optional, the maximum duration of the scroll animation. // tags: // extension }, //////////////////////////////////////////////////////// // // Store & Items // //////////////////////////////////////////////////////// _getStoreAttr: function(){ if(this.owner){ return this.owner.get("store"); } return this.store; }, _setItemsAttr: function(value){ this._set("items", value); this.displayedItemsInvalidated = true; }, _refreshItemsRendering: function(){ var rd = this.renderData; this._computeVisibleItems(rd); this._layoutRenderers(rd); }, _refreshDecorationItemsRendering: function(){ var rd = this.renderData; this._computeVisibleItems(rd); this._layoutDecorationRenderers(rd); }, invalidateLayout: function(){ // summary: // Triggers a re-layout of the renderers. this._layoutRenderers(this.renderData); this._layoutDecorationRenderers(this.renderData); }, _setDecorationItemsAttr: function(value){ this._set("decorationItems", value); this.displayedDecorationItemsInvalidated = true; }, _getDecorationStoreAttr: function(){ if(this.owner){ return this.owner.get("decorationStore"); } return this.decorationStore; }, _setDecorationStoreAttr: function(value){ this.decorationStore = value; this.decorationStoreManager.set("store", value); }, //////////////////////////////////////////////////////// // // Layout // //////////////////////////////////////////////////////// computeOverlapping: function(layoutItems, func){ // summary: // Computes the overlap layout of a list of items. A lane and extent properties are added to each layout item. // layoutItems: Object[] // List of layout items, each item must have a start and end properties. // addedPass: Function // Whether computes the extent of each item renderer on free sibling lanes. // returns: Object // tags: // protected if(layoutItems.length == 0){ return { numLanes: 0, addedPassRes: [1] }; } var lanes = []; for(var i=0; i add a lane lanes.push([layoutItem]); layoutItem.lane = lanes.length-1; layoutItem.extent = -1; }, _layoutInterval: function(renderData, index, start, end, items){ // summary: // For each item in the items list: retrieve a renderer, compute its location and size and add it to the DOM. // renderData: Object // The render data. // index: Integer // The index of the interval. // start: Date // The start time of the displayed date interval. // end: Date // The end time of the displayed date interval. // items: Object[] // The list of the items to represent. // tags: // extension }, // layoutPriorityFunction: Function // An optional comparison function use to determine the order the item will be laid out // The function is used to sort an array and must, as any sorting function, take two items // as argument and must return an integer whose sign define order between arguments. // By default, a comparison by start time then end time is used. layoutPriorityFunction: null, _sortItemsFunction: function(a, b){ var res = this.dateModule.compare(a.startTime, b.startTime); if(res == 0){ res = -1 * this.dateModule.compare(a.endTime, b.endTime); } return res; }, _layoutRenderers: function(renderData){ this._layoutRenderersImpl(renderData, this.rendererManager, renderData.items, "dataItems"); }, _layoutDecorationRenderers: function(renderData){ this._layoutRenderersImpl(renderData, this.decorationRendererManager, renderData.decorationItems, "decorationItems"); }, _layoutRenderersImpl: function(renderData, rendererManager, items, itemType){ // summary: // Renders the data items. This method will call the _layoutInterval() method. // renderData: Object // The render data. // tags: // protected if(!items){ return; } // recycle renderers first rendererManager.recycleItemRenderers(); var cal = renderData.dateModule; // Date var startDate = this.newDate(renderData.startTime); // Date and time var startTime = lang.clone(startDate); var endDate; var items = items.concat(); var itemsTemp = [], events; var processing = {}; var index = 0; while(cal.compare(startDate, renderData.endTime) == -1 && items.length > 0){ endDate = this.addAndFloor(startDate, this._layoutUnit, this._layoutStep); var endTime = lang.clone(endDate); if(renderData.minHours){ startTime.setHours(renderData.minHours); } if(renderData.maxHours != undefined && renderData.maxHours != 24){ if(renderData.maxHours < 24){ endTime = cal.add(endDate, "day", -1); } // else > 24 endTime = this.floorToDay(endTime, true, renderData); endTime.setHours(renderData.maxHours - (renderData.maxHours < 24 ? 0 : 24)); } // look for events that overlap the current sub interval events = arr.filter(items, function(item){ var r = this.isOverlapping(renderData, item.startTime, item.endTime, startTime, endTime); if(r){ processing[item.id] = true; itemsTemp.push(item); }else{ if(processing[item.id]){ delete processing[item.id]; }else{ itemsTemp.push(item); } } return r; }, this); items = itemsTemp; itemsTemp = []; // if event are in the current sub interval, layout them if(events.length > 0){ // Sort the item according a sorting function, by default start time then end time comparison are used. events.sort(lang.hitch(this, this.layoutPriorityFunction ? this.layoutPriorityFunction : this._sortItemsFunction)); this._layoutInterval(renderData, index, startTime, endTime, events, itemType); } startDate = endDate; startTime = lang.clone(startDate); index++; } this._onRenderersLayoutDone(this); }, ///////////////////////////////////////////////////////////////// // // Renderers management // //////////////////////////////////////////////////////////////// _recycleItemRenderers: function(remove){ this.rendererManager.recycleItemRenderers(remove); }, getRenderers: function(item){ // summary: // Returns the renderers that are currently used to displayed the speficied item. // Returns an array of objects that contains two properties: // - container: The DOM node that contains the renderer. // - renderer: The dojox.calendar._RendererMixin instance. // Do not keep references on the renderers are they are recycled and reused for other items. // item: Object // The data or render item. // returns: Object[] return this.rendererManager.getRenderers(item); }, // itemToRendererKindFunc: Function // An optional function to associate a kind of renderer ("horizontal", "label" or null) with the specified item. // By default, if an item is lasting more that 24 hours an horizontal item is used, otherwise a label is used. itemToRendererKindFunc: null, _itemToRendererKind: function(item){ // summary: // Associates a kind of renderer with a data item. // item: Object // The data item. // returns: String // tags: // protected if(this.itemToRendererKindFunc){ return this.itemToRendererKindFunc(item); } return this._defaultItemToRendererKindFunc(item); // String }, _defaultItemToRendererKindFunc:function(item){ // tags: // private return null; }, _createRenderer: function(item, kind, rendererClass, cssClass){ // summary: // Creates an item renderer of the specified kind. A renderer is an object with the "container" and "instance" properties. // item: Object // The data item. // kind: String // The kind of renderer. // rendererClass: Object // The class to instantiate to create the renderer. // returns: Object // tags: // protected return this.rendererManager.createRenderer(item, kind, rendererClass, cssClass); }, _onRendererCreated: function(e){ if(e.source == this){ this.onRendererCreated(e); } if(this.owner != null){ this.owner._onRendererCreated(e); } }, onRendererCreated: function(e){ // summary: // Event dispatched when an item renderer has been created. // e: __rendererLifecycleEventArgs // The renderer lifecycle event. // tags: // callback }, _onRendererRecycled: function(e){ if(e.source == this){ this.onRendererRecycled(e); } if(this.owner != null){ this.owner._onRendererRecycled(e); } }, onRendererRecycled: function(e){ // summary: // Event dispatched when an item renderer has been recycled. // e: __rendererLifecycleEventArgs // The renderer lifecycle event. // tags: // callback }, _onRendererReused: function(e){ if(e.source == this){ this.onRendererReused(e); } if(this.owner != null){ this.owner._onRendererReused(e); } }, onRendererReused: function(e){ // summary: // Event dispatched when an item renderer that was recycled is reused. // e: __rendererLifecycleEventArgs // The renderer lifecycle event. // tags: // callback }, _onRendererDestroyed: function(e){ if(e.source == this){ this.onRendererDestroyed(e); } if(this.owner != null){ this.owner._onRendererDestroyed(e); } }, onRendererDestroyed: function(e){ // summary: // Event dispatched when an item renderer is destroyed. // e: __rendererLifecycleEventArgs // The renderer lifecycle event. // tags: // callback }, _onRenderersLayoutDone: function(view){ // tags: // private this.onRenderersLayoutDone(view); if(this.owner != null){ this.owner._onRenderersLayoutDone(view); } }, onRenderersLayoutDone: function(view){ // summary: // Event triggered when item renderers layout has been done. // tags: // callback }, _recycleRenderer: function(renderer, remove){ // summary: // Recycles the item renderer to be reused in the future. // renderer: dojox/calendar/_RendererMixin // The item renderer to recycle. // tags: // protected this.rendererManager.recycleRenderer(renderer, remove); }, _destroyRenderer: function(renderer){ // summary: // Destroys the item renderer. // renderer: dojox/calendar/_RendererMixin // The item renderer to destroy. // tags: // protected this.rendererManager.destroyRenderer(renderer); }, _destroyRenderersByKind: function(kind){ // tags: // private this.rendererManager.destroyRenderersByKind(kind); }, _updateEditingCapabilities: function(item, renderer){ // summary: // Update the moveEnabled and resizeEnabled properties of a renderer according to its event current editing state. // item: Object // The store data item. // renderer: dojox/calendar/_RendererMixin // The item renderer. // tags: // protected var moveEnabled = this.isItemMoveEnabled(item, renderer.rendererKind); var resizeEnabled = this.isItemResizeEnabled(item, renderer.rendererKind); var changed = false; if(moveEnabled != renderer.get("moveEnabled")){ renderer.set("moveEnabled", moveEnabled); changed = true; } if(resizeEnabled != renderer.get("resizeEnabled")){ renderer.set("resizeEnabled", resizeEnabled); changed = true; } if(changed){ renderer.updateRendering(); } }, updateRenderers: function(obj, stateOnly){ // summary: // Updates all the renderers that represents the specified item(s). // obj: Object // A render item or an array of render items. // stateOnly: Boolean // Whether only the state of the item has changed (selected, edited, edited, focused) or a more global change has occured. // tags: // protected if(obj == null){ return; } var items = lang.isArray(obj) ? obj : [obj]; for(var i=0; i0){ var renderer = renderers[0]; if(renderer){ // trigger editing this._onRendererHandleMouseDown(e, renderer.renderer, "resizeEnd"); this._startItemEditing(newRenderItem, "mouse"); } } } }, _onGridMouseMove: function(e){ // tags: // private }, _onGridMouseUp: function(e){ // tags: // private }, _onGridTouchStart: function(e){ // tags: // private var p = this._edProps; this._gridProps = { event: e, fromItem: this.isAscendantHasClass(e.target, this.eventContainer, "dojoxCalendarEvent") }; if(this._isEditing){ if(this._gridProps){ this._gridProps.editingOnStart = true; } lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem)); if(p.touchesLen == 0){ if(p && p.endEditingTimer){ clearTimeout(p.endEditingTimer); p.endEditingTimer = null; } this._endItemEditing("touch", false); } } this._doEndItemEditing(this.owner, "touch"); event.stop(e); }, _doEndItemEditing: function(obj, eventSource){ // tags: // private if(obj && obj._isEditing){ var p = obj._edProps; if(p && p.endEditingTimer){ clearTimeout(p.endEditingTimer); p.endEditingTimer = null; } obj._endItemEditing(eventSource, false); } }, _onGridTouchEnd: function(e){ // tags: // private }, _onGridTouchMove: function(e){ // tags: // private }, __fixEvt: function(e){ // summary: // Extension point for a view to add some event properties to a calendar event. // tags: // callback return e; }, _dispatchCalendarEvt: function(e, name){ // summary: // Adds view properties to event and enable bubbling at owner level. // e: Event // The dispatched event. // name: String // The event name. // tags: // protected e = this.__fixEvt(e); this[name](e); if(this.owner){ this.owner[name](e); } return e; }, _onGridClick: function(e){ // tags: // private if(!e.triggerEvent){ e = { date: this.getTime(e), triggerEvent: e }; } this._dispatchCalendarEvt(e, "onGridClick"); }, onGridClick: function(e){ // summary: // Event dispatched when the grid has been clicked. // e: __GridClickEventArgs // The event dispatched when the grid is clicked. // tags: // callback }, _onGridDoubleClick: function(e){ // tags: // private if(!e.triggerEvent){ e = { date: this.getTime(e), triggerEvent: e }; } this._dispatchCalendarEvt(e, "onGridDoubleClick"); }, onGridDoubleClick: function(e){ // summary: // Event dispatched when the grid has been double-clicked. // e: __GridClickEventArgs // The event dispatched when the grid is double-clicked. // tags: // protected }, _onItemClick: function(e){ // tags: // private this._dispatchCalendarEvt(e, "onItemClick"); }, onItemClick: function(e){ // summary: // Event dispatched when an item renderer has been clicked. // e: __ItemMouseEventArgs // The event dispatched when an item is clicked. // tags: // callback }, _onItemDoubleClick: function(e){ // tags: // private this._dispatchCalendarEvt(e, "onItemDoubleClick"); }, onItemDoubleClick: function(e){ // summary: // Event dispatched when an item renderer has been double-clicked. // e: __ItemMouseEventArgs // The event dispatched when an item is double-clicked. // tags: // callback }, _onItemContextMenu: function(e){ this._dispatchCalendarEvt(e, "onItemContextMenu"); // tags: // private }, onItemContextMenu: function(e){ // summary: // Event dispatched when an item renderer has been context-clicked. // e: __ItemMouseEventArgs // The event dispatched when an item is context-clicked. // tags: // callback }, ////////////////////////////////////////////////////////// // // Editing // ////////////////////////////////////////////////////////// _getStartEndRenderers: function(item){ // summary: // Returns an array that contains the first and last renderers of an item // that are currently displayed. They could be the same renderer if only one renderer is used. // item: Object // The render item. // returns: Object[] // tags: // protected var list = this.rendererManager.itemToRenderer[item.id]; if(list == null){ return null; } // trivial and most common use case. if(list.length == 1){ var node = list[0].renderer; return [node, node]; } var rd = this.renderData; var resizeStartFound = false; var resizeEndFound = false; var res = []; for(var i=0; itrue, the item renderers in the control are editable. // The user can click on an item renderer, or use the keyboard or touch devices, to move or resize the associated event. editable: true, // moveEnabled: Boolean // A flag that indicates whether the user can move items displayed. // If true, the user can move the items. moveEnabled: true, // resizeEnabled: Boolean // A flag that indicates whether the items can be resized. // If `true`, the control supports resizing of items. resizeEnabled: true, isItemEditable: function(item, rendererKind){ // summary: // Computes whether particular item renderer can be edited or not. // By default it is using the editable property value. // item: Object // The item represented by the renderer. // rendererKind: String // The kind of renderer. // returns: Boolean return this.getItemStoreState(item) != "storing" && this.editable && (this.owner ? this.owner.isItemEditable(item, rendererKind) : true); }, isItemMoveEnabled: function(item, rendererKind){ // summary: // Computes whether particular item renderer can be moved. // By default it is using the moveEnabled property value. // item: Object // The item represented by the renderer. // rendererKind: String // The kind of renderer. // returns: Boolean return this.isItemEditable(item, rendererKind) && this.moveEnabled && (this.owner ? this.owner.isItemMoveEnabled(item, rendererKind): true); }, isItemResizeEnabled: function(item, rendererKind){ // summary: // Computes whether particular item renderer can be resized. // By default it is using the resizedEnabled property value. // item: Object // The item represented by the renderer. // rendererKind: String // The kind of renderer. // returns: Boolean return this.isItemEditable(item, rendererKind) && this.resizeEnabled && (this.owner ? this.owner.isItemResizeEnabled(item, rendererKind): true); }, // _isEditing: Boolean // Whether an item is being edited or not. _isEditing: false, isItemBeingEdited: function(item){ // summary: // Returns whether an item is being edited or not. // item: Object // The item to test. // returns: Boolean return this._isEditing && this._edProps && this._edProps.editedItem && this._edProps.editedItem.id == item.id; }, _setEditingProperties: function(props){ // summary: // Registers the editing properties used by the editing functions. // This method should only be called by editing interaction mixins like Mouse, Keyboard and Touch. // tags: // protected this._edProps = props; }, _startItemEditing: function(item, eventSource){ // summary: // Configures the component, renderers to start one (mouse) of several (touch, keyboard) editing gestures. // item: Object // The item that will be edited. // eventSource: String // "mouse", "keyboard", "touch" // tags: // protected this._isEditing = true; this._getTopOwner()._isEditing = true; var p = this._edProps; p.editedItem = item; p.storeItem = item._item; p.eventSource = eventSource; p.secItem = this._secondarySheet ? this._findRenderItem(item.id, this._secondarySheet.renderData.items) : null; p.ownerItem = this.owner ? this._findRenderItem(item.id, this.items) : null; if (!p.liveLayout){ p.editSaveStartTime = item.startTime; p.editSaveEndTime = item.endTime; p.editItemToRenderer = this.rendererManager.itemToRenderer; p.editItems = this.renderData.items; p.editRendererList = this.rendererManager.rendererList; this.renderData.items = [p.editedItem]; var id = p.editedItem.id; this.rendererManager.itemToRenderer = {}; this.rendererManager.rendererList = []; var list = p.editItemToRenderer[id]; p.editRendererIndices = []; arr.forEach(list, lang.hitch(this, function(ir, i){ if(this.rendererManager.itemToRenderer[id] == null){ this.rendererManager.itemToRenderer[id] = [ir]; }else{ this.rendererManager.itemToRenderer[id].push(ir); } this.rendererManager.rendererList.push(ir); })); // remove in old map & list the occurrence used by the edited item p.editRendererList = arr.filter(p.editRendererList, function(ir){ return ir != null && ir.renderer.item.id != id; }); delete p.editItemToRenderer[id]; } // graphic feedback refresh this._layoutRenderers(this.renderData); this._onItemEditBegin({ item: item, storeItem: p.storeItem, eventSource: eventSource }); }, _onItemEditBegin: function(e){ // tags: // private this._editStartTimeSave = this.newDate(e.item.startTime); this._editEndTimeSave = this.newDate(e.item.endTime); this._dispatchCalendarEvt(e, "onItemEditBegin"); }, onItemEditBegin: function(e){ // summary: // Event dispatched when the item is entering the editing mode. // tags: // callback }, _endItemEditing: function(/*String*/eventSource, /*Boolean*/canceled){ // summary: // Leaves the item editing mode. // item: Object // The item that was edited. // eventSource: String // "mouse", "keyboard", "touch" // tags: // protected if(this._editingGesture){ // make sure to stop the current gesture if any this._endItemEditingGesture(eventSource); } this._isEditing = false; this._getTopOwner()._isEditing = false; var p = this._edProps; arr.forEach(p.handles, function(handle){ handle.remove(); }); if (!p.liveLayout){ this.renderData.items = p.editItems; this.rendererManager.rendererList = p.editRendererList.concat(this.rendererManager.rendererList); lang.mixin(this.rendererManager.itemToRenderer, p.editItemToRenderer); } this._onItemEditEnd(lang.mixin(this._createItemEditEvent(), { item: p.editedItem, storeItem: p.storeItem, eventSource: eventSource, completed: !canceled })); this._layoutRenderers(this.renderData); this._edProps = null; }, _onItemEditEnd: function(e){ // tags: // private this._dispatchCalendarEvt(e, "onItemEditEnd"); if(!e.isDefaultPrevented()){ var store = this.get("store"); // updated store item var storeItem = this.renderItemToItem(e.item, store); var s = this._getItemStoreStateObj(e.item); if(s != null && s.state == "unstored"){ if(e.completed){ // renderItemToItem cannot find the original data item // (as it does not exist in the store yet) to mixin with. // so we must do it here. storeItem = lang.mixin(s.item, storeItem); this._setItemStoreState(storeItem, "storing"); var oldID = store.getIdentity(storeItem); var options = null; if(this._tempItemsMap && this._tempItemsMap[oldID]){ options = {temporaryId: oldID}; delete this._tempItemsMap[oldID]; delete storeItem[store.idProperty]; } // add to the store. when(store.add(storeItem, options), lang.hitch(this, function(res){ var id; if(lang.isObject(res)){ id = store.getIdentity(res); }else{ id = res; } if(id != oldID){ this._removeRenderItem(oldID); } })); }else{ // creation canceled // cleanup items list this._removeRenderItem(s.id); } } else if(e.completed){ // Inject new properties in data store item // and apply data changes this._setItemStoreState(storeItem, "storing"); store.put(storeItem); }else{ e.item.startTime = this._editStartTimeSave; e.item.endTime = this._editEndTimeSave; } } }, _removeRenderItem: function(id){ var owner = this._getTopOwner(); var items = owner.get("items"); var l = items.length; var found = false; for(var i=l-1; i>=0; i--){ if(items[i].id == id){ items.splice(i, 1); found = true; break; } } this._cleanItemStoreState(id); if(found){ owner.set("items", items); //force a complete relayout this.invalidateLayout(); } }, onItemEditEnd: function(e){ // summary: // Event dispatched when the item is leaving the editing mode. // tags: // protected }, _createItemEditEvent: function(){ // tags: // private var e = { cancelable: true, bubbles: false, __defaultPrevent: false }; e.preventDefault = function(){ this.__defaultPrevented = true; }; e.isDefaultPrevented = function(){ return this.__defaultPrevented; }; return e; }, _startItemEditingGesture: function(dates, editKind, eventSource, e){ // summary: // Starts the editing gesture. // date: Date[] // The reference dates (at least one). // editKind: String // Kind of edit: "resizeBoth", "resizeStart", "resizeEnd" or "move". // eventSource: String // "mouse", "keyboard", "touch" // e: Event // The event at the origin of the editing gesture. // tags: // protected var p = this._edProps; if(!p || p.editedItem == null){ return; } this._editingGesture = true; var item = p.editedItem; p.editKind = editKind; this._onItemEditBeginGesture(this.__fixEvt(lang.mixin(this._createItemEditEvent(), { item: item, storeItem: p.storeItem, startTime: item.startTime, endTime: item.endTime, editKind: editKind, rendererKind: p.rendererKind, triggerEvent: e, dates: dates, eventSource: eventSource }))); p.itemBeginDispatched = true; }, _onItemEditBeginGesture: function(e){ // tags: // private var p = this._edProps; var item = p.editedItem; var dates = e.dates; p.editingTimeFrom = []; p.editingTimeFrom[0] = dates[0]; p.editingItemRefTime = []; p.editingItemRefTime[0] = this.newDate(p.editKind == "resizeEnd" ? item.endTime : item.startTime); if (p.editKind == "resizeBoth"){ p.editingTimeFrom[1] = dates[1]; p.editingItemRefTime[1] = this.newDate(item.endTime); } var cal = this.renderData.dateModule; p.inViewOnce = this._isItemInView(item); if(p.rendererKind == "label" || this.roundToDay){ p._itemEditBeginSave = this.newDate(item.startTime); p._itemEditEndSave = this.newDate(item.endTime); } p._initDuration = cal.difference(item.startTime, item.endTime, item.allDay?"day":"millisecond"); this._dispatchCalendarEvt(e, "onItemEditBeginGesture"); if (!e.isDefaultPrevented()){ if (e.eventSource == "mouse"){ var cursor = e.editKind=="move"?"move":this.resizeCursor; p.editLayer = domConstruct.create("div", { style: "position: absolute; left:0; right:0; bottom:0; top:0; z-index:30; tabIndex:-1; background-image:url('"+this._blankGif+"'); cursor: "+cursor, onresizestart: function(e){return false;}, onselectstart: function(e){return false;} }, this.domNode); p.editLayer.focus(); } } }, onItemEditBeginGesture: function(e){ // summary: // Event dispatched when an editing gesture is beginning. // e: __itemEditingEventArgs // The editing event. // tags: // callback }, _waDojoxAddIssue: function(d, unit, steps){ // summary: // Workaround an issue of dojox.date.XXXXX.date.add() function // that does not support the subtraction of time correctly (normalization issues). // d: Date // Reference date. // unit: String // Unit to add. // steps: Integer // Number of units to add. // tags: // protected var cal = this.renderData.dateModule; if(this._calendar != "gregorian" && steps < 0){ var gd = d.toGregorian(); gd = date.add(gd, unit, steps); return new this.renderData.dateClassObj(gd); }else{ return cal.add(d, unit, steps); } }, _computeItemEditingTimes: function(item, editKind, rendererKind, times, eventSource){ // tags: // private var cal = this.renderData.dateModule; var p = this._edProps; if(editKind == "move"){ var diff = cal.difference(p.editingTimeFrom[0], times[0], "millisecond"); times[0] = this._waDojoxAddIssue(p.editingItemRefTime[0], "millisecond", diff); } return times; }, _moveOrResizeItemGesture: function(dates, eventSource, e, subColumn){ // summary: // Moves or resizes an item. // dates: Date[] // The reference dates. // editKind: String // Kind of edit: "resizeStart", "resizeEnd", "resizeBoth" or "move". // eventSource: String // "mouse", "keyboard", "touch" // e: Event // The event at the origin of the editing gesture. // subColumn: String // The sub column value, if any, or null. // tags: // private if(!this._isEditing || dates[0] == null){ return; } var p = this._edProps; var item = p.editedItem; var rd = this.renderData; var cal = rd.dateModule; var editKind = p.editKind; var newTimes = [dates[0]]; if(editKind == "resizeBoth"){ newTimes[1] = dates[1]; } newTimes = this._computeItemEditingTimes(item, p.editKind, p.rendererKind, newTimes, eventSource); var newTime = newTimes[0]; // usual use case var moveOrResizeDone = false; var oldStart = lang.clone(item.startTime); var oldEnd = lang.clone(item.endTime); var oldSubColumn = item.subColumn; // swap cannot used using keyboard as a gesture is made of one single change (loss of start/end context). var allowSwap = p.eventSource == "keyboard" ? false : this.allowStartEndSwap; // Update the Calendar with the edited value. if(editKind == "move"){ if(subColumn != null && item.subColumn != subColumn && this.allowSubColumnMove){ // TODO abstract change? item.subColumn = subColumn; // refresh the other properties that depends on this one (especially cssClass) var store = this.get("store"); var storeItem = this.renderItemToItem(item, store); lang.mixin(item, this.itemToRenderItem(storeItem, store)); moveOrResizeDone = true; } if(cal.compare(item.startTime, newTime) != 0){ var duration = cal.difference(item.startTime, item.endTime, "millisecond"); item.startTime = this.newDate(newTime); item.endTime = cal.add(item.startTime, "millisecond", duration); moveOrResizeDone = true; } }else if(editKind == "resizeStart"){ if(cal.compare(item.startTime, newTime) != 0){ if(cal.compare(item.endTime, newTime) != -1){ item.startTime = this.newDate(newTime); }else{ // swap detected if(allowSwap){ item.startTime = this.newDate(item.endTime); item.endTime = this.newDate(newTime); p.editKind = editKind = "resizeEnd"; if(eventSource == "touch"){ // invert touches as well! p.resizeEndTouchIndex = p.resizeStartTouchIndex; p.resizeStartTouchIndex = -1; } }else{ // block the swap but keep the time of day item.startTime = this.newDate(item.endTime); item.startTime.setHours(newTime.getHours()); item.startTime.setMinutes(newTime.getMinutes()); item.startTime.setSeconds(newTime.getSeconds()); } } moveOrResizeDone = true; } }else if(editKind == "resizeEnd"){ if(cal.compare(item.endTime, newTime) != 0){ if(cal.compare(item.startTime, newTime) != 1){ item.endTime = this.newDate(newTime); }else{ // swap detected if(allowSwap){ item.endTime = this.newDate(item.startTime); item.startTime = this.newDate(newTime); p.editKind = editKind = "resizeStart"; if(eventSource == "touch"){ // invert touches as well! p.resizeStartTouchIndex = p.resizeEndTouchIndex; p.resizeEndTouchIndex = -1; } }else{ // block the swap but keep the time of day item.endTime = this.newDate(item.startTime); item.endTime.setHours(newTime.getHours()); item.endTime.setMinutes(newTime.getMinutes()); item.endTime.setSeconds(newTime.getSeconds()); } } moveOrResizeDone = true; } }else if(editKind == "resizeBoth"){ moveOrResizeDone = true; var start = this.newDate(newTime); var end = this.newDate(newTimes[1]); if(cal.compare(start, end) != -1){ // swap detected if(allowSwap){ var t = start; start = end; end = t; }else{ // as both ends are moved, the simple way is to forbid the move gesture. moveOrResizeDone = false; } } if(moveOrResizeDone){ item.startTime = start; item.endTime = end; } }else{ return false; } if(!moveOrResizeDone){ return false; } var evt = lang.mixin(this._createItemEditEvent(), { item: item, storeItem: p.storeItem, startTime: item.startTime, endTime: item.endTime, editKind: editKind, rendererKind: p.rendererKind, triggerEvent: e, eventSource: eventSource }); // trigger snapping, rounding, minimal duration, boundaries checks etc. if(editKind == "move"){ this._onItemEditMoveGesture(evt); }else{ this._onItemEditResizeGesture(evt); } // prevent invalid range if(cal.compare(item.startTime, item.endTime) == 1){ var tmp = item.startTime; item.startTime = item.endTime; item.endTime = tmp; } moveOrResizeDone = oldSubColumn != item.subColumn || cal.compare(oldStart, item.startTime) != 0 || cal.compare(oldEnd, item.endTime) != 0; if(!moveOrResizeDone){ return false; } this._layoutRenderers(this.renderData); if(p.liveLayout && p.secItem != null){ p.secItem.startTime = item.startTime; p.secItem.endTime = item.endTime; this._secondarySheet._layoutRenderers(this._secondarySheet.renderData); }else if(p.ownerItem != null && this.owner.liveLayout){ p.ownerItem.startTime = item.startTime; p.ownerItem.endTime = item.endTime; this.owner._layoutRenderers(this.owner.renderData); } return true; }, _findRenderItem: function(id, list){ // tags: // private list = list || this.renderData.items; for(var i=0; i= this._DAY_IN_MILLISECONDS && !this.allowResizeLessThan24H; this.ensureMinimalDuration(this.renderData, e.item, minimalDay ? "day" : this.minDurationUnit, minimalDay ? 1 : this.minDurationSteps, e.editKind); if(!p.inViewOnce){ p.inViewOnce = this._isItemInView(e.item); } // to prevent strange behaviors use constraint in items already fully in view. if(p.inViewOnce && this.stayInView){ this._ensureItemInView(e.item); } } }, onItemEditResizeGesture: function(e){ // summary: // Event dispatched during a resize editing gesture. // e: __itemEditingEventArgs // The editing event. // tags: // callback }, _endItemEditingGesture: function(/*String*/eventSource, /*Event*/e){ // tags: // protected if(!this._isEditing){ return; } this._editingGesture = false; var p = this._edProps; var item = p.editedItem; p.itemBeginDispatched = false; this._onItemEditEndGesture(lang.mixin(this._createItemEditEvent(), { item: item, storeItem: p.storeItem, startTime: item.startTime, endTime: item.endTime, editKind: p.editKind, rendererKind: p.rendererKind, triggerEvent: e, eventSource: eventSource })); }, _onItemEditEndGesture: function(e){ // tags: // private var p = this._edProps; delete p._itemEditBeginSave; delete p._itemEditEndSave; this._dispatchCalendarEvt(e, "onItemEditEndGesture"); if (!e.isDefaultPrevented()){ if(p.editLayer){ if(has("ie")){ p.editLayer.style.cursor = "default"; } setTimeout(lang.hitch(this, function(){ if(this.domNode){ // for unit tests this.domNode.focus(); p.editLayer.parentNode.removeChild(p.editLayer); p.editLayer = null; } }), 10); } } }, onItemEditEndGesture: function(e){ // summary: // Event dispatched at the end of an editing gesture. // e: __itemEditingEventArgs // The editing event. // tags: // callback }, ensureMinimalDuration: function(renderData, item, unit, steps, editKind){ // summary: // During the resize editing gesture, ensures that the item has the specified minimal duration. // renderData: Object // The render data. // item: Object // The edited item. // unit: String // The unit used to define the minimal duration. // steps: Integer // The number of time units. // editKind: String // The edit kind: "resizeStart" or "resizeEnd". var minTime; var cal = renderData.dateModule; if(editKind == "resizeStart"){ minTime = cal.add(item.endTime, unit, -steps); if(cal.compare(item.startTime, minTime) == 1){ item.startTime = minTime; } } else { minTime = cal.add(item.startTime, unit, steps); if(cal.compare(item.endTime, minTime) == -1){ item.endTime = minTime; } } }, // doubleTapDelay: Integer // The maximum delay between two taps needed to trigger an "itemDoubleClick" event, in touch context. doubleTapDelay: 300, // snapUnit: String // The unit of the snapping to apply during the editing of an event. // "day", "hour" and "minute" are valid values. snapUnit: "minute", // snapSteps: Integer // The number of units used to compute the snapping of the edited item. snapSteps: 15, // minDurationUnit: "String" // The unit used to define the minimal duration of the edited item. // "day", "hour" and "minute" are valid values. minDurationUnit: "hour", // minDurationSteps: Integer // The number of units used to define the minimal duration of the edited item. minDurationSteps: 1, // liveLayout: Boolean // If true, all the events are laid out during the editing gesture. If false, only the edited event is laid out. liveLayout: false, // stayInView: Boolean // Specifies during editing, if the item is already in view, if the item must stay in the time range defined by the view or not. stayInView: true, // allowStartEndSwap: Boolean // Specifies if the start and end time of an item can be swapped during an editing gesture. Note that using the keyboard this property is ignored. allowStartEndSwap: true, // allowResizeLessThan24H: Boolean // If an event has a duration greater than 24 hours, indicates if using a resize gesture, it can be resized to last less than 24 hours. // This flag is usually used when two different kind of renderers are used (MatrixView) to prevent changing the kind of renderer during an editing gesture. allowResizeLessThan24H: false, // allowSubColumnMove: Boolean // If several sub columns are displayed, indicated if the data item can be reassigned to another sub column by an editing gesture. allowSubColumnMove: true }); });