source: Dev/trunk/src/client/dojox/calendar/SimpleColumnView.js @ 532

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

Added Dojo 1.9.3 release.

File size: 49.2 KB
Line 
1define([
2"./ViewBase",
3"dijit/_TemplatedMixin",
4"./_VerticalScrollBarBase",
5"dojo/text!./templates/SimpleColumnView.html",
6"dojo/_base/declare",
7"dojo/_base/event",
8"dojo/_base/lang",
9"dojo/_base/array",
10"dojo/_base/sniff",
11"dojo/_base/fx",
12"dojo/_base/html",
13"dojo/on",
14"dojo/dom",
15"dojo/dom-class",
16"dojo/dom-style",
17"dojo/dom-geometry",
18"dojo/dom-construct",
19"dojo/mouse",
20"dojo/query",
21"dojox/html/metrics"],
22
23function(
24        ViewBase,
25        _TemplatedMixin,
26        _VerticalScrollBarBase,
27        template,
28        declare,
29        event,
30        lang,
31        arr,
32        has,
33        fx,
34        html,
35        on,
36        dom,
37        domClass,
38        domStyle,
39        domGeometry,
40        domConstruct,
41        mouse,
42        query,
43        metrics){
44       
45        /*=====
46        var __ColumnClickEventArgs = {
47                // summary:
48                //              A column click event.
49                // index: Integer
50                //              The column index.
51                // date: Date
52                //              The date displayed by the column.
53                // triggerEvent: Event
54                //              The origin event.
55        };
56        =====*/
57                               
58        return declare("dojox.calendar.SimpleColumnView", [ViewBase, _TemplatedMixin], {
59               
60                // summary:
61                //              The simple column view is displaying a day per column. Each cell of a column is a time slot.
62
63                baseClass: "dojoxCalendarSimpleColumnView",
64               
65                templateString: template,
66               
67                // viewKind: String
68                //              Type of the view. Used by the calendar widget to determine how to configure the view.
69                //              This view kind is "columns".
70                viewKind: "columns",
71               
72                // scroll container is the focusable item to enable scrolling using up and down arrows
73                _setTabIndexAttr: "domNode",
74               
75                // renderData: Object
76                //              The render data is the object that contains all the properties needed to render the component.
77                renderData: null,               
78                               
79                // startDate: Date
80                //              The start date of the time interval displayed.
81                //              If not set at initialization time, will be set to current day.
82                startDate: null,
83                       
84                // columnCount: Integer
85                //              The number of column to display (from the startDate).
86                columnCount: 7,
87       
88                // minHours: Integer
89                //              The minimum hour to be displayed. It must be in the [0,23] interval and must be lower than the maxHours.
90                minHours: 8,
91               
92                // maxHours: Integer
93                //              The maximum hour to be displayed. It must be in the [1,24] interval and must be greater than the minHours.     
94                maxHours: 18,
95               
96                // hourSize: Integer
97                //              The desired size in pixels of an hour on the screen.
98                //              Note that the effective size may be different as the time slot size must be an integer.
99                hourSize: 100,
100               
101                // timeSlotDuration: Integer
102                //              Duration of the time slot in minutes. Must be a divisor of 60.
103                timeSlotDuration: 15,
104
105                // rowHeaderGridSlotDuration: Integer
106                //              Duration of the time slot in minutes in the row header. Must be a divisor of 60 and a multiple/divisor of timeSlotDuration.
107                rowHeaderGridSlotDuration: 60,
108               
109                // rowHeaderLabelSlotDuration: Integer
110                //              Duration of the time slot in minutes in the row header labels. Must be a divisor of 60 and a multiple/divisor of timeSlotDuration.
111                rowHeaderLabelSlotDuration: 60,
112               
113                // rowHeaderLabelOffset: Integer
114                //              Offset of the row label from the top of the row header cell in pixels.
115                rowHeaderLabelOffset: 2,
116               
117                // rowHeaderFirstLabelOffset: Integer
118                //              Offset of the first row label from the top of the first row header cell in pixels.
119                rowHeaderFirstLabelOffset: 2,
120               
121                // verticalRenderer: Class
122                //              The class use to create vertical renderers.
123                verticalRenderer: null,
124               
125                // percentOverlap: Integer
126                //              The percentage of the renderer width used to superimpose one item renderer on another
127                //              when two events are overlapping.
128                percentOverlap: 70,
129               
130                // horizontalGap: Integer
131                //              The number of pixels between two item renderers that are overlapping each other if the percentOverlap property is 0.
132                horizontalGap: 4,
133               
134                _columnHeaderHandlers: null,
135               
136                constructor: function(){
137                        this.invalidatingProperties = ["columnCount", "startDate", "minHours", "maxHours", "hourSize", "verticalRenderer",
138                                "rowHeaderTimePattern", "columnHeaderDatePattern", "timeSlotDuration", "rowHeaderGridSlotDuration", "rowHeaderLabelSlotDuration",
139                                "rowHeaderLabelOffset", "rowHeaderFirstLabelOffset","percentOverlap", "horizontalGap", "scrollBarRTLPosition","itemToRendererKindFunc",
140                                "layoutPriorityFunction", "formatItemTimeFunc", "textDir", "items"];
141                        this._columnHeaderHandlers = [];
142                },
143               
144                destroy: function(preserveDom){
145                        this._cleanupColumnHeader();
146                        if(this.scrollBar){
147                                this.scrollBar.destroy(preserveDom);
148                        }
149                        this.inherited(arguments);
150                },
151               
152                _scrollBar_onScroll: function(value){
153                        this._setScrollPosition(value);
154                },
155               
156                buildRendering: function(){
157                        this.inherited(arguments);
158                        if(this.vScrollBar){
159                                this.scrollBar = new _VerticalScrollBarBase(
160                                        {content: this.vScrollBarContent},
161                                        this.vScrollBar);
162                                       
163                                this.scrollBar.on("scroll", lang.hitch(this, this._scrollBar_onScroll));
164                                this._viewHandles.push(
165                                                on(this.scrollContainer, mouse.wheel, 
166                                                        dojo.hitch(this, this._mouseWheelScrollHander)));
167                        }
168                },
169               
170                postscript: function(){
171                        this.inherited(arguments);
172                        this._initialized = true;
173                        if(!this.invalidRendering){
174                                this.refreshRendering();
175                        }
176                },
177               
178                _setVerticalRendererAttr: function(value){
179                        this._destroyRenderersByKind("vertical");
180                        this._set("verticalRenderer", value);   
181                },
182                               
183                _createRenderData: function(){
184                       
185                        var renderData = {};
186
187                        renderData.minHours = this.get("minHours");             
188                        renderData.maxHours = this.get("maxHours");
189                        renderData.hourSize = this.get("hourSize");
190                        renderData.hourCount = renderData.maxHours - renderData.minHours;               
191                        renderData.slotDuration = this.get("timeSlotDuration"); // must be consistent with previous statement
192                        renderData.rowHeaderGridSlotDuration = this.get("rowHeaderGridSlotDuration");
193                        renderData.slotSize = Math.ceil(renderData.hourSize / (60 / renderData.slotDuration));
194                        renderData.hourSize = renderData.slotSize * (60 / renderData.slotDuration);                     
195                        renderData.sheetHeight = renderData.hourSize * renderData.hourCount;           
196                        renderData.scrollbarWidth = metrics.getScrollbar().w + 1;
197                       
198                        renderData.dateLocaleModule = this.dateLocaleModule;
199                        renderData.dateClassObj = this.dateClassObj;
200                        renderData.dateModule = this.dateModule; // arithmetics on Dates
201                       
202                        renderData.dates = [];
203                                               
204                        renderData.columnCount = this.get("columnCount");
205
206                        var d = this.get("startDate");
207               
208                        if (d == null){
209                                d = new renderData.dateClassObj();
210                        }
211
212                        d = this.floorToDay(d, false, renderData);
213                       
214                        this.startDate = d;
215                       
216                        for(var col = 0; col < renderData.columnCount ; col++){
217                                renderData.dates.push(d);
218                                d = renderData.dateModule.add(d, "day", 1);
219                                d = this.floorToDay(d, false, renderData);
220                        }
221
222                        renderData.startTime = new renderData.dateClassObj(renderData.dates[0]);
223                        renderData.startTime.setHours(renderData.minHours);
224                        renderData.endTime = new renderData.dateClassObj(renderData.dates[renderData.columnCount-1]);
225                        renderData.endTime.setHours(renderData.maxHours);
226                       
227                        if(this.displayedItemsInvalidated && !this._isEditing){
228                                 // while editing in no live layout we must not to recompute items (duplicate renderers)
229                                this._computeVisibleItems(renderData);
230                                                               
231                        }else if (this.renderData){
232                                renderData.items = this.renderData.items;
233                        }
234                       
235                        return renderData;
236                },
237               
238                _validateProperties: function() {
239                       
240                        this.inherited(arguments);
241                       
242                        var v = this.minHours;
243                        if(v < 0 || v>23 || isNaN(v)){
244                                this.minHours = 0;
245                        }
246                        v = this.maxHours;
247                        if (v < 1 || v>24 || isNaN(v)){
248                                this.minHours = 24;
249                        }
250                       
251                        if(this.minHours > this.maxHours){
252                                var t = this.maxHours;
253                                this.maxHours = this.minHours;
254                                this.minHours = t;
255                        }
256                        if (this.maxHours - this.minHours < 1){
257                                this.minHours = 0;
258                                this.maxHours = 24;                             
259                        }
260                        if (this.columnCount<1 || isNaN(this.columnCount)){
261                                this.columnCount = 1;                           
262                        }
263                       
264                        v = this.percentOverlap;
265                        if(v < 0 ||v > 100 || isNaN(v)){
266                                this.percentOverlap = 70;
267                        }
268                        if(this.hourSize<5 || isNaN(this.hourSize)){
269                                this.hourSize = 10;
270                        }
271                        v = this.timeSlotDuration;
272            if (v < 1 || v > 60 || isNaN(v)) {
273                this.timeSlotDuration = 15;
274            }
275                },
276               
277                _setStartDateAttr: function(value){
278                        this.displayedItemsInvalidated = true;                 
279                        this._set("startDate", value);
280                },
281               
282                _setColumnCountAttr: function(value){                   
283                        this.displayedItemsInvalidated = true;
284                        this._set("columnCount", value);
285                },
286               
287                __fixEvt:function(e){
288                        // tags:
289                        //              private
290                        e.sheet = "primary";
291                        e.source = this;
292                        return e;
293                },
294               
295                //////////////////////////////////////////
296                //
297                // Formatting functions
298                //
299                //////////////////////////////////////////
300               
301                _formatRowHeaderLabel: function(/*Date*/d){
302                        // summary:
303                        //              Computes the row header label for the specified time of day.
304                        //              By default a formatter is used, optionally the <code>rowHeaderTimePattern</code> property can be used to set a custom time pattern to the formatter.
305                        // d: Date
306                        //              The date to format
307                        // tags:
308                        //              protected
309
310                        return this.renderData.dateLocaleModule.format(d, {
311                                selector: "time",
312                                timePattern: this.rowHeaderTimePattern
313                        });
314                },
315       
316                _formatColumnHeaderLabel: function(/*Date*/d){                 
317                        // summary:
318                        //              Computes the column header label for the specified date.
319                        //              By default a formatter is used, optionally the <code>columnHeaderDatePattern</code> property can be used to set a custom date pattern to the formatter.
320                        // d: Date
321                        //              The date to format
322                        // tags:
323                        //              protected
324
325                        return this.renderData.dateLocaleModule.format(d, {
326                                selector: "date",
327                                datePattern: this.columnHeaderDatePattern,
328                                formatLength: "medium"
329                        });
330                },
331               
332                //////////////////////////////////////////
333                //
334                // Time of day management
335                //
336                //////////////////////////////////////////
337               
338                // startTimeOfDay: Object
339                //              The scroll position of the view. The value is an object made of "hours" and "minutes" properties.
340                startTimeOfDay: null,
341                               
342                // scrollBarRTLPosition: String
343                //              Position of the scroll bar in right-to-left display.
344                //              Valid values are "left" and "right", default value is "left".
345                scrollBarRTLPosition: "left",
346               
347                _getStartTimeOfDay: function(){
348                        // summary:
349                        //              Returns the visible first time of day.
350                        // tags:
351                        //              protected
352                        // returns: Object
353
354                        var v = (this.get("maxHours") - this.get("minHours")) *
355                                this._getScrollPosition() / this.renderData.sheetHeight;
356                       
357                        return {
358                                hours: this.renderData.minHours + Math.floor(v),
359                                minutes: (v - Math.floor(v)) * 60
360                        };
361                },
362               
363                _getEndTimeOfDay: function(){
364                        // summary:
365                        //              Returns the visible last time of day.
366                        // tags:
367                        //              protected
368                        // returns: Integer[]                                   
369
370                        var v = (this.get("maxHours") - this.get("minHours")) *
371                                (this._getScrollPosition() + this.scrollContainer.offsetHeight) / this.renderData.sheetHeight;
372                       
373                        return {
374                                hours: this.renderData.minHours + Math.floor(v),
375                                minutes: (v - Math.floor(v)) * 60
376                        };
377                },
378               
379                // startTimeOfDay: Object
380                //              First time (hour/minute) of day displayed, if reachable.
381                //              An object containing "hours" and "minutes" properties.
382                startTimeOfDay: 0,
383                       
384                _setStartTimeOfDayAttr: function(value){
385                        if(this.renderData){
386                                this._setStartTimeOfDay(value.hours, value.minutes, value.duration, value.easing);
387                        }else{
388                                this._startTimeOfDayInvalidated = true;
389                        }
390                        this._set("startTimeOfDay", value);
391                       
392                },
393               
394                _getStartTimeOfDayAttr: function(){
395                        if(this.renderData){
396                                return this._getStartTimeOfDay();
397                        }else{
398                                return this._get("startTimeOfDay");
399                        }
400                },
401               
402                _setStartTimeOfDay: function(hour, minutes, maxDuration, easing){
403                        // summary:
404                        //              Scrolls the view to show the specified first time of day.
405                        // hour: Integer
406                        //              The hour of the start time of day.
407                        // minutes: Integer
408                        //              The minutes part of the start time of day.
409                        // maxDuration: Integer
410                        //              The max duration of the scroll animation.
411                        // tags:
412                        //              protected
413
414                        var rd = this.renderData;
415                       
416                        hour = hour || rd.minHours;
417                        minutes = minutes || 0;
418                        maxDuration = maxDuration || 0;
419                       
420                        if (minutes < 0){
421                                minutes = 0;
422                        }else if (minutes > 59){
423                                minutes = 59;
424                        }
425                       
426                        if (hour < 0){
427                                hour = 0;
428                        }else if (hour > 24){
429                                hour = 24;
430                        }
431                       
432                        var timeInMinutes = hour * 60 + minutes;
433                       
434                        var minH = rd.minHours*60;
435                        var maxH = rd.maxHours*60;
436                       
437                        if (timeInMinutes < minH){
438                                timeInMinutes = minH;
439                        }else if(timeInMinutes > maxH){
440                                timeInMinutes = maxH;
441                        }
442                                       
443                        var pos = (timeInMinutes - minH) * rd.sheetHeight / (maxH - minH);
444                        pos = Math.min(rd.sheetHeight - this.scrollContainer.offsetHeight, pos);
445                       
446                        this._scrollToPosition(pos, maxDuration, easing);
447                },
448               
449                _scrollToPosition: function(position, maxDuration, easing){
450                        // summary:
451                        //              Scrolls the view to show the specified first time of day.
452                        // position: Integer
453                        //              The position in pixels.
454                        // maxDuration: Integer
455                        //              The max duration of the scroll animation.
456                        // tags:
457                        //              protected
458                       
459                        if (maxDuration) {
460                               
461                                if(this._scrollAnimation){
462                                        this._scrollAnimation.stop();
463                                }
464                               
465                                var scrollPos = this._getScrollPosition();
466                               
467                                var duration = Math.abs(((position - scrollPos) * maxDuration) / this.renderData.sheetHeight);
468                               
469                                this._scrollAnimation = new fx.Animation({
470                                        curve: [scrollPos, position],
471                                        duration: duration,
472                                        easing: easing,
473                                        onAnimate: lang.hitch(this, function(position) {
474                                                this._setScrollImpl(position);
475                                        })
476                                });
477                                                               
478                                this._scrollAnimation.play();
479
480                        }else{
481                                this._setScrollImpl(position);
482                        }
483                },
484               
485                _setScrollImpl: function(v){
486                        this._setScrollPosition(v);
487                        if(this.scrollBar){
488                                this.scrollBar.set("value", v);
489                        }
490                },
491               
492                ensureVisibility: function(start, end, visibilityTarget, margin, duration){
493                       
494                        // summary:
495                        //              Scrolls the view if the [start, end] time range is not visible or only partially visible.
496                        // start: Date
497                        //              Start time of the range of interest.
498                        // end: Date
499                        //              End time of the range of interest.
500                        // margin: Integer
501                        //              Margin in minutes around the time range.
502                        // visibilityTarget: String
503                        //              The end(s) of the time range to make visible.
504                        //              Valid values are: "start", "end", "both".       
505                        // duration: Number
506                        //              Optional, the maximum duration of the scroll animation.
507                       
508                        margin = margin == undefined ? this.renderData.slotDuration : margin;
509                       
510                        if(this.scrollable && this.autoScroll){
511                               
512                                var s = start.getHours() * 60 + start.getMinutes() - margin;
513                                var e = end.getHours() * 60 + end.getMinutes() + margin;
514                               
515                                var vs = this._getStartTimeOfDay();
516                                var ve = this._getEndTimeOfDay();
517                               
518                                var viewStart = vs.hours * 60 + vs.minutes;
519                                var viewEnd = ve.hours * 60 + ve.minutes;
520                               
521                                var visible = false;
522                                var target = null;
523                               
524                                switch(visibilityTarget){
525                                        case "start":
526                                                visible = s >= viewStart && s <= viewEnd;
527                                                target = s ;
528                                                break;
529                                        case "end":
530                                                visible = e >= viewStart && e <= viewEnd;
531                                                target = e - (viewEnd - viewStart);
532                                                break;
533                                        case "both":
534                                                visible = s >= viewStart && e <= viewEnd;
535                                                target = s;
536                                                break;
537                                }
538
539                                if(!visible){
540                                        this._setStartTimeOfDay(Math.floor(target/60), target%60, duration);
541                                }
542                        }
543                },
544               
545                scrollView: function(dir){
546                        // summary:
547                        //              Scrolls the view to the specified direction of one time slot duration.
548                        // dir: Integer
549                        //              Direction of the scroll. Valid values are -1 and 1.
550                        //
551                        var t = this._getStartTimeOfDay();
552                        t = t.hours*60 + t.minutes + (dir * this.timeSlotDuration);
553                        this._setStartTimeOfDay(Math.floor(t/60), t%60);
554                },
555               
556                _mouseWheelScrollHander: function(e){
557                        // summary:
558                        //              Mouse wheel handler.
559                        // tags:
560                        //              protected
561                        this.scrollView(e.wheelDelta > 0 ? -1 : 1);
562                },             
563               
564                //////////////////////////////////////////
565                //
566                // HTML structure management
567                //
568                //////////////////////////////////////////             
569       
570                refreshRendering: function(){
571                        if(!this._initialized){
572                                return;
573                        }
574                                               
575                        this._validateProperties();
576
577                        var oldRd = this.renderData;
578                        var rd = this._createRenderData();
579                        this.renderData = rd;
580                        this._createRendering(rd, oldRd);
581                        this._layoutRenderers(rd);
582                },
583               
584                _createRendering: function(/*Object*/renderData, /*Object*/oldRenderData){
585                        // tags:
586                        //              private
587                        domStyle.set(this.sheetContainer, "height", renderData.sheetHeight + "px");
588                        // padding for the scroll bar.
589                        this._configureScrollBar(renderData);
590                        this._buildColumnHeader(renderData, oldRenderData);
591                        this._buildRowHeader(renderData, oldRenderData);
592                        this._buildGrid(renderData, oldRenderData);
593                        this._buildItemContainer(renderData, oldRenderData);
594                        this._commitProperties(renderData);
595                },
596               
597                _commitProperties: function(renderData){
598                        if(this._startTimeOfDayInvalidated){
599                                this._startTimeOfDayInvalidated = false;
600                                var v = this.startTimeOfDay;
601                                if(v != null){
602                                        this._setStartTimeOfDay(v.hours, v.minutes == undefined ? 0 : v.minutes); // initial position, no animation
603                                }
604                        }
605                },
606               
607                _configureScrollBar: function(renderData){
608                        // summary:
609                        //              Sets the scroll bar size and position.
610                        // renderData: Object
611                        //              The render data.
612                        // tags:
613                        //              protected
614
615                        if(has("ie") && this.scrollBar){
616                                domStyle.set(this.scrollBar.domNode, "width", (renderData.scrollbarWidth + 1) + "px");
617                        }
618                                               
619                        var atRight = this.isLeftToRight() ? true : this.scrollBarRTLPosition == "right";
620                        var rPos = atRight ? "right" : "left";
621                        var lPos = atRight? "left" : "right";
622                       
623                        if(this.scrollBar){
624                                this.scrollBar.set("maximum", renderData.sheetHeight);                 
625                                domStyle.set(this.scrollBar.domNode, rPos, 0);
626                                domStyle.set(this.scrollBar.domNode, atRight? "left" : "right", "auto");
627                        }
628                        domStyle.set(this.scrollContainer, rPos, renderData.scrollbarWidth + "px");
629                        domStyle.set(this.scrollContainer, lPos, "0");
630                        domStyle.set(this.header, rPos, renderData.scrollbarWidth + "px");
631                        domStyle.set(this.header, lPos, "0");
632                        if(this.buttonContainer && this.owner != null && this.owner.currentView == this){
633                                domStyle.set(this.buttonContainer, rPos, renderData.scrollbarWidth + "px");
634                                domStyle.set(this.buttonContainer, lPos, "0");
635                        }
636                },
637               
638                _columnHeaderClick: function(e){
639                        // tags:
640                        //              private
641
642                        event.stop(e);
643                        var index = query("td", this.columnHeaderTable).indexOf(e.currentTarget);
644                        this._onColumnHeaderClick({
645                                index: index,
646                                date: this.renderData.dates[index],
647                                triggerEvent: e
648                        });                                             
649                },
650               
651                _buildColumnHeader: function(renderData, oldRenderData){                               
652                        // summary:
653                        //              Creates incrementally the HTML structure of the column header and configures its content.
654                        //
655                        // renderData:
656                        //              The render data to display.
657                        //
658                        // oldRenderData:
659                        //              The previously render data displayed, if any.
660                        // tags:
661                        //              private
662
663                        var table = this.columnHeaderTable;
664                       
665                        if (!table){
666                                return;
667                        }
668                                       
669                        var count = renderData.columnCount - (oldRenderData ? oldRenderData.columnCount : 0);
670                       
671                        if(has("ie") == 8){
672                                // workaround Internet Explorer 8 bug.
673                                // if on the table, width: 100% and table-layout: fixed are set
674                                // and columns are removed, width of remaining columns is not
675                                // recomputed: must rebuild all.
676                                if(this._colTableSave == null){
677                                        this._colTableSave = lang.clone(table);
678                                }else if(count < 0){
679                                        this._cleanupColumnHeader();
680                                        this.columnHeader.removeChild(table);
681                                        domConstruct.destroy(table);
682                                        table = lang.clone(this._colTableSave);
683                                        this.columnHeaderTable = table;
684                                        this.columnHeader.appendChild(table);
685                                        count = renderData.columnCount;
686                                }
687                               
688                        } // else incremental dom add/remove for real browsers.
689                                       
690                        var tbodies = query("tbody", table);
691                       
692                        var trs = query("tr", table);
693                        var tbody, tr, td;
694                       
695                        if (tbodies.length == 1){
696                                tbody = tbodies[0];
697                        }else{
698                                tbody = html.create("tbody", null, table);
699                        }
700                       
701                        if (trs.length == 1){
702                                tr = trs[0];
703                        }else{
704                                tr = domConstruct.create("tr", null, tbody);
705                        }
706                                                 
707                        // Build HTML structure (incremental)
708                        if(count > 0){ // creation                             
709                                for(var i=0; i < count; i++){
710                                                                                                               
711                                        td = domConstruct.create("td", null, tr);
712                                       
713                                        var h = [];
714                                        h.push(on(td, "click", lang.hitch(this, this._columnHeaderClick)));
715                                                                               
716                                        if(has("touch")){                                       
717                                                h.push(on(td, "touchstart", function(e){
718                                                        event.stop(e);
719                                                        domClass.add(e.currentTarget, "Active");
720                                                }));
721                                               
722                                                h.push(on(td, "touchend", function(e){                 
723                                                        event.stop(e);                 
724                                                        domClass.remove(e.currentTarget, "Active");                     
725                                                }));
726                                        }else{
727                                                h.push(on(td, "mousedown", function(e){
728                                                        event.stop(e);
729                                                        domClass.add(e.currentTarget, "Active");
730                                                }));
731                                                                                               
732                                                h.push(on(td, "mouseup", function(e){
733                                                        event.stop(e);
734                                                        domClass.remove(e.currentTarget, "Active");
735                                                }));                                   
736                                               
737                                                h.push(on(td, "mouseover", function(e){
738                                                        event.stop(e);
739                                                        domClass.add(e.currentTarget, "Hover");
740                                                }));
741                                                                                       
742                                                h.push(on(td, "mouseout", function(e){
743                                                        event.stop(e);
744                                                        domClass.remove(e.currentTarget, "Hover");
745                                                }));
746                                       
747                                        }
748                                       
749                                        this._columnHeaderHandlers.push(h);
750                                         
751                                }
752                        }else{ // deletion
753                                count = -count;
754                                for(var i=0; i < count; i++){
755                                        td = tr.lastChild;
756                                        tr.removeChild(td);
757                                        domConstruct.destroy(td);
758                                        var list = this._columnHeaderHandlers.pop();
759                                        while(list.length>0){
760                                                list.pop().remove();
761                                        }
762                                }
763                        }
764                       
765                        // fill & configure             
766                        query("td", table).forEach(function(td, i){
767                                td.className = "";                                                                                     
768                                if(i == 0){
769                                        domClass.add(td, "first-child");
770                                }else if(i == this.renderData.columnCount-1){
771                                        domClass.add(td, "last-child");
772                                }
773                                var d = renderData.dates[i];
774                                this._setText(td, this._formatColumnHeaderLabel(d));
775                                this.styleColumnHeaderCell(td, d, renderData);                                         
776                        }, this);
777                       
778                        if(this.yearColumnHeaderContent){
779                                var d = renderData.dates[0];
780                                        this._setText(this.yearColumnHeaderContent, renderData.dateLocaleModule.format(d,
781                                                {selector: "date", datePattern:"yyyy"}));
782                        }
783                },
784               
785                _cleanupColumnHeader: function(){
786                        while(this._columnHeaderHandlers.length > 0){
787                                var list = this._columnHeaderHandlers.pop();
788                                while(list.length > 0){
789                                        list.pop().remove();
790                                }
791                        }
792                },
793               
794                styleColumnHeaderCell: function(node, date, renderData){
795                        // summary:
796                        //              Styles the CSS classes to the node that displays a column header cell.
797                        //              By default this method is setting:
798                        //              - "dojoxCalendarToday" class name if the date displayed is the current date,
799                        //              - "dojoxCalendarWeekend" if the date represents a weekend,
800                        //              - the CSS class corresponding of the displayed day of week ("Sun", "Mon" and so on).
801                        // node: Node
802                        //              The DOM node that displays the column in the grid.
803                        // date: Date
804                        //              The date displayed by this column
805                        // renderData: Object                   
806                        //              The render data.
807                        // tags:
808                        //              protected
809                       
810                        domClass.add(node, this._cssDays[date.getDay()]);
811
812                        if(this.isToday(date)){                         
813                                domClass.add(node, "dojoxCalendarToday");
814                        } else if(this.isWeekEnd(date)){
815                                domClass.add(node, "dojoxCalendarWeekend");
816                        }       
817                },
818
819        _addMinutesClasses: function(node, minutes){
820            switch(minutes){
821                case 0:
822                    domClass.add(node, "hour");
823                    break;
824                case 30:
825                    domClass.add(node, "halfhour");
826                    break;
827                case 15:
828                case 45:
829                    domClass.add(node, "quarterhour");
830                    break;
831            }
832        },
833               
834                _buildRowHeader: function(renderData, oldRenderData){
835
836                        // summary:
837                        //              Creates incrementally the HTML structure of the row header and configures its content.                 
838                        //
839                        // renderData:
840                        //              The render data to display.
841                        //
842                        // oldRenderData:
843                        //              The previously render data displayed, if any.
844                        // tags:
845                        //              private
846
847                       
848                        var rowHeaderTable = this.rowHeaderTable;
849                       
850                        if (!rowHeaderTable){
851                                return;
852                        }
853                       
854                        if(this._rowHeaderLabelContainer == null){
855                                this._rowHeaderLabelContainer = domConstruct.create("div", {"class": "dojoxCalendarRowHeaderLabelContainer"}, this.rowHeader);                           
856                        }
857                       
858                                               
859                        domStyle.set(rowHeaderTable, "height", renderData.sheetHeight + "px");
860                       
861                        var tbodies = query("tbody", rowHeaderTable);                   
862                        var tbody, tr, td;
863                       
864                        if (tbodies.length == 1){
865                                tbody = tbodies[0];
866                        }else{
867                                tbody = domConstruct.create("tbody", null, rowHeaderTable);
868                        }
869                                               
870                        var nbRows = Math.floor(60 / renderData.rowHeaderGridSlotDuration) * renderData.hourCount;
871                       
872                        var count = nbRows -
873                                (oldRenderData ? Math.floor(60 / oldRenderData.rowHeaderGridSlotDuration) * oldRenderData.hourCount : 0);
874               
875                        // Build HTML structure
876                        if(count>0){ // creation
877                                for(var i=0; i < count; i++){
878                                        tr = domConstruct.create("tr", null, tbody);
879                                        td = domConstruct.create("td", null, tr);                                               
880                                }                                       
881                        }else{
882                                count = -count;
883                                // deletion of existing nodes
884                                for(var i=0; i < count; i++){
885                                        tbody.removeChild(tbody.lastChild);
886                                }
887                        }               
888                                                               
889                        // fill labels
890                       
891                                               
892                        var rd = this.renderData;
893                        var size = Math.ceil(renderData.hourSize / (60 / renderData.rowHeaderGridSlotDuration));
894                        var d = new Date(2000, 0, 1, 0, 0, 0);
895                       
896                        query("tr", rowHeaderTable).forEach(function(tr, i){
897                                var td = query("td", tr)[0];                           
898                                td.className = "";                             
899                                                               
900                                domStyle.set(tr, "height", (has("ie") == 7)?size-2*(60 / renderData.rowHeaderGridSlotDuration):size + "px");
901                               
902                                this.styleRowHeaderCell(td, d.getHours(), d.getMinutes(), rd);
903                               
904                                var m = (i * this.renderData.rowHeaderGridSlotDuration) % 60;
905
906                this._addMinutesClasses(td, m);
907
908                        }, this);
909                       
910                        var lc = this._rowHeaderLabelContainer;                 
911                        count = (Math.floor(60 / this.rowHeaderLabelSlotDuration) * renderData.hourCount) - lc.childNodes.length;
912                       
913                        var span;
914                        if(count>0){ // creation
915                                for(var i=0; i < count; i++){
916                                        span = domConstruct.create("span", null, lc);
917                                        domClass.add(span, "dojoxCalendarRowHeaderLabel");
918                                }                                       
919                        }else{
920                                count = -count;
921                                // deletion of existing nodes
922                                for(var i=0; i < count; i++){
923                                        lc.removeChild(lc.lastChild);
924                                }
925                        }
926                       
927                        size = Math.ceil(renderData.hourSize / (60 / this.rowHeaderLabelSlotDuration));
928                                                               
929                        query(">span", lc).forEach(function(span, i){
930                                d.setHours(0);
931                                d.setMinutes(renderData.minHours * 60 + (i*this.rowHeaderLabelSlotDuration));
932                                this._configureRowHeaderLabel(span, d, i, size*i, rd);
933                        }, this);
934                       
935                },
936               
937                _configureRowHeaderLabel: function(node, d, index, pos, renderData){
938                        // summary:
939                        //              Configures the label of a row header cell.
940                        // node: DOMNode
941                        //              The DOM node that is the parent of the label.
942                        // d:Date
943                        //              A date object that contains the hours and minutes displayed by this row header cell.
944                        // index: Integer
945                        //              The index of this row header cell
946                        // pos: Integer
947                        //              The computed position of the row header cell
948                        // renderData: Object
949                        //              The render data.
950                       
951                        this._setText(node, this._formatRowHeaderLabel(d));
952                        domStyle.set(node, "top", (pos + (index==0?this.rowHeaderFirstLabelOffset:this.rowHeaderLabelOffset))+"px");
953                        var m = (index * this.rowHeaderLabelSlotDuration) % 60;
954                        domClass.remove(node, ["hour", "halfhour", "quarterhour"]);
955            this._addMinutesClasses(node, m);
956                },
957               
958                styleRowHeaderCell: function(node, h, m, renderData){
959                        // summary:
960                        //              Styles the CSS classes to the node that displays a row header cell.
961                        //              By default this method is doing nothing.
962                        // node: Node
963                        //              The DOM node that displays the column in the grid.
964                        // h: Integer
965                        //              The time of day displayed by this row header cell.
966                        // renderData: Object
967                        //              The render data.
968                        // tags:
969                        //              protected
970                },
971       
972                _buildGrid: function (renderData, oldRenderData){
973                        // summary:
974                        //              Creates incrementally the HTML structure of the grid and configures its content.
975                        //
976                        // renderData:
977                        //              The render data to display.
978                        //
979                        // oldRenderData:
980                        //              The previously render data displayed, if any.   
981                        // tags:
982                        //              private
983
984                                                                       
985                        var table = this.gridTable;
986                       
987                        if (!table){
988                                return;
989                        }
990                       
991                        domStyle.set(table, "height", renderData.sheetHeight + "px");                                                                                   
992                       
993                        var nbRows = Math.floor(60 / renderData.slotDuration) * renderData.hourCount;
994                       
995                        var rowDiff = nbRows -
996                                (oldRenderData ? Math.floor(60 / oldRenderData.slotDuration) * oldRenderData.hourCount : 0);
997                               
998                        var addRows = rowDiff > 0;
999                       
1000                        var colDiff  = renderData.columnCount - (oldRenderData ? oldRenderData.columnCount : 0);
1001                       
1002                        if(has("ie") == 8){
1003                                // workaround Internet Explorer 8 bug.
1004                                // if on the table, width: 100% and table-layout: fixed are set
1005                                // and columns are removed, width of remaining columns is not
1006                                // recomputed: must rebuild all.
1007                                if(this._gridTableSave == null){
1008                                        this._gridTableSave = lang.clone(table);
1009                                }else if(colDiff < 0){                                                                         
1010                                        this.grid.removeChild(table);
1011                                        domConstruct.destroy(table);
1012                                        table = lang.clone(this._gridTableSave);
1013                                        this.gridTable = table;
1014                                        this.grid.appendChild(table);
1015                                        colDiff = renderData.columnCount;
1016                                        rowDiff = nbRows;
1017                                        addRows = true;
1018                                }                               
1019                        }
1020                       
1021                        var tbodies = query("tbody", table);                   
1022                        var tbody;
1023                       
1024                        if (tbodies.length == 1){
1025                                tbody = tbodies[0];
1026                        }else{
1027                                tbody = domConstruct.create("tbody", null, table);
1028                        }
1029                       
1030                        // Build time slots (lines) HTML structure (incremental)
1031                        if(addRows){ // creation
1032                                for(var i=0; i<rowDiff; i++){
1033                                        domConstruct.create("tr", null, tbody);
1034                                }               
1035                        }else{ // deletion               
1036                                rowDiff = -rowDiff;
1037                                for(var i=0; i<rowDiff; i++){
1038                                        tbody.removeChild(tbody.lastChild);
1039                                }
1040                        }
1041                       
1042                        var rowIndex = Math.floor(60 / renderData.slotDuration) * renderData.hourCount - rowDiff;
1043                       
1044                        var addCols = addRows || colDiff >0;
1045                        colDiff = addCols ? colDiff : -colDiff;
1046                       
1047                        query("tr", table).forEach(function(tr, i){
1048                               
1049                                if(addCols){ // creation                               
1050                                        var len = i >= rowIndex ? renderData.columnCount : colDiff;                                                     
1051                                        for(var i=0; i<len; i++){
1052                                                domConstruct.create("td", null, tr);
1053                                        }
1054                                }else{ // deletion                                                             
1055                                        for(var i=0; i<colDiff; i++){
1056                                                tr.removeChild(tr.lastChild);
1057                                        }
1058                                }
1059                        });
1060                       
1061                        // Set the CSS classes
1062                       
1063                        query("tr", table).forEach(function (tr, i){
1064                               
1065                                domStyle.set(tr, "height", renderData.slotSize + "px");
1066                               
1067                                if(i == 0){
1068                                        domClass.add(tr, "first-child");
1069                                }else if(i == nbRows-1){
1070                                        domClass.add(tr, "last-child");
1071                                }
1072                               
1073                                // the minutes part of the time of day displayed by the current tr
1074                                var m = (i * this.renderData.slotDuration) % 60;
1075                                var h = this.minHours + Math.floor((i * this.renderData.slotDuration) / 60);
1076                                query("td", tr).forEach(function (td, col){
1077                                       
1078                                        td.className = "";
1079                                       
1080                                        if(col == 0){
1081                                                domClass.add(td, "first-child");
1082                                        }else if(col == this.renderData.columnCount-1){
1083                                                domClass.add(td, "last-child");
1084                                        }
1085                                       
1086                                        var d = renderData.dates[col];
1087                                       
1088                                        this.styleGridCell(td, d, h, m, renderData);
1089
1090                    this._addMinutesClasses(td, m);
1091
1092                                }, this);                               
1093                        }, this);
1094                                                                                                 
1095                },
1096               
1097                // styleGridCellFunc: Function
1098                //              Custom function to customize the appearance of a grid cell by installing custom CSS class on the node.
1099                //              The signature of the function must be the same then the styleGridCell one.
1100                //              By default the defaultStyleGridCell function is used.
1101                styleGridCellFunc: null,
1102                               
1103                defaultStyleGridCell: function(node, date, hours, minutes, renderData){
1104                        // summary:
1105                        //              Styles the CSS classes to the node that displays a cell.
1106                        //              By default this method is setting:
1107                        //              - "dojoxCalendarToday" class name if the date displayed is the current date,
1108                        //              - "dojoxCalendarWeekend" if the date represents a weekend,
1109                        //              - the CSS class corresponding of the displayed day of week ("Sun", "Mon" and so on),
1110                        //              - the CSS classes corresponfing to the time of day (e.g. "H14" and "M30" for for 2:30pm).   
1111                        // node: Node
1112                        //              The DOM node that displays the cell in the grid.
1113                        // date: Date
1114                        //              The date displayed by this cell.
1115                        // hours: Integer
1116                        //              The hours part of time of day displayed by the start of this cell.
1117                        // minutes: Integer
1118                        //              The minutes part of time of day displayed by the start of this cell.
1119                        // renderData: Object
1120                        //              The render data object.
1121                        // tags:
1122                        //              protected
1123                       
1124                        domClass.add(node, [this._cssDays[date.getDay()], "H"+hours, "M"+minutes]);
1125
1126                        if(this.isToday(date)){                         
1127                                return domClass.add(node, "dojoxCalendarToday");
1128                        } else if(this.isWeekEnd(date)){
1129                                return domClass.add(node, "dojoxCalendarWeekend");
1130                        }
1131                },
1132               
1133                styleGridCell: function(node, date, hours, minutes, renderData){
1134                        // summary:
1135                        //              Styles the CSS classes to the node that displays a cell.
1136                        //              Delegates to styleGridCellFunc if defined or defaultStyleGridCell otherwise.
1137                        // node: Node
1138                        //              The DOM node that displays the cell in the grid.
1139                        // date: Date
1140                        //              The date displayed by this column
1141                        // renderData: Object
1142                        //              The render data object.
1143                        // tags:
1144                        //              protected
1145
1146                        if(this.styleGridCellFunc){
1147                                this.styleGridCellFunc(node, date, hours, minutes, renderData);
1148                        }else{
1149                                this.defaultStyleGridCell(node, date, hours, minutes, renderData);
1150                        }
1151                },
1152                                                       
1153                _buildItemContainer: function(renderData, oldRenderData){
1154                        // summary:
1155                        //              Creates the HTML structure of the item container and configures its content.
1156                        // renderData:
1157                        //              The render data to display.
1158                        // oldRenderData:
1159                        //              The previously render data displayed, if any.
1160                        // tags:
1161                        //              private
1162
1163                        var table = this.itemContainerTable;
1164                       
1165                        if (!table){
1166                                return;
1167                        }
1168                       
1169                        var bgCols = [];
1170       
1171                        domStyle.set(table, "height", renderData.sheetHeight + "px");                   
1172                       
1173                        var count = renderData.columnCount - (oldRenderData ? oldRenderData.columnCount : 0);
1174                       
1175                        if(has("ie") == 8){
1176                                // workaround Internet Explorer 8 bug.
1177                                // if on the table, width: 100% and table-layout: fixed are set
1178                                // and columns are removed, width of remaining columns is not
1179                                // recomputed: must rebuild all.
1180                                if(this._itemTableSave == null){
1181                                        this._itemTableSave = lang.clone(table);
1182                                }else if(count < 0){
1183                                        this.itemContainer.removeChild(table);
1184                                        this._recycleItemRenderers(true);
1185                                        domConstruct.destroy(table);
1186                                        table = lang.clone(this._itemTableSave);
1187                                        this.itemContainerTable = table;
1188                                        this.itemContainer.appendChild(table);
1189                                        count = renderData.columnCount;
1190                                }
1191                               
1192                        } // else incremental dom add/remove for real browsers.
1193                       
1194                        var tbodies = query("tbody", table);
1195                        var trs = query("tr", table);
1196                        var tbody, tr, td;
1197                       
1198                        if (tbodies.length == 1){
1199                                tbody = tbodies[0];
1200                        }else{
1201                                tbody = domConstruct.create("tbody", null, table);
1202                        }
1203                       
1204                        if (trs.length == 1){
1205                                tr = trs[0];
1206                        }else{
1207                                tr = domConstruct.create("tr", null, tbody);
1208                        }                                       
1209                                                               
1210                        // Build HTML structure (incremental)
1211                        if(count>0){ // creation
1212                                for(var i=0; i < count; i++){
1213                                        td = domConstruct.create("td", null, tr);       
1214                                        domConstruct.create("div", {"className": "dojoxCalendarContainerColumn"}, td);
1215                                }
1216                        }else{ // deletion               
1217                                count = -count;
1218                                for(var i=0; i < count; i++){
1219                                        tr.removeChild(tr.lastChild);
1220                                }
1221                        }       
1222                       
1223                        query("td>div", table).forEach(function(div, i){
1224
1225                                domStyle.set(div, {
1226                                        "height": renderData.sheetHeight + "px"
1227                                });
1228                                bgCols.push(div);               
1229                        }, this);
1230                       
1231                        renderData.cells = bgCols;
1232                },                     
1233               
1234                ///////////////////////////////////////////////////////////////
1235                //
1236                // Layout
1237                //
1238                ///////////////////////////////////////////////////////////////
1239               
1240                _overlapLayoutPass2: function(lanes){
1241                        // summary:
1242                        //              Second pass of the overlap layout (optional). Compute the extent of each layout item.
1243                        // lanes:
1244                        //              The array of lanes.
1245                        // tags:
1246                        //              private
1247                        var i,j,lane, layoutItem;
1248                        // last lane, no extent possible
1249                        lane = lanes[lanes.length-1];
1250                       
1251                        for(j = 0; j < lane.length; j++){
1252                                lane[j].extent = 1;
1253                        }
1254                                               
1255                        for(i=0; i<lanes.length-1; i++){
1256                                lane = lanes[i];
1257                               
1258                                for(var j=0; j<lane.length; j++){       
1259                                        layoutItem = lane[j];
1260                                       
1261                                        // if item was already overlapping another one there is no extent possible.
1262                                        if(layoutItem.extent == -1){
1263                                                layoutItem.extent = 1;
1264                                                var space = 0;
1265                                               
1266                                                var stop = false;
1267                                               
1268                                                for(var k = i + 1; k < lanes.length && !stop; k++){
1269                                                        var ccol = lanes[k];
1270                                                        for(var l = 0; l < ccol.length && !stop; l++){
1271                                                                var layoutItem2 = ccol[l];
1272                                                               
1273                                                                if(layoutItem.start < layoutItem2.end && layoutItem2.start < layoutItem.end){
1274                                                                        stop = true;
1275                                                                }
1276                                                        }
1277                                                        if(!stop){
1278                                                                //no hit in the entire lane
1279                                                                space++;
1280                                                        }
1281                                                }
1282                                                layoutItem.extent += space;
1283                                        }
1284                                }
1285                        }
1286                },
1287               
1288                _defaultItemToRendererKindFunc: function(item){
1289                        // tags:
1290                        //              private
1291                        return "vertical"; // String
1292                },
1293               
1294                _layoutInterval: function(/*Object*/renderData, /*Integer*/index, /*Date*/start, /*Date*/end, /*Object[]*/items){
1295                        // tags:
1296                        //              private
1297
1298                        var verticalItems = [];
1299                        renderData.colW = this.itemContainer.offsetWidth / renderData.columnCount;
1300                       
1301                        for(var i=0; i<items.length; i++){
1302                                var item = items[i];
1303                                if(this._itemToRendererKind(item) == "vertical"){
1304                                        verticalItems.push(item);
1305                                }
1306                        }
1307                       
1308                        if(verticalItems.length > 0){
1309                                this._layoutVerticalItems(renderData, index, start, end, verticalItems);
1310                        }
1311                },
1312
1313                _layoutVerticalItems: function(/*Object*/renderData, /*Integer*/index, /*Date*/startTime, /*Date*/endTime, /*Object[]*/items){
1314                        // tags:
1315                        //              private
1316
1317                        if(this.verticalRenderer == null){
1318                                return;
1319                        }
1320                       
1321                        var cell = renderData.cells[index];
1322                        var layoutItems = [];                   
1323                       
1324                        // step 1 compute projected position and size
1325                        for(var i = 0; i < items.length; i++){
1326                               
1327                                var item = items[i];
1328                                var overlap = this.computeRangeOverlap(renderData, item.startTime, item.endTime, startTime, endTime);
1329                               
1330                                var top = this.computeProjectionOnDate(renderData, startTime, overlap[0], renderData.sheetHeight);
1331                                var bottom = this.computeProjectionOnDate(renderData, startTime, overlap[1], renderData.sheetHeight);
1332                               
1333                                if (bottom > top){
1334                                        var litem = lang.mixin({
1335                                                start: top,
1336                                                end: bottom,
1337                                                range: overlap,
1338                                                item: item
1339                                        }, item);
1340                                        layoutItems.push(litem);
1341                                }
1342                        }
1343                       
1344                        // step 2: compute overlapping layout
1345                        var numLanes = this.computeOverlapping(layoutItems, this._overlapLayoutPass2).numLanes;
1346
1347                        var hOverlap = this.percentOverlap / 100;
1348
1349                        // step 3: create renderers and apply layout
1350                        for(i=0; i<layoutItems.length; i++){
1351
1352                                item = layoutItems[i];                                 
1353                                var lane = item.lane;
1354                                var extent = item.extent;
1355
1356                                var w;
1357                                var posX;                               
1358
1359                                if(hOverlap == 0) {
1360                                        //no overlap and a padding between each event
1361                                        w = numLanes == 1 ? renderData.colW : ((renderData.colW - (numLanes - 1) * this.horizontalGap)/ numLanes);
1362                                        posX = lane * (w + this.horizontalGap);
1363                                        w = extent == 1 ? w : w * extent + (extent-1) * this.horizontalGap;
1364                                        w = 100 * w / renderData.colW;
1365                                        posX = 100 * posX / renderData.colW;
1366                                } else {
1367                                        // an overlap
1368                                        w = numLanes == 1 ? 100 : (100 / (numLanes - (numLanes - 1) * hOverlap));
1369                                        posX = lane * (w - hOverlap*w);
1370                                        w = extent == 1 ? w : w * ( extent - (extent-1) * hOverlap);
1371                                }
1372
1373                                var ir = this._createRenderer(item, "vertical", this.verticalRenderer, "dojoxCalendarVertical");
1374
1375                                domStyle.set(ir.container, {
1376                                        "top": item.start + "px",
1377                                        "left": posX + "%",
1378                                        "width": w + "%",
1379                                        "height": (item.end-item.start+1) + "px"
1380                                });
1381
1382                                var edited = this.isItemBeingEdited(item);
1383                                var selected = this.isItemSelected(item);
1384                                var hovered = this.isItemHovered(item);
1385                                var focused = this.isItemFocused(item);
1386                               
1387                                var renderer = ir.renderer;
1388
1389                                renderer.set("hovered", hovered);
1390                                renderer.set("selected", selected);
1391                                renderer.set("edited", edited);
1392                                renderer.set("focused", this.showFocus ? focused : false);
1393                                renderer.set("storeState", this.getItemStoreState(item));
1394                               
1395                                renderer.set("moveEnabled", this.isItemMoveEnabled(item._item, "vertical"));
1396                                renderer.set("resizeEnabled", this.isItemResizeEnabled(item._item, "vertical"));
1397
1398                                this.applyRendererZIndex(item, ir, hovered, selected, edited, focused);
1399
1400                                if(renderer.updateRendering){
1401                                        renderer.updateRendering(w, item.end-item.start+1);
1402                                }
1403
1404                                domConstruct.place(ir.container, cell);
1405                                domStyle.set(ir.container, "display", "block");
1406                        }
1407                },
1408               
1409                _sortItemsFunction: function(a, b){
1410                        // tags:
1411                        //              private
1412
1413                        var res = this.dateModule.compare(a.startTime, b.startTime);
1414                        if(res == 0){
1415                                res = -1 * this.dateModule.compare(a.endTime, b.endTime);
1416                        }
1417                        return this.isLeftToRight() ? res : -res;
1418                },
1419               
1420                ///////////////////////////////////////////////////////////////
1421                //
1422                // View to time projection
1423                //
1424                ///////////////////////////////////////////////////////////////
1425               
1426                getTime: function(e, x, y, touchIndex){
1427                        // summary:
1428                        //              Returns the time displayed at the specified point by this component.
1429                        // e: Event
1430                        //              Optional mouse event.
1431                        // x: Number
1432                        //              Position along the x-axis with respect to the sheet container used if event is not defined.
1433                        // y: Number
1434                        //              Position along the y-axis with respect to the sheet container (scroll included) used if event is not defined.
1435                        // touchIndex: Integer
1436                        //              If parameter 'e' is not null and a touch event, the index of the touch to use.
1437                        // returns: Date
1438                       
1439                        if (e != null){                         
1440                                var refPos = domGeometry.position(this.itemContainer, true);
1441                               
1442                                if(e.touches){                                                                 
1443                                       
1444                                        touchIndex = touchIndex==undefined ? 0 : touchIndex;
1445                                                                       
1446                                        x = e.touches[touchIndex].pageX - refPos.x;
1447                                        y = e.touches[touchIndex].pageY - refPos.y;                                                                     
1448                                       
1449                                }else{
1450                                       
1451                                        x = e.pageX - refPos.x;                                 
1452                                        y = e.pageY - refPos.y;                                 
1453                                }
1454                        }
1455                       
1456                        var r = domGeometry.getContentBox(this.itemContainer);
1457                       
1458                        if(!this.isLeftToRight()){
1459                                x = r.w - x;
1460                        }
1461                       
1462                        if (x < 0){
1463                                x = 0;
1464                        }else if(x > r.w){
1465                                x = r.w-1;
1466                        }
1467                       
1468                        if (y < 0){
1469                                y = 0;
1470                        }else if(y > r.h){
1471                                y = r.h-1;
1472                        }
1473                       
1474                        var col = Math.floor(x / (domGeometry.getMarginBox(this.itemContainer).w / this.renderData.columnCount));
1475                        var t = this.getTimeOfDay(y, this.renderData);
1476                       
1477                        var date = null;
1478                        if(col < this.renderData.dates.length){                 
1479                                date = this.newDate(this.renderData.dates[col]);
1480                                date = this.floorToDay(date, true);
1481                                date.setHours(t.hours);
1482                                date.setMinutes(t.minutes);
1483                        }
1484       
1485                        return date;
1486                },
1487               
1488                ///////////////////////////////////////////////////////////////
1489                //
1490                // Events
1491                //
1492                ///////////////////////////////////////////////////////////////
1493               
1494                _onGridMouseUp: function(e){
1495                        // tags:
1496                        //              private
1497
1498                        this.inherited(arguments);
1499                       
1500                        if (this._gridMouseDown) {
1501                                this._gridMouseDown = false;
1502                               
1503                                this._onGridClick({
1504                                        date: this.getTime(e),
1505                                        triggerEvent: e
1506                                });
1507                        }                       
1508                },                     
1509                       
1510                _onGridTouchStart: function(e){
1511                        // tags:
1512                        //              private
1513
1514                        this.inherited(arguments);
1515                       
1516                        var g = this._gridProps;
1517
1518                        g.moved= false;
1519                        g.start= e.touches[0].screenY;
1520                        g.scrollTop= this._getScrollPosition();
1521                },
1522               
1523                _onGridTouchMove: function(e){
1524                        // tags:
1525                        //              private
1526
1527                        this.inherited(arguments);                                             
1528                       
1529                        if (e.touches.length > 1 && !this._isEditing){
1530                                event.stop(e);                         
1531                                return;
1532                        }                       
1533                       
1534                        if(this._gridProps && !this._isEditing){
1535                               
1536                                var touch = {x: e.touches[0].screenX, y: e.touches[0].screenY};
1537                               
1538                                var p = this._edProps;
1539                               
1540                                if (!p || p &&
1541                                        (Math.abs(touch.x - p.start.x) > 25 ||
1542                                         Math.abs(touch.y - p.start.y) > 25)) {
1543                                                                                                                                               
1544                                        this._gridProps.moved = true;
1545                                        var d = e.touches[0].screenY - this._gridProps.start;
1546                                        var value = this._gridProps.scrollTop - d;
1547                                        var max = this.itemContainer.offsetHeight - this.scrollContainer.offsetHeight;
1548                                        if (value < 0){
1549                                                this._gridProps.start = e.touches[0].screenY;
1550                                                this._setScrollImpl(0);
1551                                                this._gridProps.scrollTop = 0;
1552                                        }else if(value > max){
1553                                                this._gridProps.start = e.touches[0].screenY;
1554                                                this._setScrollImpl(max);
1555                                                this._gridProps.scrollTop = max;
1556                                        }else{
1557                                                this._setScrollImpl(value);
1558                                        }
1559                                }
1560                        }
1561                },
1562               
1563                _onGridTouchEnd: function(e){
1564                        // tags:
1565                        //              private
1566
1567                        //event.stop(e);
1568                                                               
1569                        this.inherited(arguments);
1570                                                                       
1571                        var g = this._gridProps;                                       
1572                       
1573                        if(g){
1574                                if(!this._isEditing){
1575                                        if(!g.moved){
1576                                               
1577                                                // touched on grid and on touch start editing was ongoing.
1578                                                if(!g.fromItem && !g.editingOnStart){                                                           
1579                                                        this.selectFromEvent(e, null, null, true);
1580                                                }                       
1581                                               
1582                                                if(!g.fromItem){
1583                                               
1584                                                        if(this._pendingDoubleTap && this._pendingDoubleTap.grid){
1585                                                                                                                       
1586                                                                this._onGridDoubleClick({
1587                                                                        date: this.getTime(this._gridProps.event),
1588                                                                        triggerEvent: this._gridProps.event
1589                                                                });
1590                                                               
1591                                                                clearTimeout(this._pendingDoubleTap.timer);
1592                                               
1593                                                                delete this._pendingDoubleTap;
1594                                                               
1595                                                        }else{
1596                                                                                                                       
1597                                                                this._onGridClick({
1598                                                                        date: this.getTime(this._gridProps.event),
1599                                                                        triggerEvent: this._gridProps.event
1600                                                                });
1601                                                               
1602                                                                this._pendingDoubleTap = {
1603                                                                        grid: true,
1604                                                                        timer: setTimeout(lang.hitch(this, function(){
1605                                                                                        delete this._pendingDoubleTap;
1606                                                                        }), this.doubleTapDelay)
1607                                                                };
1608                                                        }
1609                                                }       
1610                                        }
1611                                }
1612                               
1613                                this._gridProps = null;
1614                        }
1615                },
1616               
1617                _onColumnHeaderClick: function(e){
1618                        // tags:
1619                        //              private
1620
1621                        this._dispatchCalendarEvt(e, "onColumnHeaderClick");
1622                },
1623               
1624               
1625               
1626                onColumnHeaderClick: function(e){
1627                        // summary:
1628                        //              Event dispatched when a column header cell is dispatched.
1629                        // e: __ColumnClickEventArgs
1630                        //              The event has the following properties
1631                        // tags:
1632                        //              callback                                       
1633                },
1634               
1635
1636                getTimeOfDay: function (pos, rd) {
1637                        // summary:
1638                        //              Return the time of day associated to the specified position.
1639                        // pos: Integer
1640                        //              The position in pixels.
1641                        // rd: Object
1642                        //              The render data.
1643                        var minH = rd.minHours*60;
1644                        var maxH = rd.maxHours*60;
1645                        var minutes = minH + (pos * (maxH - minH) / rd.sheetHeight);
1646                        return {
1647                                hours: Math.floor(minutes / 60),
1648                                minutes: Math.floor(minutes % 60)
1649                        };
1650                },
1651               
1652                ///////////////////////////////////////////////////////////////
1653                //
1654                // View limits
1655                //
1656                ///////////////////////////////////////////////////////////////
1657               
1658                _isItemInView: function(item){
1659                       
1660                        // subclassed to add some tests
1661                                                                       
1662                        var res = this.inherited(arguments);
1663                       
1664                        if(res){
1665                               
1666                                // test if time range is overlapping [maxHours, next day min hours]
1667                                var rd = this.renderData;
1668                               
1669                                var len = rd.dateModule.difference(item.startTime, item.endTime, "millisecond");
1670                                var vLen = (24 - rd.maxHours + rd.minHours) * 3600000; // 60 * 60 * 1000, number of milliseconds in 1 minute
1671                               
1672                                if(len > vLen){ // longer events are always visible
1673                                        return true;
1674                                }                                               
1675                               
1676                                var sMin = item.startTime.getHours()*60 + item.startTime.getMinutes();
1677                                var eMin = item.endTime.getHours()*60 + item.endTime.getMinutes();
1678                                var sV = rd.minHours * 60;
1679                                var eV = rd.maxHours * 60;
1680                               
1681                                if(sMin > 0 && sMin < sV || sMin > eV && sMin <= 1440){
1682                                        return false;
1683                                }
1684                               
1685                                if(eMin > 0 && eMin < sV || eMin > eV && eMin <= 1440){
1686                                        return false;
1687                                }                                                       
1688                        }
1689                        return res;
1690                },
1691                               
1692                _ensureItemInView: function(item){
1693                                                                                       
1694                        var fixed;
1695                       
1696                        var startTime = item.startTime;
1697                        var endTime = item.endTime;
1698                                                                       
1699                        // test if time range is overlapping [maxHours, next day min hours]
1700                        var rd = this.renderData;
1701                        var cal = rd.dateModule;
1702                       
1703                        var len = Math.abs(cal.difference(item.startTime, item.endTime, "millisecond"));
1704                        var vLen = (24 - rd.maxHours + rd.minHours) * 3600000;
1705                       
1706                        if(len > vLen){ // longer events are always visible
1707                                return false;
1708                        }                                               
1709                       
1710                        var sMin = startTime.getHours()*60 + startTime.getMinutes();
1711                        var eMin = endTime.getHours()*60 + endTime.getMinutes();
1712                        var sV = rd.minHours * 60;
1713                        var eV = rd.maxHours * 60;
1714                       
1715                        if(sMin > 0 && sMin < sV){
1716                                this.floorToDay(item.startTime, true, rd);
1717                                item.startTime.setHours(rd.minHours);
1718                                item.endTime = cal.add(item.startTime, "millisecond", len);
1719                                fixed = true;
1720                        }else if(sMin > eV && sMin <= 1440){
1721                                // go on next visible time
1722                                this.floorToDay(item.startTime, true, rd);
1723                                item.startTime = cal.add(item.startTime, "day", 1);
1724                                // if we are going out of the view, the super() will fix it
1725                                item.startTime.setHours(rd.minHours);
1726                                item.endTime = cal.add(item.startTime, "millisecond", len);
1727                                fixed = true;
1728                        }
1729                       
1730                        if(eMin > 0 && eMin < sV){
1731                                // go on previous day
1732                                this.floorToDay(item.endTime, true, rd);
1733                                item.endTime = cal.add(item.endTime, "day", -1);
1734                                item.endTime.setHours(rd.maxHours);
1735                                item.startTime = cal.add(item.endTime, "millisecond", -len);
1736                                fixed = true;
1737                        }else if(eMin > eV && eMin <= 1440){
1738                                this.floorToDay(item.endTime, true, rd);
1739                                item.endTime.setHours(rd.maxHours);
1740                                item.startTime = cal.add(item.endTime, "millisecond", -len);
1741                                fixed = true;
1742                        }                                                       
1743                       
1744                        fixed = fixed || this.inherited(arguments);
1745                       
1746                        return fixed;
1747                },
1748                               
1749                _onScrollTimer_tick: function(){
1750                        // tags:
1751                        //              private
1752
1753                        this._scrollToPosition(this._getScrollPosition() + this._scrollProps.scrollStep);
1754                },
1755               
1756                ////////////////////////////////////////////
1757                //
1758                // Editing
1759                //
1760                ///////////////////////////////////////////                                             
1761               
1762                snapUnit: "minute",
1763                snapSteps: 15,
1764                minDurationUnit: "minute",
1765                minDurationSteps: 15,
1766                liveLayout: false,
1767                stayInView: true,
1768                allowStartEndSwap: true,
1769                allowResizeLessThan24H: true
1770               
1771        });
1772});
Note: See TracBrowser for help on using the repository browser.