source: Dev/trunk/src/client/dojox/calendar/ViewBase.js @ 485

Last change on this file since 485 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 75.5 KB
Line 
1define([
2        "dojo/_base/declare",
3        "dojo/_base/lang",
4        "dojo/_base/array",
5        "dojo/_base/window",
6        "dojo/_base/event",
7        "dojo/_base/html",
8        "dojo/sniff",
9        "dojo/query",
10        "dojo/dom",
11        "dojo/dom-style",
12        "dojo/dom-class",
13        "dojo/dom-construct",
14        "dojo/dom-geometry",
15        "dojo/on",
16        "dojo/date",
17        "dojo/date/locale",
18        "dojo/when",
19        "dijit/_WidgetBase",
20        "dojox/widget/_Invalidating",
21        "dojox/widget/Selection",
22        "dojox/calendar/time",
23        "./StoreMixin"],
24
25        function(
26                declare,
27                lang,
28                arr,
29                win,
30                event,
31                html,
32                has,
33                query,
34                dom,
35                domStyle,
36                domClass,
37                domConstruct,
38                domGeometry,
39                on,
40                date,
41                locale,
42                when,
43                _WidgetBase,
44                _Invalidating,
45                Selection,
46                timeUtil,
47                StoreMixin){
48       
49        /*=====
50        var __GridClickEventArgs = {
51                // summary:
52                //              The event dispatched when the grid is clicked or double-clicked.
53                // date: Date
54                //              The start of the previously displayed time interval, if any.
55                // triggerEvent: Event
56                //              The event at the origin of this event.
57        };
58        =====*/
59       
60        /*=====
61        var __ItemMouseEventArgs = {
62                // summary:
63                //              The event dispatched when an item is clicked, double-clicked or context-clicked.
64                // item: Object
65                //              The item clicked.
66                // renderer: dojox/calendar/_RendererMixin
67                //              The item renderer clicked.
68                // triggerEvent: Event
69                //              The event at the origin of this event.
70        };
71        =====*/
72       
73        /*=====
74        var __itemEditingEventArgs = {
75                // summary:
76                //              An item editing event.
77                // item: Object
78                //              The render item that is being edited. Set/get the startTime and/or endTime properties to customize editing behavior.
79                // storeItem: Object
80                //              The real data from the store. DO NOT change properties, but you may use properties of this item in the editing behavior logic.
81                // editKind: String
82                //              Kind of edit: "resizeBoth", "resizeStart", "resizeEnd" or "move".
83                // dates: Date[]
84                //              The computed date/time of the during the event editing. One entry per edited date (touch use case).
85                // startTime: Date?
86                //              The start time of data item.
87                // endTime: Date?
88                //              The end time of data item.
89                // sheet: String
90                //              For views with several sheets (columns view for example), the sheet when the event occurred.
91                // source: dojox/calendar/ViewBase
92                //              The view where the event occurred.
93                // eventSource: String
94                //              The device that triggered the event. This property can take the following values:
95                //
96                //              - "mouse",
97                //              - "keyboard",
98                //              - "touch"               
99                // triggerEvent: Event
100                //              The event at the origin of this event.
101        };
102        =====*/
103       
104        /*=====
105        var __rendererLifecycleEventArgs = {
106                // summary:
107                //              An renderer lifecycle event.
108                // renderer: Object
109                //              The renderer.           
110                // source: dojox/calendar/ViewBase
111                //              The view where the event occurred.
112                // item:Object?
113                //              The item that will be displayed by the renderer for the "rendererCreated" and "rendererReused" events.
114        };
115        =====*/
116
117        return declare("dojox.calendar.ViewBase", [_WidgetBase, StoreMixin, _Invalidating, Selection], {
118               
119                // summary:
120                //              The dojox.calendar.ViewBase widget is the base of calendar view widgets
121               
122                // datePackage: Object
123                //              JavaScript namespace to find Calendar routines. Uses Gregorian Calendar routines at dojo.date by default.
124                datePackage: date,
125               
126                _calendar: "gregorian",
127               
128                // viewKind: String
129                //              Kind of the view. Used by the calendar widget to determine how to configure the view.
130                viewKind: null,
131               
132                // _layoutStep: [protected] Integer
133                //              The number of units displayed by a visual layout unit (i.e. a column or a row)
134                _layoutStep: 1,
135               
136                // _layoutStep: [protected] Integer
137                //              The unit displayed by a visual layout unit (i.e. a column or a row)
138                _layoutUnit: "day",
139               
140                // resizeCursor: String
141                //              CSS value to apply to the cursor while resizing an item renderer.
142                resizeCursor: "n-resize",
143               
144                // formatItemTimeFunc: Function
145                //              Optional function to format the time of day of the item renderers.
146                //              The function takes the date and render data object as arguments and returns a String.
147                formatItemTimeFunc: null,
148               
149                _cssDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
150                               
151                _getFormatItemTimeFuncAttr: function(){
152                        if(this.owner != null){
153                                return this.owner.get("formatItemTimeFunc");
154                        }
155                        return this.formatItemTimeFunc;                 
156                },
157               
158                // The listeners added by the view itself.
159                _viewHandles: null,
160               
161                // doubleTapDelay: Integer
162                //              The maximum time amount in milliseconds between to touchstart events that trigger a double-tap event. 
163                doubleTapDelay: 300,
164               
165                constructor: function(/*Object*/ args){
166                        args = args || {};
167                       
168                        this._calendar = args.datePackage ? args.datePackage.substr(args.datePackage.lastIndexOf(".")+1) : this._calendar;
169                        this.dateModule = args.datePackage ? lang.getObject(args.datePackage, false) : date;
170                        this.dateClassObj = this.dateModule.Date || Date;
171                        this.dateLocaleModule = args.datePackage ? lang.getObject(args.datePackage+".locale", false) : locale;
172                       
173                        this.rendererPool = [];
174                        this.rendererList = [];
175                        this.itemToRenderer = {};
176                        this._viewHandles = [];
177                },
178               
179                destroy: function(preserveDom){
180                        // renderers
181                        while(this.rendererList.length > 0){
182                                this._destroyRenderer(this.rendererList.pop());
183                        }                       
184                        for(var kind in this._rendererPool){
185                                var pool = this._rendererPool[kind];
186                                if(pool){
187                                        while(pool.length > 0){
188                                                this._destroyRenderer(pool.pop());
189                                        }
190                                }
191                        }
192                       
193                        while(this._viewHandles.length > 0){
194                                this._viewHandles.pop().remove();
195                        }
196               
197                        this.inherited(arguments);
198                },
199               
200               
201                resize: function(changeSize){
202                        // summary:
203                        //              Function to call when the view is resized.
204                        //              If the view is in a Dijit container or in a Dojo mobile container, it will be automatically called.
205                        //              On other use cases, this method must called when the window is resized and/or when the orientation has changed.
206                        if(changeSize){
207                                domGeometry.setMarginBox(this.domNode, changeSize);
208                        }
209                },
210               
211                _getTopOwner: function(){
212                        // summary:
213                        //              Returns the top owner: the calendar or the parent view.
214                        var p = this;
215                        while(p.owner != undefined){
216                                p = p.owner;
217                        }
218                        return p;
219                },
220               
221                _createRenderData: function(){
222                        // summary:
223                        //              Creates the object that contains all the data needed to render this widget.
224                        // tags:
225                        //              protected
226                },
227               
228                _validateProperties: function(){
229                        // summary:
230                        //              Validates the widget properties before the rendering pass.
231                        // tags:
232                        //              protected
233                },
234
235                _setText: function(node, text, allowHTML){
236                        // summary:
237                        //              Creates a text node under the parent node after having removed children nodes if any.
238                        // node: Node
239                        //              The node that will contain the text node.
240                        // text: String
241                        //              The text to set to the text node.
242                        if(text != null){                       
243                                if(!allowHTML && node.hasChildNodes()){
244                                        // span > textNode
245                                        node.childNodes[0].childNodes[0].nodeValue = text;
246                                }else{                                                                                         
247                       
248                                        while(node.hasChildNodes()){
249                                                node.removeChild(node.lastChild);
250                                        }                               
251                       
252                                        var tNode = win.doc.createElement("span");
253                                        if(has("dojo-bidi")){
254                                                this.applyTextDir(tNode, text);
255                                        }
256                                       
257                                        if(allowHTML){
258                                                tNode.innerHTML = text;
259                                        }else{
260                                                tNode.appendChild(win.doc.createTextNode(text));
261                                        }
262                                        node.appendChild(tNode);
263                                }
264                        }
265                },
266               
267                isAscendantHasClass: function(node, ancestor, className){
268                        // summary:
269                        //              Determines if a node has an ascendant node that has the css class specified.
270                        // node: Node
271                        //              The DOM node.
272                        // ancestor: Node
273                        //              The ancestor node used to limit the search in hierarchy.
274                        // className: String
275                        //              The css class name.
276                        // returns: Boolean
277                       
278                        while(node != ancestor && node != document){
279                               
280                                if(domClass.contains(node, className)){
281                                        return true;
282                                }
283                               
284                                node = node.parentNode;
285                        }
286                        return false;
287                },
288               
289                isWeekEnd: function(date){
290                        // summary:
291                        //              Determines whether the specified date is a week-end.
292                        //              This method is using dojo.date.locale.isWeekend() method as
293                        //              dojox.date.XXXX calendars are not supporting this method.
294                        // date: Date
295                        //              The date to test. 
296                        return locale.isWeekend(date);
297                },
298               
299                getWeekNumberLabel: function(date){
300                        // summary:
301                        //              Returns the week number string from dojo.date.locale.format() method as
302                        //              dojox.date.XXXX calendar are not supporting the "w" pattern.
303                        // date: Date
304                        //              The date to format.
305                        if(date.toGregorian){
306                                date = date.toGregorian();
307                        }
308                        return locale.format(date, {
309                                selector: "date",
310                                datePattern: "w"});
311                },
312               
313                floorToDay: function(date, reuse){
314                        // summary:
315                        //              Floors the specified date to the start of day.
316                        // date: Date
317                        //              The date to floor.
318                        // reuse: Boolean
319                        //              Whether use the specified instance or create a new one. Default is false.
320                        // returns: Date
321                        return timeUtil.floorToDay(date, reuse, this.dateClassObj);
322                },
323               
324                floorToMonth: function(date, reuse){
325                        // summary:
326                        //              Floors the specified date to the start of the date's month.
327                        // date: Date
328                        //              The date to floor.
329                        // reuse: Boolean
330                        //              Whether use the specified instance or create a new one. Default is false.
331                        // returns: Date
332                        return timeUtil.floorToMonth(date, reuse, this.dateClassObj);
333                },
334               
335                               
336                floorDate: function(date, unit, steps, reuse){
337                        // summary:
338                        //              floors the date to the unit.
339                        // date: Date
340                        //              The date/time to floor.
341                        // unit: String
342                        //              The unit. Valid values are "minute", "hour", "day".
343                        // steps: Integer
344                        //              For "day" only 1 is valid.
345                        // reuse: Boolean
346                        //              Whether use the specified instance or create a new one. Default is false.                       
347                        // returns: Date
348                        return timeUtil.floor(date, unit, steps, reuse, this.dateClassObj);
349                },
350
351                isToday: function(date){
352                        // summary:
353                        //              Returns whether the specified date is in the current day.
354                        // date: Date
355                        //              The date to test.
356                        // renderData: Object
357                        //              The current renderData
358                        // returns: Boolean
359                        return timeUtil.isToday(date, this.dateClassObj);
360                },
361               
362                isStartOfDay: function(d){
363                        // summary:
364                        //              Tests if the specified date represents the starts of day.
365                        // d:Date
366                        //              The date to test.
367                        // returns: Boolean
368                        return timeUtil.isStartOfDay(d, this.dateClassObj, this.dateModule);
369                },
370               
371                isOverlapping: function(renderData, start1, end1, start2, end2, includeLimits){
372                        // summary:
373                        //              Computes if the first time range defined by the start1 and end1 parameters
374                        //              is overlapping the second time range defined by the start2 and end2 parameters.
375                        // renderData: Object
376                        //              The render data.
377                        // start1: Date
378                        //              The start time of the first time range.
379                        // end1: Date
380                        //              The end time of the first time range.
381                        // start2: Date
382                        //              The start time of the second time range.
383                        // end2: Date
384                        //              The end time of the second time range.
385                        // includeLimits: Boolean
386                        //              Whether include the end time or not.
387                        // returns: Boolean
388                        if(start1 == null || start2 == null || end1 == null || end2 == null){
389                                return false;
390                        }
391                       
392                        var cal = renderData.dateModule;
393                       
394                        if(includeLimits){
395                                if(cal.compare(start1, end2) == 1 || cal.compare(start2, end1) == 1){
396                                        return false;
397                                }                                       
398                        }else if(cal.compare(start1, end2) != -1 || cal.compare(start2, end1) != -1){
399                                return false;
400                        }
401                        return true;
402                },                       
403                         
404                computeRangeOverlap: function(renderData, start1, end1, start2, end2, includeLimits){
405                        // summary:
406                        //              Computes the overlap time range of the time ranges.
407                        //              Returns a vector of Date with at index 0 the start time and at index 1 the end time.
408                        // renderData: Object.
409                        //              The render data.
410                        // start1: Date
411                        //              The start time of the first time range.
412                        // end1: Date
413                        //              The end time of the first time range.
414                        // start2: Date
415                        //              The start time of the second time range.
416                        // end2: Date
417                        //              The end time of the second time range.
418                        // includeLimits: Boolean
419                        //              Whether include the end time or not.
420                        // returns: Date[]
421                        var cal = renderData.dateModule;
422                       
423                        if(start1 == null || start2 == null || end1 == null || end2 == null){
424                                return null;
425                        }
426                       
427                        var comp1 = cal.compare(start1, end2);
428                        var comp2 = cal.compare(start2, end1);
429                       
430                        if(includeLimits){
431                               
432                                if(comp1 == 0 || comp1 == 1 || comp2 == 0 || comp2 == 1){
433                                        return null;
434                                }
435                        } else if(comp1 == 1 || comp2 == 1){
436                                return null;
437                        }
438                       
439                        return [
440                                this.newDate(cal.compare(start1, start2)>0 ? start1: start2, renderData),
441                                this.newDate(cal.compare(end1, end2)>0 ? end2: end1, renderData)
442                        ];
443                },
444               
445                isSameDay : function(date1, date2){
446                        // summary:
447                        //              Tests if the specified dates are in the same day.
448                        // date1: Date
449                        //              The first date.
450                        // date2: Date
451                        //              The second date.
452                        // returns: Boolean
453                        if(date1 == null || date2 == null){
454                                return false;
455                        }
456               
457                        return date1.getFullYear() == date2.getFullYear() &&
458                                                 date1.getMonth() == date2.getMonth() &&
459                                                 date1.getDate() == date2.getDate();
460                         
461                },
462               
463                computeProjectionOnDate: function(renderData, refDate, date, max){
464                        // summary:
465                        //              Computes the time to pixel projection in a day.
466                        // renderData: Object
467                        //              The render data.
468                        // refDate: Date
469                        //              The reference date that defines the destination date.
470                        // date: Date
471                        //              The date to project.
472                        // max: Integer
473                        //              The size in pixels of the representation of a day.
474                        // tags:
475                        //              protected
476                        // returns: Number
477
478                        var cal = renderData.dateModule;
479                       
480                        if(max <= 0 || cal.compare(date, refDate) == -1){
481                                return 0;
482                        }
483                       
484                        var referenceDate = this.floorToDay(refDate, false, renderData);
485
486                        if(date.getDate() != referenceDate.getDate()){
487                                if(date.getMonth() == referenceDate.getMonth()){
488                                        if(date.getDate() < referenceDate.getDate()){
489                                                return 0;
490                                        } else if(date.getDate() > referenceDate.getDate()){
491                                                return max;
492                                        }
493                                }else{
494                                        if(date.getFullYear() == referenceDate.getFullYear()){
495                                                if(date.getMonth() < referenceDate.getMonth()){
496                                                        return 0;
497                                                } else if(date.getMonth() > referenceDate.getMonth()){
498                                                        return max;
499                                                }                                               
500                                        }else{
501                                                if(date.getFullYear() < referenceDate.getFullYear()){
502                                                        return 0;
503                                                } else if(date.getFullYear() > referenceDate.getFullYear()){
504                                                        return max;
505                                                }
506                                        }
507                                }
508                        }
509
510                        var res;
511
512                        if(this.isSameDay(refDate, date)){
513                               
514                                var d = lang.clone(refDate);
515                                var minTime = 0;
516                               
517                                if(renderData.minHours != null && renderData.minHours != 0){
518                                        d.setHours(renderData.minHours);
519                                        minTime = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
520                                }
521                               
522                                d = lang.clone(refDate);
523                               
524                                var maxTime;
525                                if(renderData.maxHours == null || renderData.maxHours == 24){
526                                        maxTime = 86400; // 24h x 60m x 60s
527                                }else{
528                                        d.setHours(renderData.maxHours);
529                                        maxTime = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
530                                }
531                               
532                                //precision is the second
533                                //use this API for daylight time issues.
534                                var delta = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() - minTime;
535                               
536                                if(delta < 0){
537                                        return 0;
538                                }
539                                if(delta > maxTime){
540                                        return max;
541                                }
542
543                                res = (max * delta)/(maxTime - minTime);
544                               
545                        }else{
546                               
547                                if(date.getDate() < refDate.getDate() &&
548                                                date.getMonth() == refDate.getMonth()){
549                                        return 0;
550                                }
551                               
552                                var d2 = this.floorToDay(date);
553                                var dp1 = renderData.dateModule.add(refDate, "day", 1);
554                                dp1 = this.floorToDay(dp1, false, renderData);
555                               
556                                if(cal.compare(d2, refDate) == 1 && cal.compare(d2, dp1) == 0 || cal.compare(d2, dp1) == 1){
557                                        res =   max;
558                                }else{
559                                        res = 0;
560                                }
561                        }
562                               
563                        return res;
564                },     
565               
566                getTime: function(e, x, y, touchIndex){
567                        // summary:
568                        //              Returns the time displayed at the specified point by this component.
569                        // e: Event
570                        //              Optional mouse event.
571                        // x: Number
572                        //              Position along the x-axis with respect to the sheet container used if event is not defined.
573                        // y: Number
574                        //              Position along the y-axis with respect to the sheet container (scroll included) used if event is not defined.
575                        // touchIndex: Integer
576                        //              If parameter 'e' is not null and a touch event, the index of the touch to use.
577                        // returns: Date
578                        return null;
579                },
580               
581                newDate: function(obj){
582                        // summary:
583                        //              Creates a new Date object.
584                        // obj: Object
585                        //              This object can have several values:
586                        //
587                        //              - the time in milliseconds since gregorian epoch.
588                        //              - a Date instance
589                        // returns: Date
590                        return timeUtil.newDate(obj, this.dateClassObj);                       
591                },
592               
593                _isItemInView: function(item){
594                        // summary:
595                        //              Computes whether the specified item is entirely in the view or not.
596                        // item: Object
597                        //              The item to test
598                        // returns: Boolean     
599                        var rd = this.renderData;
600                        var cal = rd.dateModule;
601                       
602                        if(cal.compare(item.startTime, rd.startTime) == -1){
603                                return false;
604                        }                       
605                       
606                        return cal.compare(item.endTime, rd.endTime) != 1;
607                },
608               
609                _ensureItemInView: function(item){
610                        // summary:
611                        //              If needed, moves the item to be entirely in view.
612                        // item: Object
613                        //              The item to test
614                        // returns: Boolean
615                        //              Whether the item has been moved to be in view or not.
616                        // tags:
617                        //              protected
618
619                        var rd = this.renderData;
620                        var cal = rd.dateModule;
621                       
622                        var duration = Math.abs(cal.difference(item.startTime, item.endTime, "millisecond"));
623                        var fixed = false;
624                       
625                        if(cal.compare(item.startTime, rd.startTime) == -1){
626                                item.startTime = rd.startTime;
627                                item.endTime = cal.add(item.startTime, "millisecond", duration);
628                                fixed = true;
629                        }else if(cal.compare(item.endTime, rd.endTime) == 1){
630                                item.endTime = rd.endTime;
631                                item.startTime = cal.add(item.endTime, "millisecond", -duration);
632                                fixed = true;
633                        }                       
634                        return fixed;
635                },
636               
637                /////////////////////////////////////////////////////////
638                //
639                // Scrollable
640                //
641                /////////////////////////////////////////////////////////
642                               
643                // scrollable: Boolean
644                //              Indicates whether the view can be scrolled or not.
645                scrollable: true,
646               
647                // autoScroll: Boolean
648                //              Indicates whether the view can be scrolled automatically.
649                //              Auto scrolling is used when moving focus to a non visible renderer using keyboard
650                //              and while editing an item.
651                autoScroll: true,                               
652               
653                _autoScroll: function(gx, gy, orientation){
654                        // summary:
655                        //              Starts or stops the auto scroll according to the mouse cursor position during an item editing.
656                        // gx: Integer
657                        //              The position of the mouse cursor along the x-axis.
658                        // gy: Integer
659                        //              The position of the mouse cursor along the y-axis.                     
660                        // tags:
661                        //              extension
662
663                        return false;
664                },
665                       
666                // scrollMethod: String
667                //              Method used to scroll the view, for example the scroll of column view.
668                //              Valid value are:
669                //
670                //              - "auto": let the view decide (default),
671                //              - "css": use css 3d transform,
672                //              - "dom": use the scrollTop property.
673                scrollMethod: "auto",
674               
675                _setScrollMethodAttr: function(value){
676                        if(this.scrollMethod != value){
677                                this.scrollMethod = value;
678                               
679                                // reset
680                                if(this._domScroll !== undefined){
681                                        if(this._domScroll){
682                                                domStyle.set(this.sheetContainer, this._cssPrefix+"transform", "translateY(0px)");
683                                        }else{
684                                                this.scrollContainer.scrollTop = 0;
685                                        }
686                                }
687                               
688                                delete this._domScroll;
689                                var pos = this._getScrollPosition();
690                                delete this._scrollPos;
691                               
692                                this._setScrollPosition(pos);
693                        }
694                       
695                },
696               
697                _startAutoScroll: function(step){
698                        // summary:
699                        //              Starts the auto scroll of the view (if it's scrollable). Used only during editing.
700                        // tags:
701                        //              protected
702                        var sp = this._scrollProps;
703                        if(!sp){
704                                sp = this._scrollProps = {};
705                        }
706                               
707                        sp.scrollStep = step;
708                       
709                        if (!sp.isScrolling){
710                                sp.isScrolling = true;
711                                sp.scrollTimer = setInterval(lang.hitch(this, this._onScrollTimer_tick), 10);
712                        }               
713                },
714                               
715                _stopAutoScroll: function(){
716                        // summary:
717                        //              Stops the auto scroll of the view (if it's scrollable). Used only during editing.
718                        // tags:
719                        //              protected
720                        var sp = this._scrollProps;
721                       
722                        if (sp && sp.isScrolling) {
723                                clearInterval(sp.scrollTimer);
724                                sp.scrollTimer = null;
725                        }
726                        this._scrollProps = null;
727                },
728               
729                _onScrollTimer_tick: function(pos){
730                },
731               
732                _scrollPos: 0,
733               
734                getCSSPrefix: function(){
735                        // summary:
736                        //              Utility method that return the specific CSS prefix
737                        //              for non standard CSS properties. Ex: -moz-border-radius.
738                        if(has("ie")){
739                                return "-ms-";
740                        }
741                        if(has("webkit")){
742                                return "-webkit-";
743                        }
744                        if(has("mozilla")){
745                                return "-moz-";
746                        }
747                        if(has("opera")){
748                                return "-o-";
749                        }
750            return "";
751                },                             
752               
753                _setScrollPosition: function(pos){
754                        // summary:
755                        //              Sets the scroll position (if the view is scrollable), using the scroll method defined.
756                        // tags:
757                        //              protected
758
759                        if(this._scrollPos == pos){
760                                return;
761                        }
762                       
763                        // determine scroll method once.
764                        if(this._domScroll === undefined){
765                       
766                                var sm = this.get("scrollMethod");
767                                if(sm === "auto"){                                     
768                                        this._domScroll = !has("ios") && !has("android") && !has("webkit");
769                                }else{
770                                        this._domScroll = sm === "dom";
771                                }
772                        }
773                       
774                        var containerSize = domGeometry.getMarginBox(this.scrollContainer);
775                        var sheetSize = domGeometry.getMarginBox(this.sheetContainer);
776                        var max = sheetSize.h - containerSize.h;
777                       
778                        if(pos < 0){
779                                pos = 0;
780                        }else if(pos > max){
781                                pos = max;
782                        }
783                       
784                        this._scrollPos = pos;
785                                                                                               
786                        if(this._domScroll){                           
787                                this.scrollContainer.scrollTop = pos;                           
788                        }else{                 
789                                if(!this._cssPrefix){
790                                        this._cssPrefix =  this.getCSSPrefix();
791                                }
792                                domStyle.set(this.sheetContainer, this._cssPrefix+"transform", "translateY(-"+pos+"px)");
793                        }
794                },
795               
796                _getScrollPosition: function(){
797                        // summary:
798                        //              Returns the scroll position (if the view is scrollable), using the scroll method defined.
799                        // tags:
800                        //              protected
801
802                        return this._scrollPos;
803                },
804               
805                scrollView: function(dir){
806                        // summary:
807                        //              If the view is scrollable, scrolls it to the specified direction.
808                        // dir: Integer
809                        //              Direction of the scroll. Valid values are -1 and 1.
810                        // tags:
811                        //              extension
812                },
813               
814                ensureVisibility: function(start, end, margin, visibilityTarget, duration){
815                        // summary:
816                        //              Scrolls the view if the [start, end] time range is not visible or only partially visible.
817                        // start: Date
818                        //              Start time of the range of interest.
819                        // end: Date
820                        //              End time of the range of interest.
821                        // margin: int
822                        //              Margin in minutes around the time range.
823                        // visibilityTarget: String
824                        //              The end(s) of the time range to make visible.
825                        //              Valid values are: "start", "end", "both".       
826                        // duration: Number
827                        //              Optional, the maximum duration of the scroll animation.
828                        // tags:
829                        //              extension
830
831                },
832
833                ////////////////////////////////////////////////////////
834                //
835                // Store & Items
836                //
837                ////////////////////////////////////////////////////////
838               
839                _getStoreAttr: function(){
840                        if(this.owner){
841                                return this.owner.get("store");
842                        }
843                        return this.store;
844                },
845
846                _setItemsAttr: function(value){
847                        this._set("items", value);
848                        this.displayedItemsInvalidated = true;
849                },
850
851                _refreshItemsRendering: function(){
852                        var rd = this.renderData;
853                        this._computeVisibleItems(rd);
854                        this._layoutRenderers(rd);
855                },
856               
857                invalidateLayout: function(){
858                        // summary:
859                        //              Triggers a re-layout of the renderers.
860                        this._layoutRenderers(this.renderData);
861                },
862               
863                ////////////////////////////////////////////////////////
864                //
865                // Layout
866                //
867                ////////////////////////////////////////////////////////
868                               
869                computeOverlapping: function(layoutItems, func){
870                        // summary:
871                        //              Computes the overlap layout of a list of items. A lane and extent properties are added to each layout item.
872                        // layoutItems: Object[]
873                        //              List of layout items, each item must have a start and end properties.
874                        // addedPass: Function
875                        //              Whether computes the extent of each item renderer on free sibling lanes.
876                        // returns: Object
877                        // tags:
878                        //              protected
879
880                       
881                        if(layoutItems.length == 0){
882                                return {
883                                        numLanes: 0,
884                                        addedPassRes: [1]
885                                };
886                        }
887                       
888                        var lanes = [];
889
890                        for(var i=0; i<layoutItems.length; i++){
891                                var layoutItem = layoutItems[i];
892                                this._layoutPass1(layoutItem, lanes);
893                        }
894
895                        var addedPassRes = null;
896                        if(func){
897                                addedPassRes = lang.hitch(this, func)(lanes);
898                        }
899                       
900                        return {
901                                numLanes: lanes.length,
902                                addedPassRes: addedPassRes
903                        };
904                },
905
906                _layoutPass1: function (layoutItem, lanes){
907                        // summary:
908                        //              First pass of the overlap layout. Find a lane where the item can be placed or create a new one.
909                        // layoutItem: Object
910                        //              An object that contains a start and end properties at least.
911                        // lanes:
912                        //              The array of lanes.
913                        // tags:
914                        //              protected
915                        var stop = true;
916                       
917                        for(var i=0; i<lanes.length; i++){
918                                var lane = lanes[i];
919                                stop = false;
920                                for(var j=0; j<lane.length && !stop; j++){
921                                        if(lane[j].start < layoutItem.end && layoutItem.start < lane[j].end){
922                                                // one already placed item is overlapping
923                                                stop = true;
924                                                lane[j].extent = 1;
925                                        }
926                                }
927                                if(!stop){
928                                        //we have found a place
929                                        layoutItem.lane = i;
930                                        layoutItem.extent = -1;
931                                        lane.push(layoutItem);
932                                        return;
933                                }
934                        }
935                       
936                        //no place found -> add a lane
937                        lanes.push([layoutItem]);
938                        layoutItem.lane = lanes.length-1;                       
939                        layoutItem.extent = -1;
940                },
941                       
942               
943               
944                _layoutInterval: function(renderData, index, start, end, items){
945                        // summary:
946                        //              For each item in the items list: retrieve a renderer, compute its location and size and add it to the DOM.
947                        // renderData: Object
948                        //              The render data.
949                        // index: Integer
950                        //              The index of the interval.
951                        // start: Date
952                        //              The start time of the displayed date interval.
953                        // end: Date
954                        //              The end time of the displayed date interval.
955                        // items: Object[]
956                        //              The list of the items to represent.
957                        // tags:
958                        //              extension
959                },
960               
961                // layoutPriorityFunction: Function
962                //              An optional comparison function use to determine the order the item will be laid out
963                //              The function is used to sort an array and must, as any sorting function, take two items
964                //              as argument and must return an integer whose sign define order between arguments.
965                //              By default, a comparison by start time then end time is used.
966                layoutPriorityFunction: null,
967               
968                _sortItemsFunction: function(a, b){
969                        var res = this.dateModule.compare(a.startTime, b.startTime);
970                        if(res == 0){
971                                res = -1 * this.dateModule.compare(a.endTime, b.endTime);
972                        }
973                        return res;
974                },
975               
976                _layoutRenderers: function(renderData){
977                        // summary:
978                        //              Renders the data items. This method will call the _layoutInterval() method.
979                        // renderData: Object
980                        //              The render data.
981                        // tags:
982                        //              protected
983                        if(!renderData.items){
984                                return;
985                        }
986                                               
987                        // recycle renderers first
988                        this._recycleItemRenderers();
989                       
990                        var cal = renderData.dateModule;
991                       
992                        // Date
993                        var startDate = this.newDate(renderData.startTime);
994                       
995                        // Date and time
996                        var startTime = lang.clone(startDate);
997                       
998                        var endDate;
999                       
1000                        var items = renderData.items.concat();
1001
1002                        var itemsTemp = [], events;
1003                       
1004                        var index = 0;
1005                       
1006                        while(cal.compare(startDate, renderData.endTime) == -1 && items.length > 0){
1007                       
1008                                endDate = cal.add(startDate, this._layoutUnit, this._layoutStep);
1009                                endDate = this.floorToDay(endDate, true, renderData);
1010                               
1011                                var endTime = lang.clone(endDate);
1012                               
1013                                if(renderData.minHours){
1014                                        startTime.setHours(renderData.minHours);
1015                                }
1016                               
1017                                if(renderData.maxHours && renderData.maxHours != 24){
1018                                        endTime = cal.add(endDate, "day", -1);
1019                                        endTime = this.floorToDay(endTime, true, renderData);
1020                                        endTime.setHours(renderData.maxHours);
1021                                }
1022                               
1023                                // look for events that overlap the current sub interval
1024                                events = arr.filter(items, function(item){
1025                                        var r = this.isOverlapping(renderData, item.startTime, item.endTime, startTime, endTime);
1026                                        if(r){
1027                                                // item was not fully processed as it overlaps another sub interval
1028                                                if(cal.compare(item.endTime, endTime) == 1){
1029                                                        itemsTemp.push(item);
1030                                                }       
1031                                        }else{
1032                                                itemsTemp.push(item);
1033                                        }
1034                                        return r;
1035                                }, this);
1036
1037                                items = itemsTemp;
1038                                itemsTemp = [];
1039                               
1040                                // if event are in the current sub interval, layout them
1041                                if(events.length > 0){
1042                                        // Sort the item according a sorting function, by default start time then end time comparison are used.
1043                                        events.sort(lang.hitch(this, this.layoutPriorityFunction ? this.layoutPriorityFunction : this._sortItemsFunction));
1044                                        this._layoutInterval(renderData, index, startTime, endTime, events);
1045                                }
1046
1047                                startDate = endDate;
1048                                startTime = lang.clone(startDate);
1049
1050                                index++;
1051                        }                       
1052                       
1053                        this._onRenderersLayoutDone(this);
1054                },
1055       
1056                /////////////////////////////////////////////////////////////////
1057                //
1058                //      Renderers management
1059                //
1060                ////////////////////////////////////////////////////////////////
1061               
1062                _recycleItemRenderers: function(remove){
1063                        // summary:
1064                        //              Recycles all the item renderers.
1065                        // remove: Boolean
1066                        //              Whether remove the DOM node from it parent.
1067                        // tags:
1068                        //              protected
1069                        while(this.rendererList.length>0){
1070                                this._recycleRenderer(this.rendererList.pop(), remove);
1071                        }
1072                        this.itemToRenderer = {};
1073                },
1074                               
1075                // rendererPool: [protected] Array
1076                //              The stack of recycled renderers available.
1077                rendererPool: null,
1078               
1079                // rendererList: [protected] Array
1080                //              The list of used renderers
1081                rendererList: null,
1082               
1083                // itemToRenderer: [protected] Object
1084                //              The associated array item to renderer list.
1085                itemToRenderer: null,
1086               
1087                getRenderers: function(item){
1088                        // summary:
1089                        //              Returns the renderers that are currently used to displayed the speficied item.
1090                        //              Returns an array of objects that contains two properties:
1091                        //              - container: The DOM node that contains the renderer.
1092                        //              - renderer: The dojox.calendar._RendererMixin instance.
1093                        //              Do not keep references on the renderers are they are recycled and reused for other items.
1094                        // item: Object
1095                        //              The data or render item.
1096                        // returns: Object[]
1097                        if(item == null || item.id == null){
1098                                return null;
1099                        }
1100                        var list = this.itemToRenderer[item.id];
1101                        return list == null ? null : list.concat();
1102                },
1103               
1104                _rendererHandles: {},
1105               
1106                // itemToRendererKindFunc: Function
1107                //              An optional function to associate a kind of renderer ("horizontal", "label" or null) with the specified item.
1108                //              By default, if an item is lasting more that 24 hours an horizontal item is used, otherwise a label is used.
1109                itemToRendererKindFunc: null,
1110               
1111                _itemToRendererKind: function(item){
1112                        // summary:
1113                        //              Associates a kind of renderer with a data item.
1114                        // item: Object
1115                        //              The data item.
1116                        // returns: String
1117                        // tags:
1118                        //              protected                       
1119                        if(this.itemToRendererKindFunc){
1120                                return this.itemToRendererKindFunc(item);
1121                        }
1122                        return this._defaultItemToRendererKindFunc(item); // String
1123                },
1124               
1125                _defaultItemToRendererKindFunc:function(item){
1126                        // tags:
1127                        //              private
1128                        return null;
1129                },
1130
1131                _createRenderer: function(item, kind, rendererClass, cssClass){                 
1132                        // summary:
1133                        //              Creates an item renderer of the specified kind. A renderer is an object with the "container" and "instance" properties.
1134                        // item: Object
1135                        //              The data item.
1136                        // kind: String
1137                        //              The kind of renderer.
1138                        // rendererClass: Object
1139                        //              The class to instantiate to create the renderer.
1140                        // returns: Object
1141                        // tags:
1142                        //              protected                               
1143                                               
1144                        if(item != null && kind != null && rendererClass != null){
1145                               
1146                                var res=null, renderer=null;
1147                               
1148                                var pool = this.rendererPool[kind];
1149                               
1150                                if(pool != null){
1151                                        res = pool.shift();
1152                                }
1153
1154                                if (res == null){
1155
1156                                        renderer = new rendererClass;
1157                                                                       
1158                                        res = {
1159                                                renderer: renderer,
1160                                                container: renderer.domNode,
1161                                                kind: kind
1162                                        };
1163
1164                                        this._onRendererCreated({renderer:res, source:this, item:item});
1165                                       
1166                                } else {
1167                                        renderer = res.renderer;
1168                                       
1169                                        this._onRendererReused({renderer:renderer, source:this, item:item});
1170                                }
1171                               
1172                                renderer.owner = this;
1173                                renderer.set("rendererKind", kind);
1174                                renderer.set("item", item);
1175                               
1176                                var list = this.itemToRenderer[item.id];
1177                                if (list == null) {
1178                                        this.itemToRenderer[item.id] = list = [];
1179                                }
1180                                list.push(res);
1181                               
1182                                this.rendererList.push(res);
1183                                return res;     
1184                        }
1185                        return null;
1186                },
1187               
1188                _onRendererCreated: function(e){
1189                        if(e.source == this){
1190                                this.onRendererCreated(e);
1191                        }
1192                        if(this.owner != null){
1193                                this.owner._onRendererCreated(e);
1194                        }
1195                },
1196               
1197                onRendererCreated: function(e){
1198                        // summary:
1199                        //              Event dispatched when an item renderer has been created.
1200                        // e: __rendererLifecycleEventArgs
1201                        //              The renderer lifecycle event.
1202                        // tags:
1203                        //              callback
1204                },     
1205               
1206                _onRendererRecycled: function(e){
1207                        if(e.source == this){
1208                                this.onRendererRecycled(e);
1209                        }
1210                        if(this.owner != null){
1211                                this.owner._onRendererRecycled(e);
1212                        }
1213                },
1214               
1215                onRendererRecycled: function(e){
1216                        // summary:
1217                        //              Event dispatched when an item renderer has been recycled.
1218                        // e: __rendererLifecycleEventArgs
1219                        //              The renderer lifecycle event.
1220                        // tags:
1221                        //              callback
1222
1223                },
1224                                               
1225                _onRendererReused: function(e){
1226                        if(e.source == this){
1227                                this.onRendererReused(e);
1228                        }
1229                        if(this.owner != null){
1230                                this.owner._onRendererReused(e);
1231                        }
1232                },
1233               
1234                onRendererReused: function(e){
1235                        // summary:
1236                        //              Event dispatched when an item renderer that was recycled is reused.
1237                        // e: __rendererLifecycleEventArgs
1238                        //              The renderer lifecycle event.
1239                        // tags:
1240                        //              callback
1241                },
1242               
1243                _onRendererDestroyed: function(e){
1244                        if(e.source == this){
1245                                this.onRendererDestroyed(e);
1246                        }
1247                        if(this.owner != null){
1248                                this.owner._onRendererDestroyed(e);
1249                        }
1250                },
1251               
1252                onRendererDestroyed: function(e){
1253                        // summary:
1254                        //              Event dispatched when an item renderer is destroyed.
1255                        // e: __rendererLifecycleEventArgs
1256                        //              The renderer lifecycle event.
1257                        // tags:
1258                        //              callback
1259                },
1260                               
1261                _onRenderersLayoutDone: function(view){
1262                        // tags:
1263                        //              private
1264
1265                        this.onRenderersLayoutDone(view);
1266                        if(this.owner != null){
1267                                this.owner._onRenderersLayoutDone(view);
1268                        }                               
1269                },
1270                                                                       
1271                onRenderersLayoutDone: function(view){
1272                        // summary:
1273                        //              Event triggered when item renderers layout has been done.
1274                        // tags:
1275                        //              callback
1276                },
1277
1278                _recycleRenderer: function(renderer, remove){
1279                        // summary:
1280                        //              Recycles the item renderer to be reused in the future.
1281                        // renderer: dojox/calendar/_RendererMixin
1282                        //              The item renderer to recycle.
1283                        // tags:
1284                        //              protected                       
1285                                                               
1286                        this._onRendererRecycled({renderer:renderer, source:this});
1287                       
1288                        var pool = this.rendererPool[renderer.kind];
1289                       
1290                        if(pool == null){
1291                                this.rendererPool[renderer.kind] = [renderer];
1292                        }else{
1293                                pool.push(renderer);
1294                        }
1295                                                               
1296                        if(remove){
1297                                renderer.container.parentNode.removeChild(renderer.container);
1298                        }
1299
1300                        domStyle.set(renderer.container, "display", "none");
1301
1302                        renderer.renderer.owner = null;
1303                        renderer.renderer.set("item", null);
1304                },
1305                                                       
1306                _destroyRenderer: function(renderer){
1307                        // summary:
1308                        //              Destroys the item renderer.
1309                        // renderer: dojox/calendar/_RendererMixin
1310                        //              The item renderer to destroy.
1311                        // tags:
1312                        //              protected
1313                        this._onRendererDestroyed({renderer:renderer, source:this});
1314                       
1315                        var ir = renderer.renderer;             
1316                       
1317                        if(ir["destroy"]){
1318                                ir.destroy();
1319                        }
1320                       
1321                        html.destroy(renderer.container);       
1322                },
1323               
1324                _destroyRenderersByKind: function(kind){
1325                        // tags:
1326                        //              private
1327
1328                        var list = [];
1329                        for(var i=0;i<this.rendererList.length;i++){
1330                                var ir = this.rendererList[i];
1331                                if(ir.kind == kind){
1332                                        this._destroyRenderer(ir);
1333                                }else{
1334                                        list.push(ir);
1335                                }
1336                        }
1337                       
1338                        this.rendererList = list;
1339                       
1340                        var pool = this.rendererPool[kind];
1341                        if(pool){
1342                                while(pool.length > 0){
1343                                        this._destroyRenderer(pool.pop());
1344                                }
1345                        }
1346                       
1347                },
1348                               
1349                                       
1350                _updateEditingCapabilities: function(item, renderer){
1351                        // summary:
1352                        //              Update the moveEnabled and resizeEnabled properties of a renderer according to its event current editing state.
1353                        // item: Object
1354                        //              The store data item.
1355                        // renderer: dojox/calendar/_RendererMixin
1356                        //              The item renderer.
1357                        // tags:
1358                        //              protected
1359
1360                        var moveEnabled = this.isItemMoveEnabled(item, renderer.rendererKind);
1361                        var resizeEnabled = this.isItemResizeEnabled(item, renderer.rendererKind);
1362                        var changed = false;
1363                       
1364                        if(moveEnabled != renderer.get("moveEnabled")){
1365                                renderer.set("moveEnabled", moveEnabled);
1366                                changed = true;
1367                        }
1368                        if(resizeEnabled != renderer.get("resizeEnabled")){
1369                                renderer.set("resizeEnabled", resizeEnabled);
1370                                changed = true;
1371                        }
1372                       
1373                        if(changed){
1374                                renderer.updateRendering();
1375                        }
1376                },
1377       
1378                updateRenderers: function(obj, stateOnly){
1379                        // summary:
1380                        //              Updates all the renderers that represents the specified item(s).
1381                        // obj: Object
1382                        //              A render item or an array of render items.
1383                        // stateOnly: Boolean
1384                        //              Whether only the state of the item has changed (selected, edited, edited, focused) or a more global change has occured.
1385                        // tags:
1386                        //              protected
1387
1388                        if(obj == null){
1389                                return;
1390                        }
1391                       
1392                        var items = lang.isArray(obj) ? obj : [obj];
1393                       
1394                        for(var i=0; i<items.length; i++){
1395                               
1396                                var item = items[i];
1397                               
1398                                if(item == null || item.id == null){
1399                                        continue;
1400                                }
1401                                               
1402                                var list = this.itemToRenderer[item.id];
1403                               
1404                                if(list == null){
1405                                        continue;
1406                                }
1407                               
1408                                var selected = this.isItemSelected(item);
1409                                var hovered = this.isItemHovered(item);
1410                                var edited = this.isItemBeingEdited(item);
1411                                var focused = this.showFocus ? this.isItemFocused(item) : false;                               
1412                               
1413                                for(var j = 0; j < list.length; j++){
1414                                       
1415                                        var renderer = list[j].renderer;
1416                                        renderer.set("hovered", hovered);
1417                                        renderer.set("selected", selected);
1418                                        renderer.set("edited", edited);
1419                                        renderer.set("focused", focused);
1420                                        renderer.set("storeState", this.getItemStoreState(item));
1421                                       
1422                                        this.applyRendererZIndex(item, list[j], hovered, selected, edited, focused);
1423                                       
1424                                        if(!stateOnly){
1425                                                renderer.set("item", item); // force content refresh
1426                                                if(renderer.updateRendering){
1427                                                        renderer.updateRendering(); // reuse previously set dimensions 
1428                                                }
1429                                        }
1430                                }
1431                               
1432                        }
1433                },
1434               
1435                applyRendererZIndex: function(item, renderer, hovered, selected, edited, focused){
1436                        // summary:
1437                        //              Applies the z-index to the renderer based on the state of the item.
1438                        //              This methods is setting a z-index of 20 is the item is selected or edited
1439                        //              and the current lane value computed by the overlap layout (i.e. the renderers
1440                        //              are stacked according to their lane).
1441                        // item: Object
1442                        //              The render item.
1443                        // renderer: Object
1444                        //              A renderer associated with the render item.
1445                        // hovered: Boolean
1446                        //              Whether the item is hovered or not.
1447                        // selected: Boolean
1448                        //              Whether the item is selected or not.
1449                        // edited: Boolean
1450                        //              Whether the item is being edited not not.
1451                        // focused: Boolean
1452                        //              Whether the item is focused not not.
1453                        // tags:
1454                        //              protected
1455                                               
1456                        domStyle.set(renderer.container, {"zIndex": edited || selected ? 20: item.lane == undefined ? 0 : item.lane});
1457                },
1458               
1459                getIdentity: function(item){
1460                        return this.owner ? this.owner.getIdentity(item) : item.id;
1461                },             
1462               
1463                /////////////////////////////////////////////////////
1464                //
1465                // Hovered item
1466                //
1467                ////////////////////////////////////////////////////
1468
1469                _setHoveredItem: function(item, renderer){
1470                        // summary:
1471                        //              Sets the current hovered item.
1472                        // item: Object
1473                        //              The data item.
1474                        // renderer: dojox/calendar/_RendererMixin
1475                        //              The item renderer.
1476                        // tags:
1477                        //              protected
1478
1479                        if(this.owner){
1480                                this.owner._setHoveredItem(item, renderer);
1481                                return;
1482                        }
1483                       
1484                        if(this.hoveredItem && item && this.hoveredItem.id != item.id ||
1485                                item == null || this.hoveredItem == null){
1486                                var old = this.hoveredItem;
1487                                this.hoveredItem = item;
1488                               
1489                                this.updateRenderers([old, this.hoveredItem], true);
1490                               
1491                                if(item && renderer){
1492                                        this._updateEditingCapabilities(item._item ? item._item : item, renderer);
1493                                }
1494                        }
1495                },
1496               
1497                // hoveredItem: Object
1498                //              The currently hovered data item.
1499                hoveredItem: null,
1500               
1501                isItemHovered: function(item){
1502                        // summary:
1503                        //              Returns whether the specified item is hovered or not.
1504                        // item: Object
1505                        //              The item.
1506                        // returns: Boolean
1507                        if (this._isEditing && this._edProps){
1508                                return item.id == this._edProps.editedItem.id;
1509                        }
1510                        return this.owner ? 
1511                                this.owner.isItemHovered(item) :
1512                                this.hoveredItem != null && this.hoveredItem.id == item.id;
1513                       
1514                },
1515               
1516                isItemFocused: function(item){
1517                        // summary:
1518                        //              Returns whether the specified item is focused or not.
1519                        // item: Object
1520                        //              The item.
1521                        // returns: Boolean
1522                        return this._isItemFocused ? this._isItemFocused(item) : false;
1523                },
1524               
1525                ////////////////////////////////////////////////////////////////////
1526                //
1527                // Selection delegation
1528                //
1529                ///////////////////////////////////////////////////////////////////
1530               
1531                _setSelectionModeAttr: function(value){
1532                        if(this.owner){
1533                                this.owner.set("selectionMode", value);
1534                        }else{
1535                                this.inherited(arguments);
1536                        }                       
1537                },                                     
1538               
1539                _getSelectionModeAttr: function(value){                 
1540                        if(this.owner){
1541                                return this.owner.get("selectionMode");
1542                        }
1543                        return this.inherited(arguments);                       
1544                },
1545               
1546                _setSelectedItemAttr: function(value){                 
1547                        if(this.owner){
1548                                this.owner.set("selectedItem", value);
1549                        }else{
1550                                this.inherited(arguments);
1551                        }
1552                },
1553               
1554                _getSelectedItemAttr: function(value){                 
1555                        if(this.owner){
1556                                return this.owner.get("selectedItem");
1557                        }
1558                        return this.selectedItem; // no getter on super class (dojox.widget.Selection)                 
1559                },
1560               
1561                _setSelectedItemsAttr: function(value){                 
1562                        if(this.owner){
1563                                this.owner.set("selectedItems", value);
1564                        }else{
1565                                this.inherited(arguments);
1566                        }
1567                },
1568               
1569                _getSelectedItemsAttr: function(){                     
1570                        if(this.owner){
1571                                return this.owner.get("selectedItems");
1572                        }
1573                        return this.inherited(arguments);
1574                },
1575               
1576                isItemSelected: function(item){                 
1577                        if(this.owner){
1578                                return this.owner.isItemSelected(item);
1579                        }
1580                        return this.inherited(arguments);
1581                },
1582               
1583                selectFromEvent: function(e, item, renderer, dispatch){                 
1584                        if(this.owner){
1585                                this.owner.selectFromEvent(e, item, renderer, dispatch);
1586                        }else{
1587                                this.inherited(arguments);
1588                        }
1589                },
1590               
1591                setItemSelected: function(item, value){
1592                        if(this.owner){
1593                                this.owner.setItemSelected(item, value);
1594                        }else{
1595                                this.inherited(arguments);
1596                        }
1597                },
1598               
1599                ////////////////////////////////////////////////////////////////////
1600                //
1601                // Event creation
1602                //
1603                ///////////////////////////////////////////////////////////////////
1604               
1605                createItemFunc: null,
1606                /*=====
1607                createItemFunc: function(view, d, e){
1608                        // summary:
1609                        //              A user supplied function that creates a new event.
1610                        // view: ViewBase
1611                        //              the current view,
1612                        // d: Date
1613                        //              the date at the clicked location.
1614                        // e: MouseEvemt
1615                        //              the mouse event (can be used to return null for example)
1616
1617                },
1618                =====*/
1619
1620                               
1621                _getCreateItemFuncAttr: function(){                     
1622                        if(this.owner){
1623                                return this.owner.get("createItemFunc");
1624                        }
1625                        return this.createItemFunc;
1626                },
1627               
1628                // createOnGridClick: Boolean
1629                //              Indicates whether the user can create new event by clicking and dragging the grid.
1630                //              A createItem function must be defined on the view or the calendar object.
1631                createOnGridClick: false,
1632               
1633                _getCreateOnGridClickAttr: function(){
1634                        if(this.owner){
1635                                return this.owner.get("createOnGridClick");
1636                        }
1637                        return this.createOnGridClick;                 
1638                },
1639               
1640                ////////////////////////////////////////////////////////////////////
1641                //
1642                // Event creation
1643                //
1644                ///////////////////////////////////////////////////////////////////     
1645               
1646                _gridMouseDown: false,         
1647                _tempIdCount: 0,
1648                _tempItemsMap: null,
1649                               
1650                _onGridMouseDown: function(e){
1651                        // tags:
1652                        //              private
1653                        this._gridMouseDown = true;
1654                                                               
1655                        this.showFocus = false;
1656                                                               
1657                        if(this._isEditing){   
1658                                this._endItemEditing("mouse", false);
1659                        }
1660                       
1661                        this._doEndItemEditing(this.owner, "mouse");                   
1662                       
1663                        this.set("focusedItem", null);
1664                        this.selectFromEvent(e, null, null, true);
1665                       
1666                        if(this._setTabIndexAttr){
1667                                this[this._setTabIndexAttr].focus();
1668                        }
1669                                                               
1670                        if(this._onRendererHandleMouseDown){
1671                               
1672                                var f = this.get("createItemFunc");
1673                               
1674                                if(!f){
1675                                        return;
1676                                }
1677                               
1678                                var newItem = this._createdEvent = f(this, this.getTime(e), e);
1679                                                               
1680                                var store = this.get("store");
1681                                                                                       
1682                                if(!newItem || store == null){
1683                                        return;
1684                                }
1685                                                               
1686                                // calendar needs an ID to work with
1687                                if(store.getIdentity(newItem) == undefined){
1688                                        var id = "_tempId_" + (this._tempIdCount++);
1689                                        newItem[store.idProperty] = id;
1690                                        if(this._tempItemsMap == null){
1691                                                this._tempItemsMap = {};
1692                                        }
1693                                        this._tempItemsMap[id] = true;
1694                                }
1695                                                               
1696                                var newRenderItem = this.itemToRenderItem(newItem, store);                             
1697                                newRenderItem._item = newItem;
1698                                this._setItemStoreState(newItem, "unstored");
1699                               
1700                                // add the new temporary item to the displayed list and force view refresh
1701                                var owner = this._getTopOwner();
1702                                var items = owner.get("items");
1703                               
1704                                owner.set("items", items ? items.concat([newRenderItem]) : [newRenderItem]);
1705                                                               
1706                                this._refreshItemsRendering();
1707                               
1708                                // renderer created in _refreshItemsRenderering()
1709                                var renderers = this.getRenderers(newItem);                             
1710                                if(renderers && renderers.length>0){
1711                                        var renderer = renderers[0];                                   
1712                                        if(renderer){
1713                                                // trigger editing
1714                                                this._onRendererHandleMouseDown(e, renderer.renderer, "resizeEnd");
1715                                                this._startItemEditing(newRenderItem, "mouse");
1716                                        }                                       
1717                                }
1718                        }
1719                },
1720               
1721                _onGridMouseMove: function(e){
1722                        // tags:
1723                        //              private
1724                },
1725               
1726                _onGridMouseUp: function(e){
1727                        // tags:
1728                        //              private
1729                },
1730               
1731                _onGridTouchStart: function(e){
1732                        // tags:
1733                        //              private
1734
1735                        var p = this._edProps;
1736
1737                        this._gridProps = {
1738                                event: e,                               
1739                                fromItem: this.isAscendantHasClass(e.target, this.eventContainer, "dojoxCalendarEvent")
1740                        };                     
1741       
1742                        if(this._isEditing){
1743                               
1744                                if(this._gridProps){
1745                                        this._gridProps.editingOnStart = true;
1746                                }
1747
1748                                lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem));
1749                               
1750                                if(p.touchesLen == 0){
1751                                       
1752                                        if(p && p.endEditingTimer){
1753                                                clearTimeout(p.endEditingTimer);
1754                                                p.endEditingTimer = null;
1755                                        }
1756                                        this._endItemEditing("touch", false);
1757                                }
1758                        }
1759                       
1760                        this._doEndItemEditing(this.owner, "touch");           
1761
1762                        event.stop(e);
1763                       
1764                },
1765               
1766                _doEndItemEditing: function(obj, eventSource){
1767                        // tags:
1768                        //              private
1769
1770                        if(obj && obj._isEditing){
1771                                var p = obj._edProps;
1772                                if(p && p.endEditingTimer){
1773                                        clearTimeout(p.endEditingTimer);
1774                                        p.endEditingTimer = null;
1775                                }
1776                                obj._endItemEditing(eventSource, false);
1777                        }       
1778                },
1779                                       
1780                _onGridTouchEnd: function(e){
1781                        // tags:
1782                        //              private
1783                },
1784               
1785                _onGridTouchMove: function(e){
1786                        // tags:
1787                        //              private
1788                },
1789               
1790                __fixEvt: function(e){
1791                        // summary:
1792                        //              Extension point for a view to add some event properties to a calendar event.
1793                        // tags:
1794                        //              callback
1795                        return e;
1796                },
1797               
1798                _dispatchCalendarEvt: function(e, name){
1799                        // summary:
1800                        //              Adds view properties to event and enable bubbling at owner level.
1801                        // e: Event
1802                        //              The dispatched event.
1803                        // name: String
1804                        //              The event name.
1805                        // tags:
1806                        //              protected
1807                       
1808                        e = this.__fixEvt(e);
1809                        this[name](e);
1810                        if(this.owner){
1811                                this.owner[name](e);
1812                        }
1813                        return e;
1814                },
1815
1816                _onGridClick: function(e){
1817                        // tags:
1818                        //              private
1819                        if(!e.triggerEvent){
1820                                e = {
1821                                        date: this.getTime(e),
1822                                        triggerEvent: e
1823                                };
1824                        }       
1825                       
1826                        this._dispatchCalendarEvt(e, "onGridClick");
1827                },
1828               
1829                onGridClick: function(e){
1830                        // summary:
1831                        //              Event dispatched when the grid has been clicked.
1832                        // e: __GridClickEventArgs
1833                        //              The event dispatched when the grid is clicked.
1834                        // tags:
1835                        //              callback
1836                },
1837               
1838                _onGridDoubleClick: function(e){
1839                        // tags:
1840                        //              private
1841
1842                        if(!e.triggerEvent){
1843                                e = {
1844                                        date: this.getTime(e),
1845                                        triggerEvent: e
1846                                };
1847                        }
1848                                               
1849                        this._dispatchCalendarEvt(e, "onGridDoubleClick");
1850                },
1851                               
1852                onGridDoubleClick: function(e){
1853                        // summary:
1854                        //              Event dispatched when the grid has been double-clicked.
1855                        // e: __GridClickEventArgs
1856                        //              The event dispatched when the grid is double-clicked.
1857                        // tags:
1858                        //              protected
1859
1860                },
1861               
1862                _onItemClick: function(e){
1863                        // tags:
1864                        //              private
1865
1866                        this._dispatchCalendarEvt(e, "onItemClick");
1867                },
1868               
1869                onItemClick: function(e){
1870                        // summary:
1871                        //              Event dispatched when an item renderer has been clicked.
1872                        // e: __ItemMouseEventArgs
1873                        //              The event dispatched when an item is clicked.
1874                        // tags:
1875                        //              callback
1876
1877                },
1878               
1879                _onItemDoubleClick: function(e){
1880                        // tags:
1881                        //              private
1882
1883                        this._dispatchCalendarEvt(e, "onItemDoubleClick");     
1884                },
1885               
1886                onItemDoubleClick: function(e){
1887                        // summary:
1888                        //              Event dispatched when an item renderer has been double-clicked.
1889                        // e: __ItemMouseEventArgs
1890                        //              The event dispatched when an item is double-clicked.
1891                        // tags:
1892                        //              callback
1893
1894                },
1895
1896                _onItemContextMenu: function(e){
1897                        this._dispatchCalendarEvt(e, "onItemContextMenu");
1898                        // tags:
1899                        //              private
1900
1901                },
1902               
1903                onItemContextMenu: function(e){
1904                        // summary:
1905                        //              Event dispatched when an item renderer has been context-clicked.
1906                        // e: __ItemMouseEventArgs
1907                        //              The event dispatched when an item is context-clicked.
1908                        // tags:
1909                        //              callback
1910
1911                },
1912               
1913                //////////////////////////////////////////////////////////
1914                //
1915                //      Editing
1916                //
1917                //////////////////////////////////////////////////////////
1918
1919                _getStartEndRenderers: function(item){
1920                        // summary:
1921                        //              Returns an array that contains the first and last renderers of an item                 
1922                        //              that are currently displayed. They could be the same renderer if only one renderer is used.
1923                        // item: Object
1924                        //              The render item.
1925                        // returns: Object[]
1926                        // tags:
1927                        //              protected
1928
1929
1930                        var list = this.itemToRenderer[item.id];
1931
1932                        if(list == null){
1933                                return null;
1934                        }
1935
1936                        // trivial and most common use case.
1937                        if(list.length == 1){
1938                                var node = list[0].renderer;
1939                                return [node, node];
1940                        }
1941
1942                        var rd = this.renderData;
1943                        var resizeStartFound = false;
1944                        var resizeEndFound = false;
1945
1946                        var res = [];
1947
1948                        for(var i=0; i<list.length; i++){
1949
1950                                var ir = list[i].renderer;
1951
1952                                if (!resizeStartFound){
1953                                        resizeStartFound = rd.dateModule.compare(ir.item.range[0], ir.item.startTime) == 0;
1954                                        res[0] = ir;
1955                                }
1956
1957                                if (!resizeEndFound){
1958                                        resizeEndFound =  rd.dateModule.compare(ir.item.range[1], ir.item.endTime) == 0;
1959                                        res[1] = ir;
1960                                }
1961
1962                                if (resizeStartFound && resizeEndFound){
1963                                        break; 
1964                                }
1965                        }
1966
1967                        return res;                     
1968                },
1969                                                               
1970                // editable: Boolean
1971                //              A flag that indicates whether or not the user can edit
1972                //              items in the data provider.
1973                //              If <code>true</code>, the item renderers in the control are editable.
1974                //              The user can click on an item renderer, or use the keyboard or touch devices, to move or resize the associated event.
1975                editable: true,
1976
1977                // moveEnabled: Boolean
1978                //              A flag that indicates whether the user can move items displayed.
1979                //              If <code>true</code>, the user can move the items.
1980                moveEnabled: true,
1981
1982                // resizeEnabled: Boolean
1983                //              A flag that indicates whether the items can be resized.
1984                //              If `true`, the control supports resizing of items.
1985                resizeEnabled: true,
1986               
1987                isItemEditable: function(item, rendererKind){
1988                        // summary:
1989                        //              Computes whether particular item renderer can be edited or not.
1990                        //              By default it is using the editable property value.
1991                        // item: Object
1992                        //              The item represented by the renderer.
1993                        // rendererKind: String
1994                        //              The kind of renderer.
1995                        // returns: Boolean                     
1996                        return this.getItemStoreState(item) != "storing" && this.editable && (this.owner ? this.owner.isItemEditable(item, rendererKind) : true);
1997                },
1998
1999                isItemMoveEnabled: function(item, rendererKind){
2000                        // summary:
2001                        //              Computes whether particular item renderer can be moved.
2002                        //              By default it is using the moveEnabled property value.
2003                        // item: Object
2004                        //              The item represented by the renderer.
2005                        // rendererKind: String
2006                        //              The kind of renderer.
2007                        // returns: Boolean
2008                        return this.isItemEditable(item, rendererKind) && this.moveEnabled &&
2009                                (this.owner ? this.owner.isItemMoveEnabled(item, rendererKind): true);
2010                },
2011               
2012                isItemResizeEnabled: function(item, rendererKind){
2013                        // summary:
2014                        //              Computes whether particular item renderer can be resized.
2015                        //              By default it is using the resizedEnabled property value.
2016                        // item: Object
2017                        //              The item represented by the renderer.
2018                        // rendererKind: String
2019                        //              The kind of renderer.
2020                        // returns: Boolean
2021                       
2022                        return this.isItemEditable(item, rendererKind) && this.resizeEnabled &&
2023                                (this.owner ? this.owner.isItemResizeEnabled(item, rendererKind): true);
2024                },
2025
2026                // _isEditing: Boolean
2027                //              Whether an item is being edited or not.
2028                _isEditing: false,
2029               
2030                isItemBeingEdited: function(item){
2031                        // summary:
2032                        //              Returns whether an item is being edited or not.
2033                        // item: Object
2034                        //              The item to test.
2035                        // returns: Boolean
2036                        return this._isEditing && this._edProps && this._edProps.editedItem && this._edProps.editedItem.id == item.id;
2037                },
2038               
2039                _setEditingProperties: function(props){
2040                        // summary:
2041                        //              Registers the editing properties used by the editing functions.
2042                        //              This method should only be called by editing interaction mixins like Mouse, Keyboard and Touch.
2043                        // tags:
2044                        //              protected
2045
2046                        this._edProps = props;
2047                },
2048               
2049                _startItemEditing: function(item, eventSource){
2050                        // summary:
2051                        //              Configures the component, renderers to start one (mouse) of several (touch, keyboard) editing gestures.
2052                        // item: Object
2053                        //              The item that will be edited.
2054                        // eventSource: String
2055                        //              "mouse", "keyboard", "touch"
2056                        // tags:
2057                        //              protected
2058
2059                        this._isEditing = true;
2060                        this._getTopOwner()._isEditing = true;
2061                        var p = this._edProps;
2062                       
2063                        p.editedItem = item;
2064                        p.storeItem = item._item;
2065                        p.eventSource = eventSource;
2066                       
2067                        p.secItem = this._secondarySheet ? this._findRenderItem(item.id, this._secondarySheet.renderData.items) : null;
2068                        p.ownerItem = this.owner ? this._findRenderItem(item.id, this.items) : null;
2069                                               
2070                        if (!p.liveLayout){
2071                                p.editSaveStartTime = item.startTime;
2072                                p.editSaveEndTime = item.endTime;
2073                               
2074                                p.editItemToRenderer = this.itemToRenderer;
2075                                p.editItems = this.renderData.items;
2076                                p.editRendererList = this.rendererList;
2077                               
2078                                this.renderData.items = [p.editedItem];
2079                                var id = p.editedItem.id;
2080                       
2081                                this.itemToRenderer = {};
2082                                this.rendererList = [];
2083                                var list = p.editItemToRenderer[id];
2084                               
2085                                p.editRendererIndices = [];
2086                               
2087                                arr.forEach(list, lang.hitch(this, function(ir, i){
2088                                        if(this.itemToRenderer[id] == null){
2089                                                this.itemToRenderer[id] = [ir];
2090                                        }else{
2091                                                this.itemToRenderer[id].push(ir);
2092                                        }
2093                                        this.rendererList.push(ir);
2094                                }));
2095                               
2096                                // remove in old map & list the occurrence used by the edited item
2097                                p.editRendererList = arr.filter(p.editRendererList, function(ir){
2098                                        return ir != null && ir.renderer.item.id != id;
2099                                });
2100                                delete p.editItemToRenderer[id];
2101                        }
2102                       
2103                        // graphic feedback refresh
2104                        this._layoutRenderers(this.renderData);
2105                       
2106                        this._onItemEditBegin({
2107                                item: item,
2108                                storeItem: p.storeItem,
2109                                eventSource: eventSource
2110                        });
2111                },
2112               
2113                _onItemEditBegin: function(e){
2114                        // tags:
2115                        //              private
2116
2117                        this._editStartTimeSave = this.newDate(e.item.startTime);
2118                        this._editEndTimeSave = this.newDate(e.item.endTime);
2119                       
2120                        this._dispatchCalendarEvt(e, "onItemEditBegin");
2121                },
2122               
2123                onItemEditBegin: function(e){
2124                        // summary:
2125                        //              Event dispatched when the item is entering the editing mode.
2126                        // tags:
2127                        //              callback
2128
2129                },
2130               
2131                _endItemEditing: function(/*String*/eventSource, /*Boolean*/canceled){
2132                        // summary:
2133                        //              Leaves the item editing mode.
2134                        // item: Object
2135                        //              The item that was edited.
2136                        // eventSource: String
2137                        //              "mouse", "keyboard", "touch"
2138                        // tags:
2139                        //              protected
2140
2141                        this._isEditing = false;
2142                        this._getTopOwner()._isEditing = false;
2143                       
2144                        var p = this._edProps;
2145                       
2146                        arr.forEach(p.handles, function(handle){
2147                                handle.remove();
2148                        });                                     
2149                                               
2150                        if (!p.liveLayout){
2151                                this.renderData.items = p.editItems;
2152                                this.rendererList = p.editRendererList.concat(this.rendererList);
2153                                lang.mixin(this.itemToRenderer, p.editItemToRenderer);
2154                        }
2155
2156                        this._onItemEditEnd(lang.mixin(this._createItemEditEvent(), {
2157                                item: p.editedItem,
2158                                storeItem: p.storeItem,
2159                                eventSource: eventSource,
2160                                completed: !canceled
2161                        }));
2162                       
2163                        this._layoutRenderers(this.renderData);                         
2164                       
2165                        this._edProps = null;
2166                },
2167               
2168                _onItemEditEnd: function(e){
2169                        // tags:
2170                        //              private
2171                                                               
2172                        this._dispatchCalendarEvt(e, "onItemEditEnd");
2173                       
2174                        if(!e.isDefaultPrevented()){
2175                               
2176                                var store = this.get("store");
2177                               
2178                                // updated store item
2179                                var storeItem = this.renderItemToItem(e.item, store);
2180                               
2181                                var s = this._getItemStoreStateObj(e.item);
2182                               
2183                                if(s != null && s.state == "unstored"){
2184                                                                                                               
2185                                        if(e.completed){
2186                                                // renderItemToItem cannot find the original data item
2187                                                // (as it does not exist in the store yet) to mixin with.
2188                                                // so we must do it here.
2189                                                storeItem = lang.mixin(s.item, storeItem);
2190                                                this._setItemStoreState(storeItem, "storing");
2191                                                var oldID = store.getIdentity(storeItem);
2192                                                var options = null;
2193                                               
2194                                                if(this._tempItemsMap && this._tempItemsMap[oldID]){
2195                                                        options = {temporaryId: oldID};
2196                                                        delete this._tempItemsMap[oldID];
2197                                                        delete storeItem[store.idProperty];                                                     
2198                                                }
2199                                               
2200                                                // add to the store.
2201                                                when(store.add(storeItem, options), lang.hitch(this, function(res){
2202                                                        var id;
2203                                                        if(lang.isObject(res)){
2204                                                                id = store.getIdentity(res);
2205                                                        }else{
2206                                                                id = res;
2207                                                        }
2208                                                       
2209                                                        if(id != oldID){                                                       
2210                                                                this._removeRenderItem(oldID);
2211                                                        }
2212                                                }));
2213                                               
2214                                        }else{ // creation canceled
2215                                                // cleanup items list
2216                                               
2217                                                this._removeRenderItem(s.id);                                                                                   
2218                                        }                                                                       
2219                                       
2220                                } else if(e.completed){
2221                                        // Inject new properties in data store item                             
2222                                        // and apply data changes               
2223                                        this._setItemStoreState(storeItem, "storing");
2224                                        store.put(storeItem);                                                           
2225                                }else{
2226                                        e.item.startTime = this._editStartTimeSave;
2227                                        e.item.endTime = this._editEndTimeSave;
2228                                }
2229                        }
2230                },
2231               
2232                _removeRenderItem: function(id){
2233                       
2234                        var owner = this._getTopOwner();
2235                        var items = owner.get("items");
2236                        var l = items.length;
2237                        var found = false;
2238                        for(var i=l-1; i>=0; i--){
2239                                if(items[i].id == id){
2240                                        items.splice(i, 1);
2241                                        found = true;
2242                                        break;
2243                                }
2244                        }
2245                        this._cleanItemStoreState(id);
2246                        if(found){
2247                                owner.set("items", items); //force a complete relayout 
2248                                this.invalidateLayout();
2249                        }
2250                },
2251               
2252                onItemEditEnd: function(e){
2253                        // summary:
2254                        //              Event dispatched when the item is leaving the editing mode.
2255                        // tags:
2256                        //              protected
2257
2258                },
2259               
2260                _createItemEditEvent: function(){
2261                        // tags:
2262                        //              private
2263
2264                        var e = {
2265                                cancelable: true,
2266                                bubbles: false,
2267                                __defaultPrevent: false
2268                        };
2269                       
2270                        e.preventDefault = function(){
2271                                this.__defaultPrevented = true;
2272                        };
2273                       
2274                        e.isDefaultPrevented = function(){
2275                                return this.__defaultPrevented;
2276                        };
2277                       
2278                        return e;
2279                },
2280
2281               
2282                _startItemEditingGesture: function(dates, editKind, eventSource, e){
2283                        // summary:
2284                        //              Starts the editing gesture.
2285                        // date: Date[]
2286                        //              The reference dates (at least one).
2287                        // editKind: String
2288                        //              Kind of edit: "resizeBoth", "resizeStart", "resizeEnd" or "move".
2289                        // eventSource: String
2290                        //              "mouse", "keyboard", "touch"
2291                        // e: Event
2292                        //              The event at the origin of the editing gesture.
2293                        // tags:
2294                        //              protected
2295                       
2296                        var p = this._edProps;
2297                       
2298                        if(!p || p.editedItem == null){
2299                                return;
2300                        }
2301                       
2302                        this._editingGesture = true;
2303                       
2304                        var item = p.editedItem;
2305                       
2306                        p.editKind = editKind;
2307                       
2308                        this._onItemEditBeginGesture(this.__fixEvt(lang.mixin(this._createItemEditEvent(), {
2309                                item: item,
2310                                storeItem: p.storeItem,
2311                                startTime: item.startTime,
2312                                endTime: item.endTime,
2313                                editKind: editKind,
2314                                rendererKind: p.rendererKind,
2315                                triggerEvent: e,
2316                                dates: dates,
2317                                eventSource: eventSource
2318                        })));
2319                       
2320                        p.itemBeginDispatched = true;
2321
2322                },
2323               
2324               
2325                _onItemEditBeginGesture: function(e){
2326                        // tags:
2327                        //              private
2328                        var p = this._edProps;
2329                       
2330                        var item = p.editedItem;
2331                        var dates = e.dates;
2332                       
2333                        p.editingTimeFrom = [];                 
2334                        p.editingTimeFrom[0] = dates[0];                       
2335                       
2336                        p.editingItemRefTime = [];
2337                        p.editingItemRefTime[0] = this.newDate(p.editKind == "resizeEnd" ? item.endTime : item.startTime);
2338                       
2339                        if (p.editKind == "resizeBoth"){
2340                                p.editingTimeFrom[1] = dates[1];
2341                                p.editingItemRefTime[1] = this.newDate(item.endTime);                           
2342                        }               
2343                       
2344                        var cal = this.renderData.dateModule;
2345                       
2346                        p.inViewOnce = this._isItemInView(item);
2347                       
2348                        if(p.rendererKind == "label" || this.roundToDay){
2349                                p._itemEditBeginSave = this.newDate(item.startTime);
2350                                p._itemEditEndSave = this.newDate(item.endTime);
2351                        }
2352                       
2353                        p._initDuration = cal.difference(item.startTime, item.endTime, item.allDay?"day":"millisecond");       
2354                       
2355                        this._dispatchCalendarEvt(e, "onItemEditBeginGesture");
2356
2357                        if (!e.isDefaultPrevented()){
2358                               
2359                                if (e.eventSource == "mouse"){
2360                                        var cursor = e.editKind=="move"?"move":this.resizeCursor;
2361                                        p.editLayer = domConstruct.create("div", {
2362                                                style: "position: absolute; left:0; right:0; bottom:0; top:0; z-index:30; tabIndex:-1; background-image:url('"+this._blankGif+"'); cursor: "+cursor,
2363                                                onresizestart: function(e){return false;},
2364                                                onselectstart: function(e){return false;}
2365                                        }, this.domNode);
2366                                        p.editLayer.focus();
2367                                }
2368                        }
2369                },
2370               
2371                onItemEditBeginGesture: function(e){
2372                        // summary:
2373                        //              Event dispatched when an editing gesture is beginning.
2374                        // e: __itemEditingEventArgs
2375                        //              The editing event.
2376                        // tags:
2377                        //              callback
2378
2379                },
2380               
2381                _waDojoxAddIssue: function(d, unit, steps){
2382                        // summary:
2383                        //              Workaround an issue of dojox.date.XXXXX.date.add() function
2384                        //              that does not support the subtraction of time correctly (normalization issues).
2385                        // d: Date
2386                        //              Reference date.
2387                        // unit: String
2388                        //              Unit to add.
2389                        // steps: Integer
2390                        //              Number of units to add.
2391                        // tags:
2392                        //              protected
2393
2394                        var cal = this.renderData.dateModule;
2395                        if(this._calendar != "gregorian" && steps < 0){
2396                                var gd = d.toGregorian();
2397                                gd = date.add(gd, unit, steps);
2398                                return new this.renderData.dateClassObj(gd);
2399                        }else{
2400                                return cal.add(d, unit, steps);
2401                        }
2402                },
2403                                               
2404                _computeItemEditingTimes: function(item, editKind, rendererKind, times, eventSource){
2405                        // tags:
2406                        //              private
2407
2408                        var cal = this.renderData.dateModule;
2409                        var p = this._edProps;
2410                        var diff = cal.difference(p.editingTimeFrom[0], times[0], "millisecond");
2411                        times[0] = this._waDojoxAddIssue(p.editingItemRefTime[0], "millisecond", diff);
2412                       
2413                        if(editKind == "resizeBoth"){
2414                                diff = cal.difference(p.editingTimeFrom[1], times[1], "millisecond");
2415                                times[1] = this._waDojoxAddIssue(p.editingItemRefTime[1], "millisecond", diff);
2416                        }
2417                        return times;
2418                },
2419               
2420                _moveOrResizeItemGesture: function(dates, eventSource, e){
2421                        // summary:
2422                        //              Moves or resizes an item.
2423                        // dates: Date[]
2424                        //              The reference dates.
2425                        // editKind: String
2426                        //              Kind of edit: "resizeStart", "resizeEnd", "resizeBoth" or "move".
2427                        // eventSource: String
2428                        //              "mouse", "keyboard", "touch"
2429                        // e: Event
2430                        //              The event at the origin of the editing gesture.
2431                        // tags:
2432                        //              private
2433
2434                        if(!this._isEditing || dates[0] == null){
2435                                return;
2436                        }
2437                       
2438                        var p = this._edProps;
2439                        var item = p.editedItem;
2440                        var rd = this.renderData;
2441                        var cal = rd.dateModule;
2442                        var editKind = p.editKind;
2443                                       
2444                        var newTimes = [dates[0]];
2445                       
2446                        if(editKind == "resizeBoth"){
2447                                newTimes[1] = dates[1];
2448                        }
2449                       
2450                        newTimes = this._computeItemEditingTimes(item, p.editKind, p.rendererKind, newTimes, eventSource);
2451                                                       
2452                        var newTime = newTimes[0]; // usual use case
2453                                       
2454                        var moveOrResizeDone = false;
2455                       
2456                        var oldStart = lang.clone(item.startTime);
2457                        var oldEnd = lang.clone(item.endTime);
2458                       
2459                        // swap cannot used using keyboard as a gesture is made of one single change (loss of start/end context).
2460                        var allowSwap = p.eventSource == "keyboard" ? false : this.allowStartEndSwap;
2461
2462                        // Update the Calendar with the edited value.
2463                        if(editKind == "move"){
2464                                       
2465                                if(cal.compare(item.startTime, newTime) != 0){
2466                                        var duration = cal.difference(item.startTime, item.endTime, "millisecond");
2467                                        item.startTime = this.newDate(newTime);
2468                                        item.endTime = cal.add(item.startTime, "millisecond", duration);
2469                                        moveOrResizeDone = true;
2470                                }
2471                               
2472                        }else if(editKind == "resizeStart"){
2473                               
2474                                if(cal.compare(item.startTime, newTime) != 0){ 
2475                                        if(cal.compare(item.endTime, newTime) != -1){                           
2476                                                item.startTime = this.newDate(newTime);
2477                                        }else{ // swap detected
2478                                                if(allowSwap){
2479                                                        item.startTime = this.newDate(item.endTime);
2480                                                        item.endTime = this.newDate(newTime);   
2481                                                        p.editKind = editKind = "resizeEnd";
2482                                                        if(eventSource == "touch"){ // invert touches as well!
2483                                                                p.resizeEndTouchIndex = p.resizeStartTouchIndex;
2484                                                                p.resizeStartTouchIndex = -1;
2485                                                        }       
2486                                                }else{ // block the swap but keep the time of day
2487                                                        item.startTime = this.newDate(item.endTime);
2488                                                        item.startTime.setHours(newTime.getHours());
2489                                                        item.startTime.setMinutes(newTime.getMinutes());
2490                                                        item.startTime.setSeconds(newTime.getSeconds());
2491                                                }
2492                                        }
2493                                        moveOrResizeDone = true;
2494                                }
2495                               
2496                        }else if(editKind == "resizeEnd"){
2497                               
2498                                if(cal.compare(item.endTime, newTime) != 0){
2499                                        if(cal.compare(item.startTime, newTime) != 1){
2500                                                item.endTime = this.newDate(newTime);   
2501                                        }else{ // swap detected
2502
2503                                                if(allowSwap){
2504                                                        item.endTime = this.newDate(item.startTime);
2505                                                        item.startTime = this.newDate(newTime);
2506                                                        p.editKind = editKind = "resizeStart";
2507                                                        if(eventSource == "touch"){ // invert touches as well!
2508                                                                p.resizeStartTouchIndex = p.resizeEndTouchIndex;
2509                                                                p.resizeEndTouchIndex = -1;
2510                                                        }
2511                                                }else{ // block the swap but keep the time of day
2512                                                        item.endTime = this.newDate(item.startTime);
2513                                                        item.endTime.setHours(newTime.getHours());
2514                                                        item.endTime.setMinutes(newTime.getMinutes());
2515                                                        item.endTime.setSeconds(newTime.getSeconds()); 
2516                                                }
2517                                        }
2518
2519                                        moveOrResizeDone = true;
2520                                }
2521                        }else if(editKind == "resizeBoth"){
2522                               
2523                                        moveOrResizeDone = true;
2524
2525                                        var start =  this.newDate(newTime);
2526                                        var end = this.newDate(newTimes[1]);           
2527
2528                                        if(cal.compare(start, end) != -1){ // swap detected
2529                                                if(allowSwap){
2530                                                        var t = start;
2531                                                        start = end;
2532                                                        end = t;
2533                                                }else{ // as both ends are moved, the simple way is to forbid the move gesture.
2534                                                        moveOrResizeDone = false;
2535                                                }
2536                                        }
2537
2538                                        if(moveOrResizeDone){
2539                                                item.startTime = start;
2540                                                item.endTime = end;
2541                                        }
2542
2543                        }else{ 
2544                                return false;
2545                        }
2546
2547                        if(!moveOrResizeDone){
2548                                return false;
2549                        }
2550
2551                        var evt = lang.mixin(this._createItemEditEvent(), {
2552                                item: item,
2553                                storeItem: p.storeItem,
2554                                startTime: item.startTime,
2555                                endTime: item.endTime,
2556                                editKind: editKind,
2557                                rendererKind: p.rendererKind,
2558                                triggerEvent: e,
2559                                eventSource: eventSource
2560                        });
2561                       
2562                        // trigger snapping, rounding, minimal duration, boundaries checks etc.
2563                        if(editKind == "move"){
2564                                this._onItemEditMoveGesture(evt);
2565                        }else{
2566                                this._onItemEditResizeGesture(evt);
2567                        }
2568                       
2569                        // prevent invalid range
2570                        if(cal.compare(item.startTime, item.endTime) == 1){
2571                                var tmp = item.startTime;
2572                                item.startTime = item.endTime;
2573                                item.endTime = tmp;
2574                        }
2575                       
2576                        moveOrResizeDone =
2577                                cal.compare(oldStart, item.startTime) != 0 ||
2578                                cal.compare(oldEnd, item.endTime) != 0;
2579                       
2580                        if(!moveOrResizeDone){
2581                                return false;
2582                        }
2583
2584                        this._layoutRenderers(this.renderData);
2585
2586                        if(p.liveLayout && p.secItem != null){
2587                                p.secItem.startTime = item.startTime;
2588                                p.secItem.endTime = item.endTime;
2589                                this._secondarySheet._layoutRenderers(this._secondarySheet.renderData);
2590                        }else if(p.ownerItem != null && this.owner.liveLayout){
2591                                p.ownerItem.startTime = item.startTime;
2592                                p.ownerItem.endTime = item.endTime;
2593                                this.owner._layoutRenderers(this.owner.renderData);
2594                        }
2595                                                                                               
2596                        return true;
2597                },
2598               
2599                _findRenderItem: function(id, list){
2600                        // tags:
2601                        //              private
2602
2603                        list = list || this.renderData.items;
2604                        for(var i=0; i<list.length; i++){
2605                                if(list[i].id == id){
2606                                        return list[i];
2607                                }
2608                        }
2609                        return null;
2610                },
2611
2612                _onItemEditMoveGesture: function(e){   
2613                        // tags:
2614                        //              private
2615
2616                        this._dispatchCalendarEvt(e, "onItemEditMoveGesture");
2617
2618                        if(!e.isDefaultPrevented()){
2619                               
2620                                var p = e.source._edProps;
2621                                var rd = this.renderData;
2622                                var cal = rd.dateModule;
2623                                var newStartTime, newEndTime;
2624                               
2625                                if(p.rendererKind == "label" || (this.roundToDay && !e.item.allDay)){
2626                                       
2627                                        newStartTime = this.floorToDay(e.item.startTime, false, rd);
2628                                        newStartTime.setHours(p._itemEditBeginSave.getHours());
2629                                        newStartTime.setMinutes(p._itemEditBeginSave.getMinutes());
2630                                       
2631                                        newEndTime = cal.add(newStartTime, "millisecond", p._initDuration);
2632                                       
2633                                }else if(e.item.allDay){
2634                                        newStartTime = this.floorToDay(e.item.startTime, true);
2635                                        newEndTime = cal.add(newStartTime, "day", p._initDuration);
2636                                }else{
2637                                        newStartTime = this.floorDate(e.item.startTime, this.snapUnit, this.snapSteps);
2638                                        newEndTime = cal.add(newStartTime, "millisecond", p._initDuration);
2639                                }
2640
2641                                e.item.startTime = newStartTime;
2642                                e.item.endTime = newEndTime;
2643                               
2644                                if(!p.inViewOnce){
2645                                        p.inViewOnce = this._isItemInView(e.item);
2646                                }
2647
2648                                // to prevent strange behaviors use constraint in items already fully in view.
2649                                if(p.inViewOnce && this.stayInView){
2650                                        this._ensureItemInView(e.item);
2651                                }
2652                        }
2653                },
2654               
2655                _DAY_IN_MILLISECONDS: 24 * 60 * 60 * 1000,
2656               
2657                onItemEditMoveGesture: function(e){
2658                        // summary:
2659                        //              Event dispatched during a move editing gesture.
2660                        // e: __itemEditingEventArgs
2661                        //              The editing event.
2662                        // tags:
2663                        //              callback
2664
2665                },
2666                               
2667                _onItemEditResizeGesture: function(e){
2668                        // tags:
2669                        //              private
2670
2671                        this._dispatchCalendarEvt(e, "onItemEditResizeGesture");
2672                       
2673                        if(!e.isDefaultPrevented()){                           
2674                                                       
2675                                var p = e.source._edProps;
2676                                var rd = this.renderData;
2677                                var cal = rd.dateModule;
2678                               
2679                                var newStartTime = e.item.startTime;
2680                                var newEndTime = e.item.endTime;
2681                               
2682                                if(e.editKind == "resizeStart"){
2683                                        if(e.item.allDay){
2684                                                newStartTime = this.floorToDay(e.item.startTime, false, this.renderData);
2685                                        }else if(this.roundToDay){
2686                                                newStartTime = this.floorToDay(e.item.startTime, false, rd);
2687                                                newStartTime.setHours(p._itemEditBeginSave.getHours());
2688                                                newStartTime.setMinutes(p._itemEditBeginSave.getMinutes());
2689                                        }else{
2690                                                newStartTime = this.floorDate(e.item.startTime, this.snapUnit, this.snapSteps);
2691                                        }
2692                                }else if(e.editKind == "resizeEnd"){
2693                                        if(e.item.allDay){
2694                                                if(!this.isStartOfDay(e.item.endTime)){
2695                                                        newEndTime = this.floorToDay(e.item.endTime, false, this.renderData);
2696                                                        newEndTime = cal.add(newEndTime, "day", 1);
2697                                                }
2698                                        }else if(this.roundToDay){
2699                                                newEndTime = this.floorToDay(e.item.endTime, false, rd);
2700                                                newEndTime.setHours(p._itemEditEndSave.getHours());
2701                                                newEndTime.setMinutes(p._itemEditEndSave.getMinutes());
2702                                        }else{
2703                                                newEndTime = this.floorDate(e.item.endTime, this.snapUnit, this.snapSteps);
2704                                       
2705                                                if(e.eventSource == "mouse"){
2706                                                        newEndTime = cal.add(newEndTime, this.snapUnit, this.snapSteps);
2707                                                }
2708                                        }
2709                                }else{ // Resize both
2710                                        newStartTime = this.floorDate(e.item.startTime, this.snapUnit, this.snapSteps);
2711                                        newEndTime = this.floorDate(e.item.endTime, this.snapUnit, this.snapSteps);
2712                                        newEndTime = cal.add(newEndTime, this.snapUnit, this.snapSteps);
2713                                }
2714                               
2715                                e.item.startTime = newStartTime;
2716                                e.item.endTime = newEndTime;
2717                               
2718                                var minimalDay = e.item.allDay || p._initDuration >= this._DAY_IN_MILLISECONDS && !this.allowResizeLessThan24H;
2719                               
2720                                this.ensureMinimalDuration(this.renderData, e.item,
2721                                        minimalDay ? "day" : this.minDurationUnit,
2722                                        minimalDay ? 1 : this.minDurationSteps,
2723                                        e.editKind);
2724                                       
2725                                if(!p.inViewOnce){
2726                                        p.inViewOnce = this._isItemInView(e.item);
2727                                }
2728
2729                                // to prevent strange behaviors use constraint in items already fully in view.
2730                                if(p.inViewOnce && this.stayInView){
2731                                        this._ensureItemInView(e.item);
2732                                }
2733                        }
2734                },
2735               
2736                onItemEditResizeGesture: function(e){
2737                        // summary:
2738                        //              Event dispatched during a resize editing gesture.
2739                        // e: __itemEditingEventArgs
2740                        //              The editing event.
2741                        // tags:
2742                        //              callback
2743
2744                },
2745               
2746                _endItemEditingGesture: function(/*String*/eventSource, /*Event*/e){
2747                        // tags:
2748                        //              protected
2749
2750                        if(!this._isEditing){
2751                                return;
2752                        }                                       
2753                       
2754                        this._editingGesture = false;
2755                       
2756                        var p = this._edProps;
2757                        var item = p.editedItem;
2758                       
2759                        p.itemBeginDispatched = false;                                                 
2760                       
2761                        this._onItemEditEndGesture(lang.mixin(this._createItemEditEvent(), {
2762                                item: item,
2763                                storeItem: p.storeItem,
2764                                startTime: item.startTime,
2765                                endTime: item.endTime,
2766                                editKind: p.editKind,
2767                                rendererKind: p.rendererKind,
2768                                triggerEvent: e,
2769                                eventSource: eventSource
2770                        }));
2771
2772                },
2773               
2774                _onItemEditEndGesture: function(e){
2775                        // tags:
2776                        //              private
2777
2778                        var p = this._edProps;
2779                       
2780                        delete p._itemEditBeginSave;
2781                        delete p._itemEditEndSave;
2782                                       
2783                        this._dispatchCalendarEvt(e, "onItemEditEndGesture");
2784                       
2785                        if (!e.isDefaultPrevented()){
2786                                if(p.editLayer){
2787                                        if(has("ie")){
2788                                                p.editLayer.style.cursor = "default";
2789                                        }
2790                                        setTimeout(lang.hitch(this, function(){
2791                                                if(this.domNode){ // for unit tests                                     
2792                                                        this.domNode.focus();
2793                                                        p.editLayer.parentNode.removeChild(p.editLayer);
2794                                                        p.editLayer = null;
2795                                                }               
2796                                        }), 10);
2797                                                               
2798                                }
2799                        }
2800                },
2801               
2802                onItemEditEndGesture: function(e){
2803                        // summary:
2804                        //              Event dispatched at the end of an editing gesture.
2805                        // e: __itemEditingEventArgs
2806                        //              The editing event.
2807                        // tags:
2808                        //              callback
2809
2810                },
2811               
2812                ensureMinimalDuration: function(renderData, item, unit, steps, editKind){
2813                        // summary:
2814                        //              During the resize editing gesture, ensures that the item has the specified minimal duration.
2815                        // renderData: Object
2816                        //              The render data.
2817                        // item: Object
2818                        //              The edited item.
2819                        // unit: String
2820                        //              The unit used to define the minimal duration.
2821                        // steps: Integer
2822                        //              The number of time units.
2823                        // editKind: String
2824                        //              The edit kind: "resizeStart" or "resizeEnd".
2825                        var minTime;
2826                        var cal = renderData.dateModule;
2827                       
2828                        if(editKind == "resizeStart"){
2829                                minTime = cal.add(item.endTime, unit, -steps);
2830                                if(cal.compare(item.startTime, minTime) == 1){
2831                                        item.startTime = minTime;
2832                                }
2833                        } else {
2834                                minTime = cal.add(item.startTime, unit, steps);
2835                                if(cal.compare(item.endTime, minTime) == -1){
2836                                        item.endTime = minTime;
2837                                }
2838                        }
2839                },
2840               
2841                // doubleTapDelay: Integer
2842                //              The maximum delay between two taps needed to trigger an "itemDoubleClick" event, in touch context.             
2843                doubleTapDelay: 300,
2844               
2845                // snapUnit: String
2846                //              The unit of the snapping to apply during the editing of an event.
2847                //              "day", "hour" and "minute" are valid values.
2848                snapUnit: "minute",
2849               
2850                // snapSteps: Integer
2851                //              The number of units used to compute the snapping of the edited item.
2852                snapSteps: 15,
2853               
2854                // minDurationUnit: "String"
2855                //              The unit used to define the minimal duration of the edited item.
2856                //              "day", "hour" and "minute" are valid values.
2857                minDurationUnit: "hour",
2858               
2859                // minDurationSteps: Integer
2860                //              The number of units used to define the minimal duration of the edited item.
2861                minDurationSteps: 1,
2862               
2863                // liveLayout: Boolean
2864                //              If true, all the events are laid out during the editing gesture. If false, only the edited event is laid out.
2865                liveLayout: false,                     
2866               
2867                // stayInView: Boolean
2868                //              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.           
2869                stayInView: true,
2870               
2871                // allowStartEndSwap: Boolean
2872                //              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.
2873                allowStartEndSwap: true,                       
2874               
2875                // allowResizeLessThan24H: Boolean
2876                //              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.
2877                //              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.
2878                allowResizeLessThan24H: false
2879               
2880        });
2881});
Note: See TracBrowser for help on using the repository browser.