Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / calendar / calendar-all-debug.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 Ext.ns('Ext.calendar');
8
9  (function() {
10     Ext.apply(Ext.calendar, {
11         Date: {
12             diffDays: function(start, end) {
13                 day = 1000 * 60 * 60 * 24;
14                 diff = end.clearTime(true).getTime() - start.clearTime(true).getTime();
15                 return Math.ceil(diff / day);
16             },
17
18             copyTime: function(fromDt, toDt) {
19                 var dt = toDt.clone();
20                 dt.setHours(
21                 fromDt.getHours(),
22                 fromDt.getMinutes(),
23                 fromDt.getSeconds(),
24                 fromDt.getMilliseconds());
25
26                 return dt;
27             },
28
29             compare: function(dt1, dt2, precise) {
30                 if (precise !== true) {
31                     dt1 = dt1.clone();
32                     dt1.setMilliseconds(0);
33                     dt2 = dt2.clone();
34                     dt2.setMilliseconds(0);
35                 }
36                 return dt2.getTime() - dt1.getTime();
37             },
38
39             // private helper fn
40             maxOrMin: function(max) {
41                 var dt = (max ? 0: Number.MAX_VALUE),
42                 i = 0,
43                 args = arguments[1],
44                 ln = args.length;
45                 for (; i < ln; i++) {
46                     dt = Math[max ? 'max': 'min'](dt, args[i].getTime());
47                 }
48                 return new Date(dt);
49             },
50
51             max: function() {
52                 return this.maxOrMin.apply(this, [true, arguments]);
53             },
54
55             min: function() {
56                 return this.maxOrMin.apply(this, [false, arguments]);
57             }
58         }
59     });
60 })();/**
61  * @class Ext.calendar.DayHeaderTemplate
62  * @extends Ext.XTemplate
63  * <p>This is the template used to render the all-day event container used in {@link Ext.calendar.DayView DayView} and 
64  * {@link Ext.calendar.WeekView WeekView}. Internally the majority of the layout logic is deferred to an instance of
65  * {@link Ext.calendar.BoxLayoutTemplate}.</p> 
66  * <p>This template is automatically bound to the underlying event store by the 
67  * calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
68  * <p>Note that this template would not normally be used directly. Instead you would use the {@link Ext.calendar.DayViewTemplate}
69  * that internally creates an instance of this template along with a {@link Ext.calendar.DayBodyTemplate}.</p>
70  * @constructor
71  * @param {Object} config The config object
72  */
73 Ext.calendar.DayHeaderTemplate = function(config){
74     
75     Ext.apply(this, config);
76     
77     this.allDayTpl = new Ext.calendar.BoxLayoutTemplate(config);
78     this.allDayTpl.compile();
79     
80     Ext.calendar.DayHeaderTemplate.superclass.constructor.call(this,
81         '<div class="ext-cal-hd-ct">',
82             '<table class="ext-cal-hd-days-tbl" cellspacing="0" cellpadding="0">',
83                 '<tbody>',
84                     '<tr>',
85                         '<td class="ext-cal-gutter"></td>',
86                         '<td class="ext-cal-hd-days-td"><div class="ext-cal-hd-ad-inner">{allDayTpl}</div></td>',
87                         '<td class="ext-cal-gutter-rt"></td>',
88                     '</tr>',
89                 '</tobdy>',
90             '</table>',
91         '</div>'
92     );
93 };
94
95 Ext.extend(Ext.calendar.DayHeaderTemplate, Ext.XTemplate, {
96     applyTemplate : function(o){
97         return Ext.calendar.DayHeaderTemplate.superclass.applyTemplate.call(this, {
98             allDayTpl: this.allDayTpl.apply(o)
99         });
100     }
101 });
102
103 Ext.calendar.DayHeaderTemplate.prototype.apply = Ext.calendar.DayHeaderTemplate.prototype.applyTemplate;
104 /**
105  * @class Ext.calendar.DayBodyTemplate
106  * @extends Ext.XTemplate
107  * <p>This is the template used to render the scrolling body container used in {@link Ext.calendar.DayView DayView} and 
108  * {@link Ext.calendar.WeekView WeekView}. This template is automatically bound to the underlying event store by the 
109  * calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
110  * <p>Note that this template would not normally be used directly. Instead you would use the {@link Ext.calendar.DayViewTemplate}
111  * that internally creates an instance of this template along with a {@link Ext.calendar.DayHeaderTemplate}.</p>
112  * @constructor
113  * @param {Object} config The config object
114  */
115 Ext.calendar.DayBodyTemplate = function(config){
116     
117     Ext.apply(this, config);
118     
119     Ext.calendar.DayBodyTemplate.superclass.constructor.call(this,
120         '<table class="ext-cal-bg-tbl" cellspacing="0" cellpadding="0">',
121             '<tbody>',
122                 '<tr height="1">',
123                     '<td class="ext-cal-gutter"></td>',
124                     '<td colspan="{dayCount}">',
125                         '<div class="ext-cal-bg-rows">',
126                             '<div class="ext-cal-bg-rows-inner">',
127                                 '<tpl for="times">',
128                                     '<div class="ext-cal-bg-row">',
129                                         '<div class="ext-cal-bg-row-div ext-row-{[xindex]}"></div>',
130                                     '</div>',
131                                 '</tpl>',
132                             '</div>',
133                         '</div>',
134                     '</td>',
135                 '</tr>',
136                 '<tr>',
137                     '<td class="ext-cal-day-times">',
138                         '<tpl for="times">',
139                             '<div class="ext-cal-bg-row">',
140                                 '<div class="ext-cal-day-time-inner">{.}</div>',
141                             '</div>',
142                         '</tpl>',
143                     '</td>',
144                     '<tpl for="days">',
145                         '<td class="ext-cal-day-col">',
146                             '<div class="ext-cal-day-col-inner">',
147                                 '<div id="{[this.id]}-day-col-{.:date("Ymd")}" class="ext-cal-day-col-gutter"></div>',
148                             '</div>',
149                         '</td>',
150                     '</tpl>',
151                 '</tr>',
152             '</tbody>',
153         '</table>'
154     );
155 };
156
157 Ext.extend(Ext.calendar.DayBodyTemplate, Ext.XTemplate, {
158     // private
159     applyTemplate : function(o){
160         this.today = new Date().clearTime();
161         this.dayCount = this.dayCount || 1;
162         
163         var i = 0, days = [],
164             dt = o.viewStart.clone(),
165             times;
166             
167         for(; i<this.dayCount; i++){
168             days[i] = dt.add(Date.DAY, i);
169         }
170
171         times = [];
172         dt = new Date().clearTime();
173         for(i=0; i<24; i++){
174             times.push(dt.format('ga'));
175             dt = dt.add(Date.HOUR, 1);
176         }
177         
178         return Ext.calendar.DayBodyTemplate.superclass.applyTemplate.call(this, {
179             days: days,
180             dayCount: days.length,
181             times: times
182         });
183     }
184 });
185
186 Ext.calendar.DayBodyTemplate.prototype.apply = Ext.calendar.DayBodyTemplate.prototype.applyTemplate;
187 /**
188  * @class Ext.calendar.DayViewTemplate
189  * @extends Ext.XTemplate
190  * <p>This is the template used to render the all-day event container used in {@link Ext.calendar.DayView DayView} and 
191  * {@link Ext.calendar.WeekView WeekView}. Internally this class simply defers to instances of {@link Ext.calerndar.DayHeaderTemplate}
192  * and  {@link Ext.calerndar.DayBodyTemplate} to perform the actual rendering logic, but it also provides the overall calendar view
193  * container that contains them both.  As such this is the template that should be used when rendering day or week views.</p> 
194  * <p>This template is automatically bound to the underlying event store by the 
195  * calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
196  * @constructor
197  * @param {Object} config The config object
198  */
199 Ext.calendar.DayViewTemplate = function(config){
200     
201     Ext.apply(this, config);
202     
203     this.headerTpl = new Ext.calendar.DayHeaderTemplate(config);
204     this.headerTpl.compile();
205     
206     this.bodyTpl = new Ext.calendar.DayBodyTemplate(config);
207     this.bodyTpl.compile();
208     
209     Ext.calendar.DayViewTemplate.superclass.constructor.call(this,
210         '<div class="ext-cal-inner-ct">',
211             '{headerTpl}',
212             '{bodyTpl}',
213         '</div>'
214     );
215 };
216
217 Ext.extend(Ext.calendar.DayViewTemplate, Ext.XTemplate, {
218     // private
219     applyTemplate : function(o){
220         return Ext.calendar.DayViewTemplate.superclass.applyTemplate.call(this, {
221             headerTpl: this.headerTpl.apply(o),
222             bodyTpl: this.bodyTpl.apply(o)
223         });
224     }
225 });
226
227 Ext.calendar.DayViewTemplate.prototype.apply = Ext.calendar.DayViewTemplate.prototype.applyTemplate;
228 /**
229  * @class Ext.calendar.BoxLayoutTemplate
230  * @extends Ext.XTemplate
231  * <p>This is the template used to render calendar views based on small day boxes within a non-scrolling container (currently
232  * the {@link Ext.calendar.MonthView MonthView} and the all-day headers for {@link Ext.calendar.DayView DayView} and 
233  * {@link Ext.calendar.WeekView WeekView}. This template is automatically bound to the underlying event store by the 
234  * calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
235  * @constructor
236  * @param {Object} config The config object
237  */
238 Ext.calendar.BoxLayoutTemplate = function(config){
239     
240     Ext.apply(this, config);
241     
242     var weekLinkTpl = this.showWeekLinks ? '<div id="{weekLinkId}" class="ext-cal-week-link">{weekNum}</div>' : '';
243     
244     Ext.calendar.BoxLayoutTemplate.superclass.constructor.call(this,
245         '<tpl for="weeks">',
246             '<div id="{[this.id]}-wk-{[xindex-1]}" class="ext-cal-wk-ct" style="top:{[this.getRowTop(xindex, xcount)]}%; height:{[this.getRowHeight(xcount)]}%;">',
247                 weekLinkTpl,
248                 '<table class="ext-cal-bg-tbl" cellpadding="0" cellspacing="0">',
249                     '<tbody>',
250                         '<tr>',
251                             '<tpl for=".">',
252                                  '<td id="{[this.id]}-day-{date:date("Ymd")}" class="{cellCls}">&nbsp;</td>',
253                             '</tpl>',
254                         '</tr>',
255                     '</tbody>',
256                 '</table>',
257                 '<table class="ext-cal-evt-tbl" cellpadding="0" cellspacing="0">',
258                     '<tbody>',
259                         '<tr>',
260                             '<tpl for=".">',
261                                 '<td id="{[this.id]}-ev-day-{date:date("Ymd")}" class="{titleCls}"><div>{title}</div></td>',
262                             '</tpl>',
263                         '</tr>',
264                     '</tbody>',
265                 '</table>',
266             '</div>',
267         '</tpl>', {
268             getRowTop: function(i, ln){
269                 return ((i-1)*(100/ln));
270             },
271             getRowHeight: function(ln){
272                 return 100/ln;
273             }
274         }
275     );
276 };
277
278 Ext.extend(Ext.calendar.BoxLayoutTemplate, Ext.XTemplate, {
279     // private
280     applyTemplate : function(o){
281         
282         Ext.apply(this, o);
283         
284         var w = 0, title = '', first = true, isToday = false, showMonth = false, prevMonth = false, nextMonth = false,
285             weeks = [[]],
286             today = new Date().clearTime(),
287             dt = this.viewStart.clone(),
288             thisMonth = this.startDate.getMonth();
289         
290         for(; w < this.weekCount || this.weekCount == -1; w++){
291             if(dt > this.viewEnd){
292                 break;
293             }
294             weeks[w] = [];
295             
296             for(var d = 0; d < this.dayCount; d++){
297                 isToday = dt.getTime() === today.getTime();
298                 showMonth = first || (dt.getDate() == 1);
299                 prevMonth = (dt.getMonth() < thisMonth) && this.weekCount == -1;
300                 nextMonth = (dt.getMonth() > thisMonth) && this.weekCount == -1;
301                 
302                 if(dt.getDay() == 1){
303                     // The ISO week format 'W' is relative to a Monday week start. If we
304                     // make this check on Sunday the week number will be off.
305                     weeks[w].weekNum = this.showWeekNumbers ? dt.format('W') : '&nbsp;';
306                     weeks[w].weekLinkId = 'ext-cal-week-'+dt.format('Ymd');
307                 }
308                 
309                 if(showMonth){
310                     if(isToday){
311                         title = this.getTodayText();
312                     }
313                     else{
314                         title = dt.format(this.dayCount == 1 ? 'l, F j, Y' : (first ? 'M j, Y' : 'M j'));
315                     }
316                 }
317                 else{
318                     var dayFmt = (w == 0 && this.showHeader !== true) ? 'D j' : 'j';
319                     title = isToday ? this.getTodayText() : dt.format(dayFmt);
320                 }
321                 
322                 weeks[w].push({
323                     title: title,
324                     date: dt.clone(),
325                     titleCls: 'ext-cal-dtitle ' + (isToday ? ' ext-cal-dtitle-today' : '') + 
326                         (w==0 ? ' ext-cal-dtitle-first' : '') +
327                         (prevMonth ? ' ext-cal-dtitle-prev' : '') + 
328                         (nextMonth ? ' ext-cal-dtitle-next' : ''),
329                     cellCls: 'ext-cal-day ' + (isToday ? ' ext-cal-day-today' : '') + 
330                         (d==0 ? ' ext-cal-day-first' : '') +
331                         (prevMonth ? ' ext-cal-day-prev' : '') +
332                         (nextMonth ? ' ext-cal-day-next' : '')
333                 });
334                 dt = dt.add(Date.DAY, 1);
335                 first = false;
336             }
337         }
338         
339         return Ext.calendar.BoxLayoutTemplate.superclass.applyTemplate.call(this, {
340             weeks: weeks
341         });
342     },
343     
344     // private
345     getTodayText : function(){
346         var dt = new Date().format('l, F j, Y'),
347             todayText = this.showTodayText !== false ? this.todayText : '',
348             timeText = this.showTime !== false ? ' <span id="'+this.id+'-clock" class="ext-cal-dtitle-time">' + 
349                     new Date().format('g:i a') + '</span>' : '',
350             separator = todayText.length > 0 || timeText.length > 0 ? ' &mdash; ' : '';
351         
352         if(this.dayCount == 1){
353             return dt + separator + todayText + timeText;
354         }
355         fmt = this.weekCount == 1 ? 'D j' : 'j';
356         return todayText.length > 0 ? todayText + timeText : new Date().format(fmt) + timeText;
357     }
358 });
359
360 Ext.calendar.BoxLayoutTemplate.prototype.apply = Ext.calendar.BoxLayoutTemplate.prototype.applyTemplate;
361 /**\r
362  * @class Ext.calendar.MonthViewTemplate\r
363  * @extends Ext.XTemplate\r
364  * <p>This is the template used to render the {@link Ext.calendar.MonthView MonthView}. Internally this class defers to an\r
365  * instance of {@link Ext.calerndar.BoxLayoutTemplate} to handle the inner layout rendering and adds containing elements around\r
366  * that to form the month view.</p> \r
367  * <p>This template is automatically bound to the underlying event store by the \r
368  * calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>\r
369  * @constructor\r
370  * @param {Object} config The config object\r
371  */\r
372 Ext.calendar.MonthViewTemplate = function(config){\r
373     \r
374     Ext.apply(this, config);\r
375     \r
376     this.weekTpl = new Ext.calendar.BoxLayoutTemplate(config);\r
377     this.weekTpl.compile();\r
378     \r
379     var weekLinkTpl = this.showWeekLinks ? '<div class="ext-cal-week-link-hd">&nbsp;</div>' : '';\r
380     \r
381     Ext.calendar.MonthViewTemplate.superclass.constructor.call(this,\r
382         '<div class="ext-cal-inner-ct {extraClasses}">',\r
383             '<div class="ext-cal-hd-ct ext-cal-month-hd">',\r
384                 weekLinkTpl,\r
385                 '<table class="ext-cal-hd-days-tbl" cellpadding="0" cellspacing="0">',\r
386                     '<tbody>',\r
387                         '<tr>',\r
388                             '<tpl for="days">',\r
389                                 '<th class="ext-cal-hd-day{[xindex==1 ? " ext-cal-day-first" : ""]}" title="{.:date("l, F j, Y")}">{.:date("D")}</th>',\r
390                             '</tpl>',\r
391                         '</tr>',\r
392                     '</tbody>',\r
393                 '</table>',\r
394             '</div>',\r
395             '<div class="ext-cal-body-ct">{weeks}</div>',\r
396         '</div>'\r
397     );\r
398 };\r
399 \r
400 Ext.extend(Ext.calendar.MonthViewTemplate, Ext.XTemplate, {\r
401     // private\r
402     applyTemplate : function(o){\r
403         var days = [],\r
404             weeks = this.weekTpl.apply(o),\r
405             dt = o.viewStart;\r
406         \r
407         for(var i = 0; i < 7; i++){\r
408             days.push(dt.add(Date.DAY, i));\r
409         }\r
410         \r
411         var extraClasses = this.showHeader === true ? '' : 'ext-cal-noheader';\r
412         if(this.showWeekLinks){\r
413             extraClasses += ' ext-cal-week-links';\r
414         }\r
415         \r
416         return Ext.calendar.MonthViewTemplate.superclass.applyTemplate.call(this, {\r
417             days: days,\r
418             weeks: weeks,\r
419             extraClasses: extraClasses \r
420         });\r
421     }\r
422 });\r
423 \r
424 Ext.calendar.MonthViewTemplate.prototype.apply = Ext.calendar.MonthViewTemplate.prototype.applyTemplate;\r
425 /**
426  * @class Ext.dd.ScrollManager
427  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
428  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
429  * but you can also override most of the configs per scroll container by adding a 
430  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
431  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
432  * <pre><code>
433 var el = Ext.get('scroll-ct');
434 el.ddScrollConfig = {
435     vthresh: 50,
436     hthresh: -1,
437     frequency: 100,
438     increment: 200
439 };
440 Ext.dd.ScrollManager.register(el);
441 </code></pre>
442  * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
443  * @singleton
444  */
445 Ext.dd.ScrollManager = function() {
446     var ddm = Ext.dd.DragDropMgr,
447         els = {},
448         dragEl = null,
449         proc = {},
450         onStop = function(e) {
451             dragEl = null;
452             clearProc();
453         },
454         triggerRefresh = function() {
455             if (ddm.dragCurrent) {
456                 ddm.refreshCache(ddm.dragCurrent.groups);
457             }
458         },
459         doScroll = function() {
460             if (ddm.dragCurrent) {
461                 var dds = Ext.dd.ScrollManager,
462                     inc = proc.el.ddScrollConfig ? proc.el.ddScrollConfig.increment: dds.increment;
463                 if (!dds.animate) {
464                     if (proc.el.scroll(proc.dir, inc)) {
465                         triggerRefresh();
466                     }
467                 } else {
468                     proc.el.scroll(proc.dir, inc, true, dds.animDuration, triggerRefresh);
469                 }
470             }
471         },
472         clearProc = function() {
473             if (proc.id) {
474                 clearInterval(proc.id);
475             }
476             proc.id = 0;
477             proc.el = null;
478             proc.dir = "";
479         },
480         startProc = function(el, dir) {
481             clearProc();
482             proc.el = el;
483             proc.dir = dir;
484             var freq = (el.ddScrollConfig && el.ddScrollConfig.frequency) ?
485                             el.ddScrollConfig.frequency: Ext.dd.ScrollManager.frequency,
486                 group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup: undefined;
487
488             if (group === undefined || ddm.dragCurrent.ddGroup == group) {
489                 proc.id = setInterval(doScroll, freq);
490             }
491         },
492         onFire = function(e, isDrop) {
493             if (isDrop || !ddm.dragCurrent) {
494                 return;
495             }
496             var dds = Ext.dd.ScrollManager;
497             if (!dragEl || dragEl != ddm.dragCurrent) {
498                 dragEl = ddm.dragCurrent;
499                 // refresh regions on drag start
500                 dds.refreshCache();
501             }
502
503             var xy = Ext.lib.Event.getXY(e),
504                 pt = new Ext.lib.Point(xy[0], xy[1]),
505                 id,
506                 el,
507                 r,
508                 c;
509             for (id in els) {
510                 if (els.hasOwnProperty(id)) {
511                     el = els[id];
512                     r = el._region;
513                     c = el.ddScrollConfig ? el.ddScrollConfig: dds;
514                     if (r && r.contains(pt) && el.isScrollable()) {
515                         if (r.bottom - pt.y <= c.vthresh) {
516                             if (proc.el != el) {
517                                 startProc(el, "down");
518                             }
519                             return;
520                         } else if (r.right - pt.x <= c.hthresh) {
521                             if (proc.el != el) {
522                                 startProc(el, "left");
523                             }
524                             return;
525                         } else if (pt.y - r.top <= c.vthresh) {
526                             if (proc.el != el) {
527                                 startProc(el, "up");
528                             }
529                             return;
530                         } else if (pt.x - r.left <= c.hthresh) {
531                             if (proc.el != el) {
532                                 startProc(el, "right");
533                             }
534                             return;
535                         }
536                     }
537                 }
538             }
539             clearProc();
540         };
541
542     ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
543     ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
544
545     return {
546         /**
547          * Registers new overflow element(s) to auto scroll
548          * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
549          */
550         register: function(el) {
551             if (Ext.isArray(el)) {
552                 var i = 0,
553                     len = el.length;
554                 for (; i < len; i++) {
555                     this.register(el[i]);
556                 }
557             } else {
558                 el = Ext.get(el);
559                 els[el.id] = el;
560             }
561         },
562
563         /**
564          * Unregisters overflow element(s) so they are no longer scrolled
565          * @param {Mixed/Array} el The id of or the element to be removed or an array of either
566          */
567         unregister: function(el) {
568             if (Ext.isArray(el)) {
569                 var i = 0,
570                     len = el.length;
571                 for (; i < len; i++) {
572                     this.unregister(el[i]);
573                 }
574             } else {
575                 el = Ext.get(el);
576                 delete els[el.id];
577             }
578         },
579
580         /**
581          * The number of pixels from the top or bottom edge of a container the pointer needs to be to
582          * trigger scrolling (defaults to 25)
583          * @type Number
584          */
585         vthresh: 25,
586         /**
587          * The number of pixels from the right or left edge of a container the pointer needs to be to
588          * trigger scrolling (defaults to 25)
589          * @type Number
590          */
591         hthresh: 25,
592
593         /**
594          * The number of pixels to scroll in each scroll increment (defaults to 50)
595          * @type Number
596          */
597         increment: 100,
598
599         /**
600          * The frequency of scrolls in milliseconds (defaults to 500)
601          * @type Number
602          */
603         frequency: 500,
604
605         /**
606          * True to animate the scroll (defaults to true)
607          * @type Boolean
608          */
609         animate: true,
610
611         /**
612          * The animation duration in seconds - 
613          * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
614          * @type Number
615          */
616         animDuration: 0.4,
617
618         /**
619          * Manually trigger a cache refresh.
620          */
621         refreshCache: function() {
622             var id;
623             for (id in els) {
624                 if (els.hasOwnProperty(id)) {
625                     if (typeof els[id] == 'object') {
626                         // for people extending the object prototype
627                         els[id]._region = els[id].getRegion();
628                     }
629                 }
630             }
631         }
632     };
633 }();/*
634  * @class Ext.calendar.StatusProxy
635  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. It also
636  * contains a calendar-specific drag status message containing details about the dragged event's target drop date range.  
637  * This is the default drag proxy used by all calendar views.
638  * @constructor
639  * @param {Object} config
640  */
641 Ext.calendar.StatusProxy = function(config) {
642     Ext.apply(this, config);
643     this.id = this.id || Ext.id();
644     this.el = new Ext.Layer({
645         dh: {
646             id: this.id,
647             cls: 'ext-dd-drag-proxy x-dd-drag-proxy ' + this.dropNotAllowed,
648             cn: [
649             {
650                 cls: 'x-dd-drop-icon'
651             },
652             {
653                 cls: 'ext-dd-ghost-ct',
654                 cn: [
655                 {
656                     cls: 'x-dd-drag-ghost'
657                 },
658                 {
659                     cls: 'ext-dd-msg'
660                 }
661                 ]
662             }
663             ]
664         },
665         shadow: !config || config.shadow !== false
666     });
667     this.ghost = Ext.get(this.el.dom.childNodes[1].childNodes[0]);
668     this.message = Ext.get(this.el.dom.childNodes[1].childNodes[1]);
669     this.dropStatus = this.dropNotAllowed;
670 };
671
672 Ext.extend(Ext.calendar.StatusProxy, Ext.dd.StatusProxy, {
673     /**
674      * @cfg {String} moveEventCls
675      * The CSS class to apply to the status element when an event is being dragged (defaults to 'ext-cal-dd-move').
676      */
677     moveEventCls: 'ext-cal-dd-move',
678     /**
679      * @cfg {String} addEventCls
680      * The CSS class to apply to the status element when drop is not allowed (defaults to 'ext-cal-dd-add').
681      */
682     addEventCls: 'ext-cal-dd-add',
683
684     // inherit docs
685     update: function(html) {
686         if (typeof html == 'string') {
687             this.ghost.update(html);
688         } else {
689             this.ghost.update('');
690             html.style.margin = '0';
691             this.ghost.dom.appendChild(html);
692         }
693         var el = this.ghost.dom.firstChild;
694         if (el) {
695             Ext.fly(el).setStyle('float', 'none').setHeight('auto');
696             Ext.getDom(el).id += '-ddproxy';
697         }
698     },
699
700     /**
701      * Update the calendar-specific drag status message without altering the ghost element.
702      * @param {String} msg The new status message
703      */
704     updateMsg: function(msg) {
705         this.message.update(msg);
706     }
707 });/*
708  * Internal drag zone implementation for the calendar components. This provides base functionality
709  * and is primarily for the month view -- DayViewDD adds day/week view-specific functionality.
710  */
711 Ext.calendar.DragZone = Ext.extend(Ext.dd.DragZone, {
712     ddGroup: 'CalendarDD',
713     eventSelector: '.ext-cal-evt',
714
715     constructor: function(el, config) {
716         if (!Ext.calendar._statusProxyInstance) {
717             Ext.calendar._statusProxyInstance = new Ext.calendar.StatusProxy();
718         }
719         this.proxy = Ext.calendar._statusProxyInstance;
720         Ext.calendar.DragZone.superclass.constructor.call(this, el, config);
721     },
722
723     getDragData: function(e) {
724         // Check whether we are dragging on an event first
725         var t = e.getTarget(this.eventSelector, 3);
726         if (t) {
727             var rec = this.view.getEventRecordFromEl(t);
728             return {
729                 type: 'eventdrag',
730                 ddel: t,
731                 eventStart: rec.data[Ext.calendar.EventMappings.StartDate.name],
732                 eventEnd: rec.data[Ext.calendar.EventMappings.EndDate.name],
733                 proxy: this.proxy
734             };
735         }
736
737         // If not dragging an event then we are dragging on
738         // the calendar to add a new event
739         t = this.view.getDayAt(e.getPageX(), e.getPageY());
740         if (t.el) {
741             return {
742                 type: 'caldrag',
743                 start: t.date,
744                 proxy: this.proxy
745             };
746         }
747         return null;
748     },
749
750     onInitDrag: function(x, y) {
751         if (this.dragData.ddel) {
752             var ghost = this.dragData.ddel.cloneNode(true),
753             child = Ext.fly(ghost).child('dl');
754
755             Ext.fly(ghost).setWidth('auto');
756
757             if (child) {
758                 // for IE/Opera
759                 child.setHeight('auto');
760             }
761             this.proxy.update(ghost);
762             this.onStartDrag(x, y);
763         }
764         else if (this.dragData.start) {
765             this.onStartDrag(x, y);
766         }
767         this.view.onInitDrag();
768         return true;
769     },
770
771     afterRepair: function() {
772         if (Ext.enableFx && this.dragData.ddel) {
773             Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || 'c3daf9');
774         }
775         this.dragging = false;
776     },
777
778     getRepairXY: function(e) {
779         if (this.dragData.ddel) {
780             return Ext.Element.fly(this.dragData.ddel).getXY();
781         }
782     },
783
784     afterInvalidDrop: function(e, id) {
785         Ext.select('.ext-dd-shim').hide();
786     }
787 });
788
789 /*
790  * Internal drop zone implementation for the calendar components. This provides base functionality
791  * and is primarily for the month view -- DayViewDD adds day/week view-specific functionality.
792  */
793 Ext.calendar.DropZone = Ext.extend(Ext.dd.DropZone, {
794     ddGroup: 'CalendarDD',
795     eventSelector: '.ext-cal-evt',
796
797     // private
798     shims: [],
799
800     getTargetFromEvent: function(e) {
801         var dragOffset = this.dragOffset || 0,
802         y = e.getPageY() - dragOffset,
803         d = this.view.getDayAt(e.getPageX(), y);
804
805         return d.el ? d: null;
806     },
807
808     onNodeOver: function(n, dd, e, data) {
809         var D = Ext.calendar.Date,
810         start = data.type == 'eventdrag' ? n.date: D.min(data.start, n.date),
811         end = data.type == 'eventdrag' ? n.date.add(Date.DAY, D.diffDays(data.eventStart, data.eventEnd)) :
812         D.max(data.start, n.date);
813
814         if (!this.dragStartDate || !this.dragEndDate || (D.diffDays(start, this.dragStartDate) != 0) || (D.diffDays(end, this.dragEndDate) != 0)) {
815             this.dragStartDate = start;
816             this.dragEndDate = end.clearTime().add(Date.DAY, 1).add(Date.MILLI, -1);
817             this.shim(start, end);
818
819             var range = start.format('n/j');
820             if (D.diffDays(start, end) > 0) {
821                 range += '-' + end.format('n/j');
822             }
823             var msg = String.format(data.type == 'eventdrag' ? this.moveText: this.createText, range);
824             data.proxy.updateMsg(msg);
825         }
826         return this.dropAllowed;
827     },
828
829     shim: function(start, end) {
830         this.currWeek = -1;
831         var dt = start.clone(),
832             i = 0,
833             shim,
834             box,
835             cnt = Ext.calendar.Date.diffDays(dt, end) + 1;
836
837         Ext.each(this.shims,
838             function(shim) {
839                 if (shim) {
840                     shim.isActive = false;
841                 }
842             }
843         );
844
845         while (i++<cnt) {
846             var dayEl = this.view.getDayEl(dt);
847
848             // if the date is not in the current view ignore it (this
849             // can happen when an event is dragged to the end of the
850             // month so that it ends outside the view)
851             if (dayEl) {
852                 var wk = this.view.getWeekIndex(dt);
853                 shim = this.shims[wk];
854
855                 if (!shim) {
856                     shim = this.createShim();
857                     this.shims[wk] = shim;
858                 }
859                 if (wk != this.currWeek) {
860                     shim.boxInfo = dayEl.getBox();
861                     this.currWeek = wk;
862                 }
863                 else {
864                     box = dayEl.getBox();
865                     shim.boxInfo.right = box.right;
866                     shim.boxInfo.width = box.right - shim.boxInfo.x;
867                 }
868                 shim.isActive = true;
869             }
870             dt = dt.add(Date.DAY, 1);
871         }
872
873         Ext.each(this.shims,
874         function(shim) {
875             if (shim) {
876                 if (shim.isActive) {
877                     shim.show();
878                     shim.setBox(shim.boxInfo);
879                 }
880                 else if (shim.isVisible()) {
881                     shim.hide();
882                 }
883             }
884         });
885     },
886
887     createShim: function() {
888         if (!this.shimCt) {
889             this.shimCt = Ext.get('ext-dd-shim-ct');
890             if (!this.shimCt) {
891                 this.shimCt = document.createElement('div');
892                 this.shimCt.id = 'ext-dd-shim-ct';
893                 Ext.getBody().appendChild(this.shimCt);
894             }
895         }
896         var el = document.createElement('div');
897         el.className = 'ext-dd-shim';
898         this.shimCt.appendChild(el);
899
900         return new Ext.Layer({
901             shadow: false,
902             useDisplay: true,
903             constrain: false
904         },
905         el);
906     },
907
908     clearShims: function() {
909         Ext.each(this.shims,
910         function(shim) {
911             if (shim) {
912                 shim.hide();
913             }
914         });
915     },
916
917     onContainerOver: function(dd, e, data) {
918         return this.dropAllowed;
919     },
920
921     onCalendarDragComplete: function() {
922         delete this.dragStartDate;
923         delete this.dragEndDate;
924         this.clearShims();
925     },
926
927     onNodeDrop: function(n, dd, e, data) {
928         if (n && data) {
929             if (data.type == 'eventdrag') {
930                 var rec = this.view.getEventRecordFromEl(data.ddel),
931                 dt = Ext.calendar.Date.copyTime(rec.data[Ext.calendar.EventMappings.StartDate.name], n.date);
932
933                 this.view.onEventDrop(rec, dt);
934                 this.onCalendarDragComplete();
935                 return true;
936             }
937             if (data.type == 'caldrag') {
938                 this.view.onCalendarEndDrag(this.dragStartDate, this.dragEndDate,
939                 this.onCalendarDragComplete.createDelegate(this));
940                 //shims are NOT cleared here -- they stay visible until the handling
941                 //code calls the onCalendarDragComplete callback which hides them.
942                 return true;
943             }
944         }
945         this.onCalendarDragComplete();
946         return false;
947     },
948
949     onContainerDrop: function(dd, e, data) {
950         this.onCalendarDragComplete();
951         return false;
952     },
953
954     destroy: function() {
955         Ext.calendar.DropZone.superclass.destroy.call(this);
956         Ext.destroy(this.shimCt);
957     }
958 });
959
960 /*
961  * Internal drag zone implementation for the calendar day and week views.
962  */
963 Ext.calendar.DayViewDragZone = Ext.extend(Ext.calendar.DragZone, {
964     ddGroup: 'DayViewDD',
965     resizeSelector: '.ext-evt-rsz',
966
967     getDragData: function(e) {
968         var t = e.getTarget(this.resizeSelector, 2, true),
969             p,
970             rec;
971         if (t) {
972             p = t.parent(this.eventSelector);
973             rec = this.view.getEventRecordFromEl(p);
974
975             return {
976                 type: 'eventresize',
977                 ddel: p.dom,
978                 eventStart: rec.data[Ext.calendar.EventMappings.StartDate.name],
979                 eventEnd: rec.data[Ext.calendar.EventMappings.EndDate.name],
980                 proxy: this.proxy
981             };
982         }
983         t = e.getTarget(this.eventSelector, 3);
984         if (t) {
985             rec = this.view.getEventRecordFromEl(t);
986             return {
987                 type: 'eventdrag',
988                 ddel: t,
989                 eventStart: rec.data[Ext.calendar.EventMappings.StartDate.name],
990                 eventEnd: rec.data[Ext.calendar.EventMappings.EndDate.name],
991                 proxy: this.proxy
992             };
993         }
994
995         // If not dragging/resizing an event then we are dragging on
996         // the calendar to add a new event
997         t = this.view.getDayAt(e.getPageX(), e.getPageY());
998         if (t.el) {
999             return {
1000                 type: 'caldrag',
1001                 dayInfo: t,
1002                 proxy: this.proxy
1003             };
1004         }
1005         return null;
1006     }
1007 });
1008
1009 /*
1010  * Internal drop zone implementation for the calendar day and week views.
1011  */
1012 Ext.calendar.DayViewDropZone = Ext.extend(Ext.calendar.DropZone, {
1013     ddGroup: 'DayViewDD',
1014
1015     onNodeOver: function(n, dd, e, data) {
1016         var dt,
1017             box,
1018             endDt,
1019             text = this.createText,
1020             curr,
1021             start,
1022             end,
1023             evtEl,
1024             dayCol;
1025         if (data.type == 'caldrag') {
1026             if (!this.dragStartMarker) {
1027                 // Since the container can scroll, this gets a little tricky.
1028                 // There is no el in the DOM that we can measure by default since
1029                 // the box is simply calculated from the original drag start (as opposed
1030                 // to dragging or resizing the event where the orig event box is present).
1031                 // To work around this we add a placeholder el into the DOM and give it
1032                 // the original starting time's box so that we can grab its updated
1033                 // box measurements as the underlying container scrolls up or down.
1034                 // This placeholder is removed in onNodeDrop.
1035                 this.dragStartMarker = n.el.parent().createChild({
1036                     style: 'position:absolute;'
1037                 });
1038                 this.dragStartMarker.setBox(n.timeBox);
1039                 this.dragCreateDt = n.date;
1040             }
1041             box = this.dragStartMarker.getBox();
1042             box.height = Math.ceil(Math.abs(e.xy[1] - box.y) / n.timeBox.height) * n.timeBox.height;
1043
1044             if (e.xy[1] < box.y) {
1045                 box.height += n.timeBox.height;
1046                 box.y = box.y - box.height + n.timeBox.height;
1047                 endDt = this.dragCreateDt.add(Date.MINUTE, 30);
1048             }
1049             else {
1050                 n.date = n.date.add(Date.MINUTE, 30);
1051             }
1052             this.shim(this.dragCreateDt, box);
1053
1054             curr = Ext.calendar.Date.copyTime(n.date, this.dragCreateDt);
1055             this.dragStartDate = Ext.calendar.Date.min(this.dragCreateDt, curr);
1056             this.dragEndDate = endDt || Ext.calendar.Date.max(this.dragCreateDt, curr);
1057
1058             dt = this.dragStartDate.format('g:ia-') + this.dragEndDate.format('g:ia');
1059         }
1060         else {
1061             evtEl = Ext.get(data.ddel);
1062             dayCol = evtEl.parent().parent();
1063             box = evtEl.getBox();
1064
1065             box.width = dayCol.getWidth();
1066
1067             if (data.type == 'eventdrag') {
1068                 if (this.dragOffset === undefined) {
1069                     this.dragOffset = n.timeBox.y - box.y;
1070                     box.y = n.timeBox.y - this.dragOffset;
1071                 }
1072                 else {
1073                     box.y = n.timeBox.y;
1074                 }
1075                 dt = n.date.format('n/j g:ia');
1076                 box.x = n.el.getLeft();
1077
1078                 this.shim(n.date, box);
1079                 text = this.moveText;
1080             }
1081             if (data.type == 'eventresize') {
1082                 if (!this.resizeDt) {
1083                     this.resizeDt = n.date;
1084                 }
1085                 box.x = dayCol.getLeft();
1086                 box.height = Math.ceil(Math.abs(e.xy[1] - box.y) / n.timeBox.height) * n.timeBox.height;
1087                 if (e.xy[1] < box.y) {
1088                     box.y -= box.height;
1089                 }
1090                 else {
1091                     n.date = n.date.add(Date.MINUTE, 30);
1092                 }
1093                 this.shim(this.resizeDt, box);
1094
1095                 curr = Ext.calendar.Date.copyTime(n.date, this.resizeDt);
1096                 start = Ext.calendar.Date.min(data.eventStart, curr);
1097                 end = Ext.calendar.Date.max(data.eventStart, curr);
1098
1099                 data.resizeDates = {
1100                     StartDate: start,
1101                     EndDate: end
1102                 };
1103                 dt = start.format('g:ia-') + end.format('g:ia');
1104                 text = this.resizeText;
1105             }
1106         }
1107
1108         data.proxy.updateMsg(String.format(text, dt));
1109         return this.dropAllowed;
1110     },
1111
1112     shim: function(dt, box) {
1113         Ext.each(this.shims,
1114         function(shim) {
1115             if (shim) {
1116                 shim.isActive = false;
1117                 shim.hide();
1118             }
1119         });
1120
1121         var shim = this.shims[0];
1122         if (!shim) {
1123             shim = this.createShim();
1124             this.shims[0] = shim;
1125         }
1126
1127         shim.isActive = true;
1128         shim.show();
1129         shim.setBox(box);
1130     },
1131
1132     onNodeDrop: function(n, dd, e, data) {
1133         var rec;
1134         if (n && data) {
1135             if (data.type == 'eventdrag') {
1136                 rec = this.view.getEventRecordFromEl(data.ddel);
1137                 this.view.onEventDrop(rec, n.date);
1138                 this.onCalendarDragComplete();
1139                 delete this.dragOffset;
1140                 return true;
1141             }
1142             if (data.type == 'eventresize') {
1143                 rec = this.view.getEventRecordFromEl(data.ddel);
1144                 this.view.onEventResize(rec, data.resizeDates);
1145                 this.onCalendarDragComplete();
1146                 delete this.resizeDt;
1147                 return true;
1148             }
1149             if (data.type == 'caldrag') {
1150                 Ext.destroy(this.dragStartMarker);
1151                 delete this.dragStartMarker;
1152                 delete this.dragCreateDt;
1153                 this.view.onCalendarEndDrag(this.dragStartDate, this.dragEndDate,
1154                 this.onCalendarDragComplete.createDelegate(this));
1155                 //shims are NOT cleared here -- they stay visible until the handling
1156                 //code calls the onCalendarDragComplete callback which hides them.
1157                 return true;
1158             }
1159         }
1160         this.onCalendarDragComplete();
1161         return false;
1162     }
1163 });
1164 /**
1165  * @class Ext.calendar.EventMappings
1166  * @extends Object
1167  * A simple object that provides the field definitions for EventRecords so that they can be easily overridden.
1168  */
1169 Ext.calendar.EventMappings = {
1170     EventId: {
1171         name: 'EventId',
1172         mapping: 'id',
1173         type: 'int'
1174     },
1175     CalendarId: {
1176         name: 'CalendarId',
1177         mapping: 'cid',
1178         type: 'int'
1179     },
1180     Title: {
1181         name: 'Title',
1182         mapping: 'title',
1183         type: 'string'
1184     },
1185     StartDate: {
1186         name: 'StartDate',
1187         mapping: 'start',
1188         type: 'date',
1189         dateFormat: 'c'
1190     },
1191     EndDate: {
1192         name: 'EndDate',
1193         mapping: 'end',
1194         type: 'date',
1195         dateFormat: 'c'
1196     },
1197     Location: {
1198         name: 'Location',
1199         mapping: 'loc',
1200         type: 'string'
1201     },
1202     Notes: {
1203         name: 'Notes',
1204         mapping: 'notes',
1205         type: 'string'
1206     },
1207     Url: {
1208         name: 'Url',
1209         mapping: 'url',
1210         type: 'string'
1211     },
1212     IsAllDay: {
1213         name: 'IsAllDay',
1214         mapping: 'ad',
1215         type: 'boolean'
1216     },
1217     Reminder: {
1218         name: 'Reminder',
1219         mapping: 'rem',
1220         type: 'string'
1221     },
1222     IsNew: {
1223         name: 'IsNew',
1224         mapping: 'n',
1225         type: 'boolean'
1226     }
1227 };
1228
1229 /**
1230  * @class Ext.calendar.EventRecord
1231  * @extends Ext.data.Record
1232  * <p>This is the {@link Ext.data.Record Record} specification for calendar event data used by the
1233  * {@link Ext.calendar.CalendarPanel CalendarPanel}'s underlying store. It can be overridden as 
1234  * necessary to customize the fields supported by events, although the existing column names should
1235  * not be altered. If your model fields are named differently you should update the <b>mapping</b>
1236  * configs accordingly.</p>
1237  * <p>The only required fields when creating a new event record instance are StartDate and
1238  * EndDate.  All other fields are either optional are will be defaulted if blank.</p>
1239  * <p>Here is a basic example for how to create a new record of this type:<pre><code>
1240 rec = new Ext.calendar.EventRecord({
1241     StartDate: '2101-01-12 12:00:00',
1242     EndDate: '2101-01-12 13:30:00',
1243     Title: 'My cool event',
1244     Notes: 'Some notes'
1245 });
1246 </code></pre>
1247  * If you have overridden any of the record's data mappings via the Ext.calendar.EventMappings object
1248  * you may need to set the values using this alternate syntax to ensure that the fields match up correctly:<pre><code>
1249 var M = Ext.calendar.EventMappings;
1250
1251 rec = new Ext.calendar.EventRecord();
1252 rec.data[M.StartDate.name] = '2101-01-12 12:00:00';
1253 rec.data[M.EndDate.name] = '2101-01-12 13:30:00';
1254 rec.data[M.Title.name] = 'My cool event';
1255 rec.data[M.Notes.name] = 'Some notes';
1256 </code></pre>
1257  * @constructor
1258  * @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
1259  * fields. If not specified the {@link Ext.data.Field#defaultValue defaultValue}
1260  * for each field will be assigned.
1261  * @param {Object} id (Optional) The id of the Record. The id is used by the
1262  * {@link Ext.data.Store} object which owns the Record to index its collection
1263  * of Records (therefore this id should be unique within each store). If an
1264  * id is not specified a {@link #phantom}
1265  * Record will be created with an {@link #Record.id automatically generated id}.
1266  */
1267  (function() {
1268     var M = Ext.calendar.EventMappings;
1269
1270     Ext.calendar.EventRecord = Ext.data.Record.create([
1271     M.EventId,
1272     M.CalendarId,
1273     M.Title,
1274     M.StartDate,
1275     M.EndDate,
1276     M.Location,
1277     M.Notes,
1278     M.Url,
1279     M.IsAllDay,
1280     M.Reminder,
1281     M.IsNew
1282     ]);
1283
1284     /**
1285      * Reconfigures the default record definition based on the current Ext.calendar.EventMappings object
1286      */
1287     Ext.calendar.EventRecord.reconfigure = function() {
1288         Ext.calendar.EventRecord = Ext.data.Record.create([
1289         M.EventId,
1290         M.CalendarId,
1291         M.Title,
1292         M.StartDate,
1293         M.EndDate,
1294         M.Location,
1295         M.Notes,
1296         M.Url,
1297         M.IsAllDay,
1298         M.Reminder,
1299         M.IsNew
1300         ]);
1301     };
1302 })();
1303 /*\r
1304  * This is the view used internally by the panel that displays overflow events in the\r
1305  * month view. Anytime a day cell cannot display all of its events, it automatically displays\r
1306  * a link at the bottom to view all events for that day. When clicked, a panel pops up that\r
1307  * uses this view to display the events for that day.\r
1308  */\r
1309 Ext.calendar.MonthDayDetailView = Ext.extend(Ext.BoxComponent, {\r
1310     initComponent: function() {\r
1311         Ext.calendar.CalendarView.superclass.initComponent.call(this);\r
1312 \r
1313         this.addEvents({\r
1314             eventsrendered: true\r
1315         });\r
1316 \r
1317         if (!this.el) {\r
1318             this.el = document.createElement('div');\r
1319         }\r
1320     },\r
1321 \r
1322     afterRender: function() {\r
1323         this.tpl = this.getTemplate();\r
1324 \r
1325         Ext.calendar.MonthDayDetailView.superclass.afterRender.call(this);\r
1326 \r
1327         this.el.on({\r
1328             'click': this.view.onClick,\r
1329             'mouseover': this.view.onMouseOver,\r
1330             'mouseout': this.view.onMouseOut,\r
1331             scope: this.view\r
1332         });\r
1333     },\r
1334 \r
1335     getTemplate: function() {\r
1336         if (!this.tpl) {\r
1337             this.tpl = new Ext.XTemplate(\r
1338             '<div class="ext-cal-mdv x-unselectable">',\r
1339             '<table class="ext-cal-mvd-tbl" cellpadding="0" cellspacing="0">',\r
1340             '<tbody>',\r
1341             '<tpl for=".">',\r
1342             '<tr><td class="ext-cal-ev">{markup}</td></tr>',\r
1343             '</tpl>',\r
1344             '</tbody>',\r
1345             '</table>',\r
1346             '</div>'\r
1347             );\r
1348         }\r
1349         this.tpl.compile();\r
1350         return this.tpl;\r
1351     },\r
1352 \r
1353     update: function(dt) {\r
1354         this.date = dt;\r
1355         this.refresh();\r
1356     },\r
1357 \r
1358     refresh: function() {\r
1359         if (!this.rendered) {\r
1360             return;\r
1361         }\r
1362         var eventTpl = this.view.getEventTemplate(),\r
1363 \r
1364         templateData = [];\r
1365 \r
1366         evts = this.store.queryBy(function(rec) {\r
1367             var thisDt = this.date.clearTime(true).getTime(),\r
1368                 recStart = rec.data[Ext.calendar.EventMappings.StartDate.name].clearTime(true).getTime(),\r
1369                 startsOnDate = (thisDt == recStart),\r
1370                 spansDate = false;\r
1371 \r
1372             if (!startsOnDate) {\r
1373                 var recEnd = rec.data[Ext.calendar.EventMappings.EndDate.name].clearTime(true).getTime();\r
1374                 spansDate = recStart < thisDt && recEnd >= thisDt;\r
1375             }\r
1376             return startsOnDate || spansDate;\r
1377         },\r
1378         this);\r
1379 \r
1380         evts.each(function(evt) {\r
1381             var item = evt.data,\r
1382             M = Ext.calendar.EventMappings;\r
1383 \r
1384             item._renderAsAllDay = item[M.IsAllDay.name] || Ext.calendar.Date.diffDays(item[M.StartDate.name], item[M.EndDate.name]) > 0;\r
1385             item.spanLeft = Ext.calendar.Date.diffDays(item[M.StartDate.name], this.date) > 0;\r
1386             item.spanRight = Ext.calendar.Date.diffDays(this.date, item[M.EndDate.name]) > 0;\r
1387             item.spanCls = (item.spanLeft ? (item.spanRight ? 'ext-cal-ev-spanboth':\r
1388             'ext-cal-ev-spanleft') : (item.spanRight ? 'ext-cal-ev-spanright': ''));\r
1389 \r
1390             templateData.push({\r
1391                 markup: eventTpl.apply(this.getTemplateEventData(item))\r
1392             });\r
1393         },\r
1394         this);\r
1395 \r
1396         this.tpl.overwrite(this.el, templateData);\r
1397         this.fireEvent('eventsrendered', this, this.date, evts.getCount());\r
1398     },\r
1399 \r
1400     getTemplateEventData: function(evt) {\r
1401         var data = this.view.getTemplateEventData(evt);\r
1402         data._elId = 'dtl-' + data._elId;\r
1403         return data;\r
1404     }\r
1405 });\r
1406 \r
1407 Ext.reg('monthdaydetailview', Ext.calendar.MonthDayDetailView);\r
1408 /**
1409  * @class Ext.calendar.CalendarPicker
1410  * @extends Ext.form.ComboBox
1411  * <p>A custom combo used for choosing from the list of available calendars to assign an event to.</p>
1412  * <p>This is pretty much a standard combo that is simply pre-configured for the options needed by the
1413  * calendar components. The default configs are as follows:<pre><code>
1414     fieldLabel: 'Calendar',
1415     valueField: 'CalendarId',
1416     displayField: 'Title',
1417     triggerAction: 'all',
1418     mode: 'local',
1419     forceSelection: true,
1420     width: 200
1421 </code></pre>
1422  * @constructor
1423  * @param {Object} config The config object
1424  */
1425 Ext.calendar.CalendarPicker = Ext.extend(Ext.form.ComboBox, {
1426     fieldLabel: 'Calendar',
1427     valueField: 'CalendarId',
1428     displayField: 'Title',
1429     triggerAction: 'all',
1430     mode: 'local',
1431     forceSelection: true,
1432     width: 200,
1433
1434     // private
1435     initComponent: function() {
1436         Ext.calendar.CalendarPicker.superclass.initComponent.call(this);
1437         this.tpl = this.tpl ||
1438         '<tpl for="."><div class="x-combo-list-item ext-color-{' + this.valueField +
1439         '}"><div class="ext-cal-picker-icon">&nbsp;</div>{' + this.displayField + '}</div></tpl>';
1440     },
1441
1442     // private
1443     afterRender: function() {
1444         Ext.calendar.CalendarPicker.superclass.afterRender.call(this);
1445
1446         this.wrap = this.el.up('.x-form-field-wrap');
1447         this.wrap.addClass('ext-calendar-picker');
1448
1449         this.icon = Ext.DomHelper.append(this.wrap, {
1450             tag: 'div',
1451             cls: 'ext-cal-picker-icon ext-cal-picker-mainicon'
1452         });
1453     },
1454
1455     // inherited docs
1456     setValue: function(value) {
1457         this.wrap.removeClass('ext-color-' + this.getValue());
1458         if (!value && this.store !== undefined) {
1459             // always default to a valid calendar
1460             value = this.store.getAt(0).data.CalendarId;
1461         }
1462         Ext.calendar.CalendarPicker.superclass.setValue.call(this, value);
1463         this.wrap.addClass('ext-color-' + value);
1464     }
1465 });
1466
1467 Ext.reg('calendarpicker', Ext.calendar.CalendarPicker);
1468 /*
1469  * This is an internal helper class for the calendar views and should not be overridden.
1470  * It is responsible for the base event rendering logic underlying all of the calendar views.
1471  */
1472 Ext.calendar.WeekEventRenderer = function() {
1473
1474     var getEventRow = function(id, week, index) {
1475         var indexOffset = 1,
1476             //skip row with date #'s
1477             evtRow,
1478             wkRow = Ext.get(id + '-wk-' + week);
1479         if (wkRow) {
1480             var table = wkRow.child('.ext-cal-evt-tbl', true);
1481                 evtRow = table.tBodies[0].childNodes[index + indexOffset];
1482             if (!evtRow) {
1483                 evtRow = Ext.DomHelper.append(table.tBodies[0], '<tr></tr>');
1484             }
1485         }
1486         return Ext.get(evtRow);
1487     };
1488
1489     return {
1490         render: function(o) {
1491             var w = 0,
1492                 grid = o.eventGrid,
1493                 dt = o.viewStart.clone(),
1494                 eventTpl = o.tpl,
1495                 max = o.maxEventsPerDay != undefined ? o.maxEventsPerDay: 999,
1496                 weekCount = o.weekCount < 1 ? 6: o.weekCount,
1497                 dayCount = o.weekCount == 1 ? o.dayCount: 7,
1498                 cellCfg;
1499
1500             for (; w < weekCount; w++) {
1501                 if (!grid[w] || grid[w].length == 0) {
1502                     // no events or span cells for the entire week
1503                     if (weekCount == 1) {
1504                         row = getEventRow(o.id, w, 0);
1505                         cellCfg = {
1506                             tag: 'td',
1507                             cls: 'ext-cal-ev',
1508                             id: o.id + '-empty-0-day-' + dt.format('Ymd'),
1509                             html: '&nbsp;'
1510                         };
1511                         if (dayCount > 1) {
1512                             cellCfg.colspan = dayCount;
1513                         }
1514                         Ext.DomHelper.append(row, cellCfg);
1515                     }
1516                     dt = dt.add(Date.DAY, 7);
1517                 } else {
1518                     var row,
1519                         d = 0,
1520                         wk = grid[w],
1521                         startOfWeek = dt.clone(),
1522                         endOfWeek = startOfWeek.add(Date.DAY, dayCount).add(Date.MILLI, -1);
1523
1524                     for (; d < dayCount; d++) {
1525                         if (wk[d]) {
1526                             var ev = emptyCells = skipped = 0,
1527                                 day = wk[d],
1528                                 ct = day.length,
1529                                 evt;
1530
1531                             for (; ev < ct; ev++) {
1532                                 if (!day[ev]) {
1533                                     emptyCells++;
1534                                     continue;
1535                                 }
1536                                 if (emptyCells > 0 && ev - emptyCells < max) {
1537                                     row = getEventRow(o.id, w, ev - emptyCells);
1538                                     cellCfg = {
1539                                         tag: 'td',
1540                                         cls: 'ext-cal-ev',
1541                                         id: o.id + '-empty-' + ct + '-day-' + dt.format('Ymd')
1542                                     };
1543                                     if (emptyCells > 1 && max - ev > emptyCells) {
1544                                         cellCfg.rowspan = Math.min(emptyCells, max - ev);
1545                                     }
1546                                     Ext.DomHelper.append(row, cellCfg);
1547                                     emptyCells = 0;
1548                                 }
1549
1550                                 if (ev >= max) {
1551                                     skipped++;
1552                                     continue;
1553                                 }
1554                                 evt = day[ev];
1555
1556                                 if (!evt.isSpan || evt.isSpanStart) {
1557                                     //skip non-starting span cells
1558                                     var item = evt.data || evt.event.data;
1559                                     item._weekIndex = w;
1560                                     item._renderAsAllDay = item[Ext.calendar.EventMappings.IsAllDay.name] || evt.isSpanStart;
1561                                     item.spanLeft = item[Ext.calendar.EventMappings.StartDate.name].getTime() < startOfWeek.getTime();
1562                                     item.spanRight = item[Ext.calendar.EventMappings.EndDate.name].getTime() > endOfWeek.getTime();
1563                                     item.spanCls = (item.spanLeft ? (item.spanRight ? 'ext-cal-ev-spanboth':
1564                                     'ext-cal-ev-spanleft') : (item.spanRight ? 'ext-cal-ev-spanright': ''));
1565
1566                                     row = getEventRow(o.id, w, ev);
1567                                     cellCfg = {
1568                                         tag: 'td',
1569                                         cls: 'ext-cal-ev',
1570                                         cn: eventTpl.apply(o.templateDataFn(item))
1571                                     };
1572                                     var diff = Ext.calendar.Date.diffDays(dt, item[Ext.calendar.EventMappings.EndDate.name]) + 1,
1573                                         cspan = Math.min(diff, dayCount - d);
1574
1575                                     if (cspan > 1) {
1576                                         cellCfg.colspan = cspan;
1577                                     }
1578                                     Ext.DomHelper.append(row, cellCfg);
1579                                 }
1580                             }
1581                             if (ev > max) {
1582                                 row = getEventRow(o.id, w, max);
1583                                 Ext.DomHelper.append(row, {
1584                                     tag: 'td',
1585                                     cls: 'ext-cal-ev-more',
1586                                     id: 'ext-cal-ev-more-' + dt.format('Ymd'),
1587                                     cn: {
1588                                         tag: 'a',
1589                                         html: '+' + skipped + ' more...'
1590                                     }
1591                                 });
1592                             }
1593                             if (ct < o.evtMaxCount[w]) {
1594                                 row = getEventRow(o.id, w, ct);
1595                                 if (row) {
1596                                     cellCfg = {
1597                                         tag: 'td',
1598                                         cls: 'ext-cal-ev',
1599                                         id: o.id + '-empty-' + (ct + 1) + '-day-' + dt.format('Ymd')
1600                                     };
1601                                     var rowspan = o.evtMaxCount[w] - ct;
1602                                     if (rowspan > 1) {
1603                                         cellCfg.rowspan = rowspan;
1604                                     }
1605                                     Ext.DomHelper.append(row, cellCfg);
1606                                 }
1607                             }
1608                         } else {
1609                             row = getEventRow(o.id, w, 0);
1610                             if (row) {
1611                                 cellCfg = {
1612                                     tag: 'td',
1613                                     cls: 'ext-cal-ev',
1614                                     id: o.id + '-empty-day-' + dt.format('Ymd')
1615                                 };
1616                                 if (o.evtMaxCount[w] > 1) {
1617                                     cellCfg.rowSpan = o.evtMaxCount[w];
1618                                 }
1619                                 Ext.DomHelper.append(row, cellCfg);
1620                             }
1621                         }
1622                         dt = dt.add(Date.DAY, 1);
1623                     }
1624                 }
1625             }
1626         }
1627     };
1628 }();
1629 /**\r
1630  * @class Ext.calendar.CalendarView\r
1631  * @extends Ext.BoxComponent\r
1632  * <p>This is an abstract class that serves as the base for other calendar views. This class is not\r
1633  * intended to be directly instantiated.</p>\r
1634  * <p>When extending this class to create a custom calendar view, you must provide an implementation\r
1635  * for the <code>renderItems</code> method, as there is no default implementation for rendering events\r
1636  * The rendering logic is totally dependent on how the UI structures its data, which\r
1637  * is determined by the underlying UI template (this base class does not have a template).</p>\r
1638  * @constructor\r
1639  * @param {Object} config The config object\r
1640  */\r
1641 Ext.calendar.CalendarView = Ext.extend(Ext.BoxComponent, {\r
1642     /**\r
1643      * @cfg {Number} startDay\r
1644      * The 0-based index for the day on which the calendar week begins (0=Sunday, which is the default)\r
1645      */\r
1646     startDay: 0,\r
1647     /**\r
1648      * @cfg {Boolean} spansHavePriority\r
1649      * Allows switching between two different modes of rendering events that span multiple days. When true,\r
1650      * span events are always sorted first, possibly at the expense of start dates being out of order (e.g., \r
1651      * a span event that starts at 11am one day and spans into the next day would display before a non-spanning \r
1652      * event that starts at 10am, even though they would not be in date order). This can lead to more compact\r
1653      * layouts when there are many overlapping events. If false (the default), events will always sort by start date\r
1654      * first which can result in a less compact, but chronologically consistent layout.\r
1655      */\r
1656     spansHavePriority: false,\r
1657     /**\r
1658      * @cfg {Boolean} trackMouseOver\r
1659      * Whether or not the view tracks and responds to the browser mouseover event on contained elements (defaults to\r
1660      * true). If you don't need mouseover event highlighting you can disable this.\r
1661      */\r
1662     trackMouseOver: true,\r
1663     /**\r
1664      * @cfg {Boolean} enableFx\r
1665      * Determines whether or not visual effects for CRUD actions are enabled (defaults to true). If this is false\r
1666      * it will override any values for {@link #enableAddFx}, {@link #enableUpdateFx} or {@link enableRemoveFx} and\r
1667      * all animations will be disabled.\r
1668      */\r
1669     enableFx: true,\r
1670     /**\r
1671      * @cfg {Boolean} enableAddFx\r
1672      * True to enable a visual effect on adding a new event (the default), false to disable it. Note that if \r
1673      * {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the\r
1674      * {@link #doAddFx} method.\r
1675      */\r
1676     enableAddFx: true,\r
1677     /**\r
1678      * @cfg {Boolean} enableUpdateFx\r
1679      * True to enable a visual effect on updating an event, false to disable it (the default). Note that if \r
1680      * {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the\r
1681      * {@link #doUpdateFx} method.\r
1682      */\r
1683     enableUpdateFx: false,\r
1684     /**\r
1685      * @cfg {Boolean} enableRemoveFx\r
1686      * True to enable a visual effect on removing an event (the default), false to disable it. Note that if \r
1687      * {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the\r
1688      * {@link #doRemoveFx} method.\r
1689      */\r
1690     enableRemoveFx: true,\r
1691     /**\r
1692      * @cfg {Boolean} enableDD\r
1693      * True to enable drag and drop in the calendar view (the default), false to disable it\r
1694      */\r
1695     enableDD: true,\r
1696     /**\r
1697      * @cfg {Boolean} monitorResize\r
1698      * True to monitor the browser's resize event (the default), false to ignore it. If the calendar view is rendered\r
1699      * into a fixed-size container this can be set to false. However, if the view can change dimensions (e.g., it's in \r
1700      * fit layout in a viewport or some other resizable container) it is very important that this config is true so that\r
1701      * any resize event propagates properly to all subcomponents and layouts get recalculated properly.\r
1702      */\r
1703     monitorResize: true,\r
1704     /**\r
1705      * @cfg {String} ddCreateEventText\r
1706      * The text to display inside the drag proxy while dragging over the calendar to create a new event (defaults to \r
1707      * 'Create event for {0}' where {0} is a date range supplied by the view)\r
1708      */\r
1709     ddCreateEventText: 'Create event for {0}',\r
1710     /**\r
1711      * @cfg {String} ddMoveEventText\r
1712      * The text to display inside the drag proxy while dragging an event to reposition it (defaults to \r
1713      * 'Move event to {0}' where {0} is the updated event start date/time supplied by the view)\r
1714      */\r
1715     ddMoveEventText: 'Move event to {0}',\r
1716     /**\r
1717      * @cfg {String} ddResizeEventText\r
1718      * The string displayed to the user in the drag proxy while dragging the resize handle of an event (defaults to \r
1719      * 'Update event to {0}' where {0} is the updated event start-end range supplied by the view). Note that \r
1720      * this text is only used in views\r
1721      * that allow resizing of events.\r
1722      */\r
1723     ddResizeEventText: 'Update event to {0}',\r
1724 \r
1725     //private properties -- do not override:\r
1726     weekCount: 1,\r
1727     dayCount: 1,\r
1728     eventSelector: '.ext-cal-evt',\r
1729     eventOverClass: 'ext-evt-over',\r
1730     eventElIdDelimiter: '-evt-',\r
1731     dayElIdDelimiter: '-day-',\r
1732 \r
1733     /**\r
1734      * Returns a string of HTML template markup to be used as the body portion of the event template created\r
1735      * by {@link #getEventTemplate}. This provdes the flexibility to customize what's in the body without\r
1736      * having to override the entire XTemplate. This string can include any valid {@link Ext.Template} code, and\r
1737      * any data tokens accessible to the containing event template can be referenced in this string.\r
1738      * @return {String} The body template string\r
1739      */\r
1740     getEventBodyMarkup: Ext.emptyFn,\r
1741     // must be implemented by a subclass\r
1742     /**\r
1743      * <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type\r
1744      * {@link Ext.calendar.EventRecord}) to populate the calendar views with events. Internally this method\r
1745      * by default generates different markup for browsers that support CSS border radius and those that don't.\r
1746      * This method can be overridden as needed to customize the markup generated.</p>\r
1747      * <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately\r
1748      * from the surrounding container markup.  This provdes the flexibility to customize what's in the body without\r
1749      * having to override the entire XTemplate. If you do override this method, you should make sure that your \r
1750      * overridden version also does the same.</p>\r
1751      * @return {Ext.XTemplate} The event XTemplate\r
1752      */\r
1753     getEventTemplate: Ext.emptyFn,\r
1754     // must be implemented by a subclass\r
1755     // private\r
1756     initComponent: function() {\r
1757         this.setStartDate(this.startDate || new Date());\r
1758 \r
1759         Ext.calendar.CalendarView.superclass.initComponent.call(this);\r
1760 \r
1761         this.addEvents({\r
1762             /**\r
1763              * @event eventsrendered\r
1764              * Fires after events are finished rendering in the view\r
1765              * @param {Ext.calendar.CalendarView} this \r
1766              */\r
1767             eventsrendered: true,\r
1768             /**\r
1769              * @event eventclick\r
1770              * Fires after the user clicks on an event element\r
1771              * @param {Ext.calendar.CalendarView} this\r
1772              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on\r
1773              * @param {HTMLNode} el The DOM node that was clicked on\r
1774              */\r
1775             eventclick: true,\r
1776             /**\r
1777              * @event eventover\r
1778              * Fires anytime the mouse is over an event element\r
1779              * @param {Ext.calendar.CalendarView} this\r
1780              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over\r
1781              * @param {HTMLNode} el The DOM node that is being moused over\r
1782              */\r
1783             eventover: true,\r
1784             /**\r
1785              * @event eventout\r
1786              * Fires anytime the mouse exits an event element\r
1787              * @param {Ext.calendar.CalendarView} this\r
1788              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited\r
1789              * @param {HTMLNode} el The DOM node that was exited\r
1790              */\r
1791             eventout: true,\r
1792             /**\r
1793              * @event datechange\r
1794              * Fires after the start date of the view changes\r
1795              * @param {Ext.calendar.CalendarView} this\r
1796              * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}\r
1797              * @param {Date} viewStart The first displayed date in the view\r
1798              * @param {Date} viewEnd The last displayed date in the view\r
1799              */\r
1800             datechange: true,\r
1801             /**\r
1802              * @event rangeselect\r
1803              * Fires after the user drags on the calendar to select a range of dates/times in which to create an event\r
1804              * @param {Ext.calendar.CalendarView} this\r
1805              * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected\r
1806              * @param {Function} callback A callback function that MUST be called after the event handling is complete so that\r
1807              * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the\r
1808              * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard\r
1809              * function call (e.g., callback()).\r
1810              */\r
1811             rangeselect: true,\r
1812             /**\r
1813              * @event eventmove\r
1814              * Fires after an event element is dragged by the user and dropped in a new position\r
1815              * @param {Ext.calendar.CalendarView} this\r
1816              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with\r
1817              * updated start and end dates\r
1818              */\r
1819             eventmove: true,\r
1820             /**\r
1821              * @event initdrag\r
1822              * Fires when a drag operation is initiated in the view\r
1823              * @param {Ext.calendar.CalendarView} this\r
1824              */\r
1825             initdrag: true,\r
1826             /**\r
1827              * @event dayover\r
1828              * Fires while the mouse is over a day element \r
1829              * @param {Ext.calendar.CalendarView} this\r
1830              * @param {Date} dt The date that is being moused over\r
1831              * @param {Ext.Element} el The day Element that is being moused over\r
1832              */\r
1833             dayover: true,\r
1834             /**\r
1835              * @event dayout\r
1836              * Fires when the mouse exits a day element \r
1837              * @param {Ext.calendar.CalendarView} this\r
1838              * @param {Date} dt The date that is exited\r
1839              * @param {Ext.Element} el The day Element that is exited\r
1840              */\r
1841             dayout: true\r
1842             /*\r
1843              * @event eventdelete\r
1844              * Fires after an event element is deleted by the user. Not currently implemented directly at the view level -- currently \r
1845              * deletes only happen from one of the forms.\r
1846              * @param {Ext.calendar.CalendarView} this\r
1847              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was deleted\r
1848              */\r
1849             //eventdelete: true\r
1850         });\r
1851     },\r
1852 \r
1853     // private\r
1854     afterRender: function() {\r
1855         Ext.calendar.CalendarView.superclass.afterRender.call(this);\r
1856 \r
1857         this.renderTemplate();\r
1858 \r
1859         if (this.store) {\r
1860             this.setStore(this.store, true);\r
1861         }\r
1862 \r
1863         this.el.on({\r
1864             'mouseover': this.onMouseOver,\r
1865             'mouseout': this.onMouseOut,\r
1866             'click': this.onClick,\r
1867             'resize': this.onResize,\r
1868             scope: this\r
1869         });\r
1870 \r
1871         this.el.unselectable();\r
1872 \r
1873         if (this.enableDD && this.initDD) {\r
1874             this.initDD();\r
1875         }\r
1876 \r
1877         this.on('eventsrendered', this.forceSize);\r
1878         this.forceSize.defer(100, this);\r
1879 \r
1880     },\r
1881 \r
1882     // private\r
1883     forceSize: function() {\r
1884         if (this.el && this.el.child) {\r
1885             var hd = this.el.child('.ext-cal-hd-ct'),\r
1886             bd = this.el.child('.ext-cal-body-ct');\r
1887 \r
1888             if (bd == null || hd == null) return;\r
1889 \r
1890             var headerHeight = hd.getHeight(),\r
1891             sz = this.el.parent().getSize();\r
1892 \r
1893             bd.setHeight(sz.height - headerHeight);\r
1894         }\r
1895     },\r
1896 \r
1897     refresh: function() {\r
1898         this.prepareData();\r
1899         this.renderTemplate();\r
1900         this.renderItems();\r
1901     },\r
1902 \r
1903     getWeekCount: function() {\r
1904         var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd);\r
1905         return Math.ceil(days / this.dayCount);\r
1906     },\r
1907 \r
1908     // private\r
1909     prepareData: function() {\r
1910         var lastInMonth = this.startDate.getLastDateOfMonth(),\r
1911         w = 0,\r
1912         row = 0,\r
1913         dt = this.viewStart.clone(),\r
1914         weeks = this.weekCount < 1 ? 6: this.weekCount;\r
1915 \r
1916         this.eventGrid = [[]];\r
1917         this.allDayGrid = [[]];\r
1918         this.evtMaxCount = [];\r
1919 \r
1920         var evtsInView = this.store.queryBy(function(rec) {\r
1921             return this.isEventVisible(rec.data);\r
1922         },\r
1923         this);\r
1924 \r
1925         for (; w < weeks; w++) {\r
1926             this.evtMaxCount[w] = 0;\r
1927             if (this.weekCount == -1 && dt > lastInMonth) {\r
1928                 //current week is fully in next month so skip\r
1929                 break;\r
1930             }\r
1931             this.eventGrid[w] = this.eventGrid[w] || [];\r
1932             this.allDayGrid[w] = this.allDayGrid[w] || [];\r
1933 \r
1934             for (d = 0; d < this.dayCount; d++) {\r
1935                 if (evtsInView.getCount() > 0) {\r
1936                     var evts = evtsInView.filterBy(function(rec) {\r
1937                         var startsOnDate = (dt.getTime() == rec.data[Ext.calendar.EventMappings.StartDate.name].clearTime(true).getTime());\r
1938                         var spansFromPrevView = (w == 0 && d == 0 && (dt > rec.data[Ext.calendar.EventMappings.StartDate.name]));\r
1939                         return startsOnDate || spansFromPrevView;\r
1940                     },\r
1941                     this);\r
1942 \r
1943                     this.sortEventRecordsForDay(evts);\r
1944                     this.prepareEventGrid(evts, w, d);\r
1945                 }\r
1946                 dt = dt.add(Date.DAY, 1);\r
1947             }\r
1948         }\r
1949         this.currentWeekCount = w;\r
1950     },\r
1951 \r
1952     // private\r
1953     prepareEventGrid: function(evts, w, d) {\r
1954         var row = 0,\r
1955         dt = this.viewStart.clone(),\r
1956         max = this.maxEventsPerDay ? this.maxEventsPerDay: 999;\r
1957 \r
1958         evts.each(function(evt) {\r
1959             var M = Ext.calendar.EventMappings,\r
1960             days = Ext.calendar.Date.diffDays(\r
1961             Ext.calendar.Date.max(this.viewStart, evt.data[M.StartDate.name]),\r
1962             Ext.calendar.Date.min(this.viewEnd, evt.data[M.EndDate.name])) + 1;\r
1963 \r
1964             if (days > 1 || Ext.calendar.Date.diffDays(evt.data[M.StartDate.name], evt.data[M.EndDate.name]) > 1) {\r
1965                 this.prepareEventGridSpans(evt, this.eventGrid, w, d, days);\r
1966                 this.prepareEventGridSpans(evt, this.allDayGrid, w, d, days, true);\r
1967             } else {\r
1968                 row = this.findEmptyRowIndex(w, d);\r
1969                 this.eventGrid[w][d] = this.eventGrid[w][d] || [];\r
1970                 this.eventGrid[w][d][row] = evt;\r
1971 \r
1972                 if (evt.data[M.IsAllDay.name]) {\r
1973                     row = this.findEmptyRowIndex(w, d, true);\r
1974                     this.allDayGrid[w][d] = this.allDayGrid[w][d] || [];\r
1975                     this.allDayGrid[w][d][row] = evt;\r
1976                 }\r
1977             }\r
1978 \r
1979             if (this.evtMaxCount[w] < this.eventGrid[w][d].length) {\r
1980                 this.evtMaxCount[w] = Math.min(max + 1, this.eventGrid[w][d].length);\r
1981             }\r
1982             return true;\r
1983         },\r
1984         this);\r
1985     },\r
1986 \r
1987     // private\r
1988     prepareEventGridSpans: function(evt, grid, w, d, days, allday) {\r
1989         // this event spans multiple days/weeks, so we have to preprocess\r
1990         // the events and store special span events as placeholders so that\r
1991         // the render routine can build the necessary TD spans correctly.\r
1992         var w1 = w,\r
1993         d1 = d,\r
1994         row = this.findEmptyRowIndex(w, d, allday),\r
1995         dt = this.viewStart.clone();\r
1996 \r
1997         var start = {\r
1998             event: evt,\r
1999             isSpan: true,\r
2000             isSpanStart: true,\r
2001             spanLeft: false,\r
2002             spanRight: (d == 6)\r
2003         };\r
2004         grid[w][d] = grid[w][d] || [];\r
2005         grid[w][d][row] = start;\r
2006 \r
2007         while (--days) {\r
2008             dt = dt.add(Date.DAY, 1);\r
2009             if (dt > this.viewEnd) {\r
2010                 break;\r
2011             }\r
2012             if (++d1 > 6) {\r
2013                 // reset counters to the next week\r
2014                 d1 = 0;\r
2015                 w1++;\r
2016                 row = this.findEmptyRowIndex(w1, 0);\r
2017             }\r
2018             grid[w1] = grid[w1] || [];\r
2019             grid[w1][d1] = grid[w1][d1] || [];\r
2020 \r
2021             grid[w1][d1][row] = {\r
2022                 event: evt,\r
2023                 isSpan: true,\r
2024                 isSpanStart: (d1 == 0),\r
2025                 spanLeft: (w1 > w) && (d1 % 7 == 0),\r
2026                 spanRight: (d1 == 6) && (days > 1)\r
2027             };\r
2028         }\r
2029     },\r
2030 \r
2031     // private\r
2032     findEmptyRowIndex: function(w, d, allday) {\r
2033         var grid = allday ? this.allDayGrid: this.eventGrid,\r
2034         day = grid[w] ? grid[w][d] || [] : [],\r
2035         i = 0,\r
2036         ln = day.length;\r
2037 \r
2038         for (; i < ln; i++) {\r
2039             if (day[i] == null) {\r
2040                 return i;\r
2041             }\r
2042         }\r
2043         return ln;\r
2044     },\r
2045 \r
2046     // private\r
2047     renderTemplate: function() {\r
2048         if (this.tpl) {\r
2049             this.tpl.overwrite(this.el, this.getParams());\r
2050             this.lastRenderStart = this.viewStart.clone();\r
2051             this.lastRenderEnd = this.viewEnd.clone();\r
2052         }\r
2053     },\r
2054 \r
2055     disableStoreEvents: function() {\r
2056         this.monitorStoreEvents = false;\r
2057     },\r
2058 \r
2059     enableStoreEvents: function(refresh) {\r
2060         this.monitorStoreEvents = true;\r
2061         if (refresh === true) {\r
2062             this.refresh();\r
2063         }\r
2064     },\r
2065 \r
2066     // private\r
2067     onResize: function() {\r
2068         this.refresh();\r
2069     },\r
2070 \r
2071     // private\r
2072     onInitDrag: function() {\r
2073         this.fireEvent('initdrag', this);\r
2074     },\r
2075 \r
2076     // private\r
2077     onEventDrop: function(rec, dt) {\r
2078         if (Ext.calendar.Date.compare(rec.data[Ext.calendar.EventMappings.StartDate.name], dt) === 0) {\r
2079             // no changes\r
2080             return;\r
2081         }\r
2082         var diff = dt.getTime() - rec.data[Ext.calendar.EventMappings.StartDate.name].getTime();\r
2083         rec.set(Ext.calendar.EventMappings.StartDate.name, dt);\r
2084         rec.set(Ext.calendar.EventMappings.EndDate.name, rec.data[Ext.calendar.EventMappings.EndDate.name].add(Date.MILLI, diff));\r
2085 \r
2086         this.fireEvent('eventmove', this, rec);\r
2087     },\r
2088 \r
2089     // private\r
2090     onCalendarEndDrag: function(start, end, onComplete) {\r
2091         // set this flag for other event handlers that might conflict while we're waiting\r
2092         this.dragPending = true;\r
2093 \r
2094         // have to wait for the user to save or cancel before finalizing the dd interation\r
2095         var o = {};\r
2096         o[Ext.calendar.EventMappings.StartDate.name] = start;\r
2097         o[Ext.calendar.EventMappings.EndDate.name] = end;\r
2098 \r
2099         this.fireEvent('rangeselect', this, o, this.onCalendarEndDragComplete.createDelegate(this, [onComplete]));\r
2100     },\r
2101 \r
2102     // private\r
2103     onCalendarEndDragComplete: function(onComplete) {\r
2104         // callback for the drop zone to clean up\r
2105         onComplete();\r
2106         // clear flag for other events to resume normally\r
2107         this.dragPending = false;\r
2108     },\r
2109 \r
2110     // private\r
2111     onUpdate: function(ds, rec, operation) {\r
2112         if (this.monitorStoreEvents === false) {\r
2113             return;\r
2114         }\r
2115         if (operation == Ext.data.Record.COMMIT) {\r
2116             this.refresh();\r
2117             if (this.enableFx && this.enableUpdateFx) {\r
2118                 this.doUpdateFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {\r
2119                     scope: this\r
2120                 });\r
2121             }\r
2122         }\r
2123     },\r
2124 \r
2125 \r
2126     doUpdateFx: function(els, o) {\r
2127         this.highlightEvent(els, null, o);\r
2128     },\r
2129 \r
2130     // private\r
2131     onAdd: function(ds, records, index) {\r
2132         if (this.monitorStoreEvents === false) {\r
2133             return;\r
2134         }\r
2135         var rec = records[0];\r
2136         this.tempEventId = rec.id;\r
2137         this.refresh();\r
2138 \r
2139         if (this.enableFx && this.enableAddFx) {\r
2140             this.doAddFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {\r
2141                 scope: this\r
2142             });\r
2143         };\r
2144     },\r
2145 \r
2146     doAddFx: function(els, o) {\r
2147         els.fadeIn(Ext.apply(o, {\r
2148             duration: 2\r
2149         }));\r
2150     },\r
2151 \r
2152     // private\r
2153     onRemove: function(ds, rec) {\r
2154         if (this.monitorStoreEvents === false) {\r
2155             return;\r
2156         }\r
2157         if (this.enableFx && this.enableRemoveFx) {\r
2158             this.doRemoveFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {\r
2159                 remove: true,\r
2160                 scope: this,\r
2161                 callback: this.refresh\r
2162             });\r
2163         }\r
2164         else {\r
2165             this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]).remove();\r
2166             this.refresh();\r
2167         }\r
2168     },\r
2169 \r
2170     doRemoveFx: function(els, o) {\r
2171         els.fadeOut(o);\r
2172     },\r
2173 \r
2174     /**\r
2175      * Visually highlights an event using {@link Ext.Fx#highlight} config options.\r
2176      * If {@link #highlightEventActions} is false this method will have no effect.\r
2177      * @param {Ext.CompositeElement} els The element(s) to highlight\r
2178      * @param {Object} color (optional) The highlight color. Should be a 6 char hex \r
2179      * color without the leading # (defaults to yellow: 'ffff9c')\r
2180      * @param {Object} o (optional) Object literal with any of the {@link Ext.Fx} config \r
2181      * options. See {@link Ext.Fx#highlight} for usage examples.\r
2182      */\r
2183     highlightEvent: function(els, color, o) {\r
2184         if (this.enableFx) {\r
2185             var c;\r
2186             ! (Ext.isIE || Ext.isOpera) ?\r
2187             els.highlight(color, o) :\r
2188             // Fun IE/Opera handling:\r
2189             els.each(function(el) {\r
2190                 el.highlight(color, Ext.applyIf({\r
2191                     attr: 'color'\r
2192                 },\r
2193                 o));\r
2194                 c = el.child('.ext-cal-evm');\r
2195                 if (c) {\r
2196                     c.highlight(color, o);\r
2197                 }\r
2198             },\r
2199             this);\r
2200         }\r
2201     },\r
2202 \r
2203     /**\r
2204      * Retrieve an Event object's id from its corresponding node in the DOM.\r
2205      * @param {String/Element/HTMLElement} el An {@link Ext.Element}, DOM node or id\r
2206      */\r
2207     getEventIdFromEl: function(el) {\r
2208         el = Ext.get(el);\r
2209         var id = el.id.split(this.eventElIdDelimiter)[1];\r
2210         if (id.indexOf('-') > -1) {\r
2211             //This id has the index of the week it is rendered in as the suffix.\r
2212             //This allows events that span across weeks to still have reproducibly-unique DOM ids.\r
2213             id = id.split('-')[0];\r
2214         }\r
2215         return id;\r
2216     },\r
2217 \r
2218     // private\r
2219     getEventId: function(eventId) {\r
2220         if (eventId === undefined && this.tempEventId) {\r
2221             eventId = this.tempEventId;\r
2222         }\r
2223         return eventId;\r
2224     },\r
2225 \r
2226     /**\r
2227      * \r
2228      * @param {String} eventId\r
2229      * @param {Boolean} forSelect\r
2230      * @return {String} The selector class\r
2231      */\r
2232     getEventSelectorCls: function(eventId, forSelect) {\r
2233         var prefix = forSelect ? '.': '';\r
2234         return prefix + this.id + this.eventElIdDelimiter + this.getEventId(eventId);\r
2235     },\r
2236 \r
2237     /**\r
2238      * \r
2239      * @param {String} eventId\r
2240      * @return {Ext.CompositeElement} The matching CompositeElement of nodes\r
2241      * that comprise the rendered event.  Any event that spans across a view \r
2242      * boundary will contain more than one internal Element.\r
2243      */\r
2244     getEventEls: function(eventId) {\r
2245         var els = Ext.select(this.getEventSelectorCls(this.getEventId(eventId), true), false, this.el.id);\r
2246         return new Ext.CompositeElement(els);\r
2247     },\r
2248 \r
2249     /**\r
2250      * Returns true if the view is currently displaying today's date, else false.\r
2251      * @return {Boolean} True or false\r
2252      */\r
2253     isToday: function() {\r
2254         var today = new Date().clearTime().getTime();\r
2255         return this.viewStart.getTime() <= today && this.viewEnd.getTime() >= today;\r
2256     },\r
2257 \r
2258     // private\r
2259     onDataChanged: function(store) {\r
2260         this.refresh();\r
2261     },\r
2262 \r
2263     // private\r
2264     isEventVisible: function(evt) {\r
2265         var start = this.viewStart.getTime(),\r
2266         end = this.viewEnd.getTime(),\r
2267         M = Ext.calendar.EventMappings,\r
2268         evStart = (evt.data ? evt.data[M.StartDate.name] : evt[M.StartDate.name]).getTime(),\r
2269         evEnd = (evt.data ? evt.data[M.EndDate.name] : evt[M.EndDate.name]).add(Date.SECOND, -1).getTime(),\r
2270 \r
2271         startsInRange = (evStart >= start && evStart <= end),\r
2272         endsInRange = (evEnd >= start && evEnd <= end),\r
2273         spansRange = (evStart < start && evEnd > end);\r
2274 \r
2275         return (startsInRange || endsInRange || spansRange);\r
2276     },\r
2277 \r
2278     // private\r
2279     isOverlapping: function(evt1, evt2) {\r
2280         var ev1 = evt1.data ? evt1.data: evt1,\r
2281         ev2 = evt2.data ? evt2.data: evt2,\r
2282         M = Ext.calendar.EventMappings,\r
2283         start1 = ev1[M.StartDate.name].getTime(),\r
2284         end1 = ev1[M.EndDate.name].add(Date.SECOND, -1).getTime(),\r
2285         start2 = ev2[M.StartDate.name].getTime(),\r
2286         end2 = ev2[M.EndDate.name].add(Date.SECOND, -1).getTime();\r
2287 \r
2288         if (end1 < start1) {\r
2289             end1 = start1;\r
2290         }\r
2291         if (end2 < start2) {\r
2292             end2 = start2;\r
2293         }\r
2294 \r
2295         var ev1startsInEv2 = (start1 >= start2 && start1 <= end2),\r
2296         ev1EndsInEv2 = (end1 >= start2 && end1 <= end2),\r
2297         ev1SpansEv2 = (start1 < start2 && end1 > end2);\r
2298 \r
2299         return (ev1startsInEv2 || ev1EndsInEv2 || ev1SpansEv2);\r
2300     },\r
2301 \r
2302     getDayEl: function(dt) {\r
2303         return Ext.get(this.getDayId(dt));\r
2304     },\r
2305 \r
2306     getDayId: function(dt) {\r
2307         if (Ext.isDate(dt)) {\r
2308             dt = dt.format('Ymd');\r
2309         }\r
2310         return this.id + this.dayElIdDelimiter + dt;\r
2311     },\r
2312 \r
2313     /**\r
2314      * Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not \r
2315      * be the first date displayed in the rendered calendar -- to get the start and end dates displayed\r
2316      * to the user use {@link #getViewBounds}.\r
2317      * @return {Date} The start date\r
2318      */\r
2319     getStartDate: function() {\r
2320         return this.startDate;\r
2321     },\r
2322 \r
2323     /**\r
2324      * Sets the start date used to calculate the view boundaries to display. The displayed view will be the \r
2325      * earliest and latest dates that match the view requirements and contain the date passed to this function.\r
2326      * @param {Date} dt The date used to calculate the new view boundaries\r
2327      */\r
2328     setStartDate: function(start, refresh) {\r
2329         this.startDate = start.clearTime();\r
2330         this.setViewBounds(start);\r
2331         this.store.load({\r
2332             params: {\r
2333                 start: this.viewStart.format('m-d-Y'),\r
2334                 end: this.viewEnd.format('m-d-Y')\r
2335             }\r
2336         });\r
2337         if (refresh === true) {\r
2338             this.refresh();\r
2339         }\r
2340         this.fireEvent('datechange', this, this.startDate, this.viewStart, this.viewEnd);\r
2341     },\r
2342 \r
2343     // private\r
2344     setViewBounds: function(startDate) {\r
2345         var start = startDate || this.startDate,\r
2346         offset = start.getDay() - this.startDay;\r
2347 \r
2348         switch (this.weekCount) {\r
2349         case 0:\r
2350         case 1:\r
2351             this.viewStart = this.dayCount < 7 ? start: start.add(Date.DAY, -offset).clearTime(true);\r
2352             this.viewEnd = this.viewStart.add(Date.DAY, this.dayCount || 7).add(Date.SECOND, -1);\r
2353             return;\r
2354 \r
2355         case - 1:\r
2356             // auto by month\r
2357             start = start.getFirstDateOfMonth();\r
2358             offset = start.getDay() - this.startDay;\r
2359             if (offset < 0) {\r
2360                 offset += 7;\r
2361             }\r
2362             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
2363 \r
2364             // start from current month start, not view start:\r
2365             var end = start.add(Date.MONTH, 1).add(Date.SECOND, -1);\r
2366             // fill out to the end of the week:\r
2367             this.viewEnd = end.add(Date.DAY, 6 - end.getDay());\r
2368             return;\r
2369 \r
2370         default:\r
2371             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
2372             this.viewEnd = this.viewStart.add(Date.DAY, this.weekCount * 7).add(Date.SECOND, -1);\r
2373         }\r
2374     },\r
2375 \r
2376     // private\r
2377     getViewBounds: function() {\r
2378         return {\r
2379             start: this.viewStart,\r
2380             end: this.viewEnd\r
2381         };\r
2382     },\r
2383 \r
2384     /* private\r
2385      * Sort events for a single day for display in the calendar.  This sorts allday\r
2386      * events first, then non-allday events are sorted either based on event start\r
2387      * priority or span priority based on the value of {@link #spansHavePriority} \r
2388      * (defaults to event start priority).\r
2389      * @param {MixedCollection} evts A {@link Ext.util.MixedCollection MixedCollection}  \r
2390      * of {@link #Ext.calendar.EventRecord EventRecord} objects\r
2391      */\r
2392     sortEventRecordsForDay: function(evts) {\r
2393         if (evts.length < 2) {\r
2394             return;\r
2395         }\r
2396         evts.sort('ASC',\r
2397         function(evtA, evtB) {\r
2398             var a = evtA.data,\r
2399             b = evtB.data,\r
2400             M = Ext.calendar.EventMappings;\r
2401 \r
2402             // Always sort all day events before anything else\r
2403             if (a[M.IsAllDay.name]) {\r
2404                 return - 1;\r
2405             }\r
2406             else if (b[M.IsAllDay.name]) {\r
2407                 return 1;\r
2408             }\r
2409             if (this.spansHavePriority) {\r
2410                 // This logic always weights span events higher than non-span events\r
2411                 // (at the possible expense of start time order). This seems to\r
2412                 // be the approach used by Google calendar and can lead to a more\r
2413                 // visually appealing layout in complex cases, but event order is\r
2414                 // not guaranteed to be consistent.\r
2415                 var diff = Ext.calendar.Date.diffDays;\r
2416                 if (diff(a[M.StartDate.name], a[M.EndDate.name]) > 0) {\r
2417                     if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
2418                         // Both events are multi-day\r
2419                         if (a[M.StartDate.name].getTime() == b[M.StartDate.name].getTime()) {\r
2420                             // If both events start at the same time, sort the one\r
2421                             // that ends later (potentially longer span bar) first\r
2422                             return b[M.EndDate.name].getTime() - a[M.EndDate.name].getTime();\r
2423                         }\r
2424                         return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
2425                     }\r
2426                     return - 1;\r
2427                 }\r
2428                 else if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
2429                     return 1;\r
2430                 }\r
2431                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
2432             }\r
2433             else {\r
2434                 // Doing this allows span and non-span events to intermingle but\r
2435                 // remain sorted sequentially by start time. This seems more proper\r
2436                 // but can make for a less visually-compact layout when there are\r
2437                 // many such events mixed together closely on the calendar.\r
2438                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
2439             }\r
2440         }.createDelegate(this));\r
2441     },\r
2442 \r
2443     /**\r
2444      * Updates the view to contain the passed date\r
2445      * @param {Date} dt The date to display\r
2446      */\r
2447     moveTo: function(dt, noRefresh) {\r
2448         if (Ext.isDate(dt)) {\r
2449             this.setStartDate(dt);\r
2450             if (noRefresh !== false) {\r
2451                 this.refresh();\r
2452             }\r
2453             return this.startDate;\r
2454         }\r
2455         return dt;\r
2456     },\r
2457 \r
2458     /**\r
2459      * Updates the view to the next consecutive date(s)\r
2460      */\r
2461     moveNext: function(noRefresh) {\r
2462         return this.moveTo(this.viewEnd.add(Date.DAY, 1));\r
2463     },\r
2464 \r
2465     /**\r
2466      * Updates the view to the previous consecutive date(s)\r
2467      */\r
2468     movePrev: function(noRefresh) {\r
2469         var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd) + 1;\r
2470         return this.moveDays( - days, noRefresh);\r
2471     },\r
2472 \r
2473     /**\r
2474      * Shifts the view by the passed number of months relative to the currently set date\r
2475      * @param {Number} value The number of months (positive or negative) by which to shift the view\r
2476      */\r
2477     moveMonths: function(value, noRefresh) {\r
2478         return this.moveTo(this.startDate.add(Date.MONTH, value), noRefresh);\r
2479     },\r
2480 \r
2481     /**\r
2482      * Shifts the view by the passed number of weeks relative to the currently set date\r
2483      * @param {Number} value The number of weeks (positive or negative) by which to shift the view\r
2484      */\r
2485     moveWeeks: function(value, noRefresh) {\r
2486         return this.moveTo(this.startDate.add(Date.DAY, value * 7), noRefresh);\r
2487     },\r
2488 \r
2489     /**\r
2490      * Shifts the view by the passed number of days relative to the currently set date\r
2491      * @param {Number} value The number of days (positive or negative) by which to shift the view\r
2492      */\r
2493     moveDays: function(value, noRefresh) {\r
2494         return this.moveTo(this.startDate.add(Date.DAY, value), noRefresh);\r
2495     },\r
2496 \r
2497     /**\r
2498      * Updates the view to show today\r
2499      */\r
2500     moveToday: function(noRefresh) {\r
2501         return this.moveTo(new Date(), noRefresh);\r
2502     },\r
2503 \r
2504     /**\r
2505      * Sets the event store used by the calendar to display {@link Ext.calendar.EventRecord events}.\r
2506      * @param {Ext.data.Store} store\r
2507      */\r
2508     setStore: function(store, initial) {\r
2509         if (!initial && this.store) {\r
2510             this.store.un("datachanged", this.onDataChanged, this);\r
2511             this.store.un("add", this.onAdd, this);\r
2512             this.store.un("remove", this.onRemove, this);\r
2513             this.store.un("update", this.onUpdate, this);\r
2514             this.store.un("clear", this.refresh, this);\r
2515         }\r
2516         if (store) {\r
2517             store.on("datachanged", this.onDataChanged, this);\r
2518             store.on("add", this.onAdd, this);\r
2519             store.on("remove", this.onRemove, this);\r
2520             store.on("update", this.onUpdate, this);\r
2521             store.on("clear", this.refresh, this);\r
2522         }\r
2523         this.store = store;\r
2524         if (store && store.getCount() > 0) {\r
2525             this.refresh();\r
2526         }\r
2527     },\r
2528 \r
2529     getEventRecord: function(id) {\r
2530         var idx = this.store.find(Ext.calendar.EventMappings.EventId.name, id);\r
2531         return this.store.getAt(idx);\r
2532     },\r
2533 \r
2534     getEventRecordFromEl: function(el) {\r
2535         return this.getEventRecord(this.getEventIdFromEl(el));\r
2536     },\r
2537 \r
2538     // private\r
2539     getParams: function() {\r
2540         return {\r
2541             viewStart: this.viewStart,\r
2542             viewEnd: this.viewEnd,\r
2543             startDate: this.startDate,\r
2544             dayCount: this.dayCount,\r
2545             weekCount: this.weekCount,\r
2546             title: this.getTitle()\r
2547         };\r
2548     },\r
2549 \r
2550     getTitle: function() {\r
2551         return this.startDate.format('F Y');\r
2552     },\r
2553 \r
2554     /*\r
2555      * Shared click handling.  Each specific view also provides view-specific\r
2556      * click handling that calls this first.  This method returns true if it\r
2557      * can handle the click (and so the subclass should ignore it) else false.\r
2558      */\r
2559     onClick: function(e, t) {\r
2560         var el = e.getTarget(this.eventSelector, 5);\r
2561         if (el) {\r
2562             var id = this.getEventIdFromEl(el);\r
2563             this.fireEvent('eventclick', this, this.getEventRecord(id), el);\r
2564             return true;\r
2565         }\r
2566     },\r
2567 \r
2568     // private\r
2569     onMouseOver: function(e, t) {\r
2570         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
2571             if (!this.handleEventMouseEvent(e, t, 'over')) {\r
2572                 this.handleDayMouseEvent(e, t, 'over');\r
2573             }\r
2574         }\r
2575     },\r
2576 \r
2577     // private\r
2578     onMouseOut: function(e, t) {\r
2579         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
2580             if (!this.handleEventMouseEvent(e, t, 'out')) {\r
2581                 this.handleDayMouseEvent(e, t, 'out');\r
2582             }\r
2583         }\r
2584     },\r
2585 \r
2586     // private\r
2587     handleEventMouseEvent: function(e, t, type) {\r
2588         var el = e.getTarget(this.eventSelector, 5, true),\r
2589             rel,\r
2590             els,\r
2591             evtId;\r
2592         if (el) {\r
2593             rel = Ext.get(e.getRelatedTarget());\r
2594             if (el == rel || el.contains(rel)) {\r
2595                 return true;\r
2596             }\r
2597 \r
2598             evtId = this.getEventIdFromEl(el);\r
2599 \r
2600             if (this.eventOverClass != '') {\r
2601                 els = this.getEventEls(evtId);\r
2602                 els[type == 'over' ? 'addClass': 'removeClass'](this.eventOverClass);\r
2603             }\r
2604             this.fireEvent('event' + type, this, this.getEventRecord(evtId), el);\r
2605             return true;\r
2606         }\r
2607         return false;\r
2608     },\r
2609 \r
2610     // private\r
2611     getDateFromId: function(id, delim) {\r
2612         var parts = id.split(delim);\r
2613         return parts[parts.length - 1];\r
2614     },\r
2615 \r
2616     // private\r
2617     handleDayMouseEvent: function(e, t, type) {\r
2618         t = e.getTarget('td', 3);\r
2619         if (t) {\r
2620             if (t.id && t.id.indexOf(this.dayElIdDelimiter) > -1) {\r
2621                 var dt = this.getDateFromId(t.id, this.dayElIdDelimiter),\r
2622                 rel = Ext.get(e.getRelatedTarget()),\r
2623                 relTD,\r
2624                 relDate;\r
2625 \r
2626                 if (rel) {\r
2627                     relTD = rel.is('td') ? rel: rel.up('td', 3);\r
2628                     relDate = relTD && relTD.id ? this.getDateFromId(relTD.id, this.dayElIdDelimiter) : '';\r
2629                 }\r
2630                 if (!rel || dt != relDate) {\r
2631                     var el = this.getDayEl(dt);\r
2632                     if (el && this.dayOverClass != '') {\r
2633                         el[type == 'over' ? 'addClass': 'removeClass'](this.dayOverClass);\r
2634                     }\r
2635                     this.fireEvent('day' + type, this, Date.parseDate(dt, "Ymd"), el);\r
2636                 }\r
2637             }\r
2638         }\r
2639     },\r
2640 \r
2641     // private\r
2642     renderItems: function() {\r
2643         throw 'This method must be implemented by a subclass';\r
2644     }\r
2645 });\r
2646 /**\r
2647  * @class Ext.calendar.MonthView\r
2648  * @extends Ext.calendar.CalendarView\r
2649  * <p>Displays a calendar view by month. This class does not usually need ot be used directly as you can\r
2650  * use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including\r
2651  * the month view.</p>\r
2652  * @constructor\r
2653  * @param {Object} config The config object\r
2654  */\r
2655 Ext.calendar.MonthView = Ext.extend(Ext.calendar.CalendarView, {\r
2656     /**\r
2657      * @cfg {Boolean} showTime\r
2658      * True to display the current time in today's box in the calendar, false to not display it (defautls to true)\r
2659      */\r
2660     showTime: true,\r
2661     /**\r
2662      * @cfg {Boolean} showTodayText\r
2663      * True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)\r
2664      */\r
2665     showTodayText: true,\r
2666     /**\r
2667      * @cfg {String} todayText\r
2668      * The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')\r
2669      */\r
2670     todayText: 'Today',\r
2671     /**\r
2672      * @cfg {Boolean} showHeader\r
2673      * True to display a header beneath the navigation bar containing the week names above each week's column, false not to \r
2674      * show it and instead display the week names in the first row of days in the calendar (defaults to false).\r
2675      */\r
2676     showHeader: false,\r
2677     /**\r
2678      * @cfg {Boolean} showWeekLinks\r
2679      * True to display an extra column before the first day in the calendar that links to the {@link Ext.calendar.WeekView view}\r
2680      * for each individual week, false to not show it (defaults to false). If true, the week links can also contain the week \r
2681      * number depending on the value of {@link #showWeekNumbers}.\r
2682      */\r
2683     showWeekLinks: false,\r
2684     /**\r
2685      * @cfg {Boolean} showWeekNumbers\r
2686      * True to show the week number for each week in the calendar in the week link column, false to show nothing (defaults to false).\r
2687      * Note that if {@link #showWeekLinks} is false this config will have no affect even if true.\r
2688      */\r
2689     showWeekNumbers: false,\r
2690     /**\r
2691      * @cfg {String} weekLinkOverClass\r
2692      * The CSS class name applied when the mouse moves over a week link element (only applies when {@link #showWeekLinks} is true,\r
2693      * defaults to 'ext-week-link-over').\r
2694      */\r
2695     weekLinkOverClass: 'ext-week-link-over',\r
2696 \r
2697     //private properties -- do not override:\r
2698     daySelector: '.ext-cal-day',\r
2699     moreSelector: '.ext-cal-ev-more',\r
2700     weekLinkSelector: '.ext-cal-week-link',\r
2701     weekCount: -1,\r
2702     // defaults to auto by month\r
2703     dayCount: 7,\r
2704     moreElIdDelimiter: '-more-',\r
2705     weekLinkIdDelimiter: 'ext-cal-week-',\r
2706 \r
2707     // private\r
2708     initComponent: function() {\r
2709         Ext.calendar.MonthView.superclass.initComponent.call(this);\r
2710         this.addEvents({\r
2711             /**\r
2712              * @event dayclick\r
2713              * Fires after the user clicks within the view container and not on an event element\r
2714              * @param {Ext.calendar.MonthView} this\r
2715              * @param {Date} dt The date/time that was clicked on\r
2716              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the \r
2717              * MonthView always return true for this param.\r
2718              * @param {Ext.Element} el The Element that was clicked on\r
2719              */\r
2720             dayclick: true,\r
2721             /**\r
2722              * @event weekclick\r
2723              * Fires after the user clicks within a week link (when {@link #showWeekLinks is true)\r
2724              * @param {Ext.calendar.MonthView} this\r
2725              * @param {Date} dt The start date of the week that was clicked on\r
2726              */\r
2727             weekclick: true,\r
2728             // inherited docs\r
2729             dayover: true,\r
2730             // inherited docs\r
2731             dayout: true\r
2732         });\r
2733     },\r
2734 \r
2735     // private\r
2736     initDD: function() {\r
2737         var cfg = {\r
2738             view: this,\r
2739             createText: this.ddCreateEventText,\r
2740             moveText: this.ddMoveEventText,\r
2741             ddGroup: 'MonthViewDD'\r
2742         };\r
2743 \r
2744         this.dragZone = new Ext.calendar.DragZone(this.el, cfg);\r
2745         this.dropZone = new Ext.calendar.DropZone(this.el, cfg);\r
2746     },\r
2747 \r
2748     // private\r
2749     onDestroy: function() {\r
2750         Ext.destroy(this.ddSelector);\r
2751         Ext.destroy(this.dragZone);\r
2752         Ext.destroy(this.dropZone);\r
2753         Ext.calendar.MonthView.superclass.onDestroy.call(this);\r
2754     },\r
2755 \r
2756     // private\r
2757     afterRender: function() {\r
2758         if (!this.tpl) {\r
2759             this.tpl = new Ext.calendar.MonthViewTemplate({\r
2760                 id: this.id,\r
2761                 showTodayText: this.showTodayText,\r
2762                 todayText: this.todayText,\r
2763                 showTime: this.showTime,\r
2764                 showHeader: this.showHeader,\r
2765                 showWeekLinks: this.showWeekLinks,\r
2766                 showWeekNumbers: this.showWeekNumbers\r
2767             });\r
2768         }\r
2769         this.tpl.compile();\r
2770         this.addClass('ext-cal-monthview ext-cal-ct');\r
2771 \r
2772         Ext.calendar.MonthView.superclass.afterRender.call(this);\r
2773     },\r
2774 \r
2775     // private\r
2776     onResize: function() {\r
2777         if (this.monitorResize) {\r
2778             this.maxEventsPerDay = this.getMaxEventsPerDay();\r
2779             this.refresh();\r
2780         }\r
2781     },\r
2782 \r
2783     // private\r
2784     forceSize: function() {\r
2785         // Compensate for the week link gutter width if visible\r
2786         if (this.showWeekLinks && this.el && this.el.child) {\r
2787             var hd = this.el.select('.ext-cal-hd-days-tbl'),\r
2788             bgTbl = this.el.select('.ext-cal-bg-tbl'),\r
2789             evTbl = this.el.select('.ext-cal-evt-tbl'),\r
2790             wkLinkW = this.el.child('.ext-cal-week-link').getWidth(),\r
2791             w = this.el.getWidth() - wkLinkW;\r
2792 \r
2793             hd.setWidth(w);\r
2794             bgTbl.setWidth(w);\r
2795             evTbl.setWidth(w);\r
2796         }\r
2797         Ext.calendar.MonthView.superclass.forceSize.call(this);\r
2798     },\r
2799 \r
2800     //private\r
2801     initClock: function() {\r
2802         if (Ext.fly(this.id + '-clock') !== null) {\r
2803             this.prevClockDay = new Date().getDay();\r
2804             if (this.clockTask) {\r
2805                 Ext.TaskMgr.stop(this.clockTask);\r
2806             }\r
2807             this.clockTask = Ext.TaskMgr.start({\r
2808                 run: function() {\r
2809                     var el = Ext.fly(this.id + '-clock'),\r
2810                     t = new Date();\r
2811 \r
2812                     if (t.getDay() == this.prevClockDay) {\r
2813                         if (el) {\r
2814                             el.update(t.format('g:i a'));\r
2815                         }\r
2816                     }\r
2817                     else {\r
2818                         this.prevClockDay = t.getDay();\r
2819                         this.moveTo(t);\r
2820                     }\r
2821                 },\r
2822                 scope: this,\r
2823                 interval: 1000\r
2824             });\r
2825         }\r
2826     },\r
2827 \r
2828     // inherited docs\r
2829     getEventBodyMarkup: function() {\r
2830         if (!this.eventBodyMarkup) {\r
2831             this.eventBodyMarkup = ['{Title}',\r
2832             '<tpl if="_isReminder">',\r
2833             '<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',\r
2834             '</tpl>',\r
2835             '<tpl if="_isRecurring">',\r
2836             '<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',\r
2837             '</tpl>',\r
2838             '<tpl if="spanLeft">',\r
2839             '<i class="ext-cal-spl">&nbsp;</i>',\r
2840             '</tpl>',\r
2841             '<tpl if="spanRight">',\r
2842             '<i class="ext-cal-spr">&nbsp;</i>',\r
2843             '</tpl>'\r
2844             ].join('');\r
2845         }\r
2846         return this.eventBodyMarkup;\r
2847     },\r
2848 \r
2849     // inherited docs\r
2850     getEventTemplate: function() {\r
2851         if (!this.eventTpl) {\r
2852             var tpl,\r
2853             body = this.getEventBodyMarkup();\r
2854 \r
2855             tpl = !(Ext.isIE || Ext.isOpera) ?\r
2856             new Ext.XTemplate(\r
2857             '<div id="{_elId}" class="{_selectorCls} {_colorCls} {values.spanCls} ext-cal-evt ext-cal-evr">',\r
2858             body,\r
2859             '</div>'\r
2860             )\r
2861             : new Ext.XTemplate(\r
2862             '<tpl if="_renderAsAllDay">',\r
2863             '<div id="{_elId}" class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evt ext-cal-evo">',\r
2864             '<div class="ext-cal-evm">',\r
2865             '<div class="ext-cal-evi">',\r
2866             '</tpl>',\r
2867             '<tpl if="!_renderAsAllDay">',\r
2868             '<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr">',\r
2869             '</tpl>',\r
2870             body,\r
2871             '<tpl if="_renderAsAllDay">',\r
2872             '</div>',\r
2873             '</div>',\r
2874             '</tpl>',\r
2875             '</div>'\r
2876             );\r
2877             tpl.compile();\r
2878             this.eventTpl = tpl;\r
2879         }\r
2880         return this.eventTpl;\r
2881     },\r
2882 \r
2883     // private\r
2884     getTemplateEventData: function(evt) {\r
2885         var M = Ext.calendar.EventMappings,\r
2886         selector = this.getEventSelectorCls(evt[M.EventId.name]),\r
2887         title = evt[M.Title.name];\r
2888 \r
2889         return Ext.applyIf({\r
2890             _selectorCls: selector,\r
2891             _colorCls: 'ext-color-' + (evt[M.CalendarId.name] ?\r
2892             evt[M.CalendarId.name] : 'default') + (evt._renderAsAllDay ? '-ad': ''),\r
2893             _elId: selector + '-' + evt._weekIndex,\r
2894             _isRecurring: evt.Recurrence && evt.Recurrence != '',\r
2895             _isReminder: evt[M.Reminder.name] && evt[M.Reminder.name] != '',\r
2896             Title: (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title)\r
2897         },\r
2898         evt);\r
2899     },\r
2900 \r
2901     // private\r
2902     refresh: function() {\r
2903         if (this.detailPanel) {\r
2904             this.detailPanel.hide();\r
2905         }\r
2906         Ext.calendar.MonthView.superclass.refresh.call(this);\r
2907 \r
2908         if (this.showTime !== false) {\r
2909             this.initClock();\r
2910         }\r
2911     },\r
2912 \r
2913     // private\r
2914     renderItems: function() {\r
2915         Ext.calendar.WeekEventRenderer.render({\r
2916             eventGrid: this.allDayOnly ? this.allDayGrid: this.eventGrid,\r
2917             viewStart: this.viewStart,\r
2918             tpl: this.getEventTemplate(),\r
2919             maxEventsPerDay: this.maxEventsPerDay,\r
2920             id: this.id,\r
2921             templateDataFn: this.getTemplateEventData.createDelegate(this),\r
2922             evtMaxCount: this.evtMaxCount,\r
2923             weekCount: this.weekCount,\r
2924             dayCount: this.dayCount\r
2925         });\r
2926         this.fireEvent('eventsrendered', this);\r
2927     },\r
2928 \r
2929     // private\r
2930     getDayEl: function(dt) {\r
2931         return Ext.get(this.getDayId(dt));\r
2932     },\r
2933 \r
2934     // private\r
2935     getDayId: function(dt) {\r
2936         if (Ext.isDate(dt)) {\r
2937             dt = dt.format('Ymd');\r
2938         }\r
2939         return this.id + this.dayElIdDelimiter + dt;\r
2940     },\r
2941 \r
2942     // private\r
2943     getWeekIndex: function(dt) {\r
2944         var el = this.getDayEl(dt).up('.ext-cal-wk-ct');\r
2945         return parseInt(el.id.split('-wk-')[1], 10);\r
2946     },\r
2947 \r
2948     // private\r
2949     getDaySize: function(contentOnly) {\r
2950         var box = this.el.getBox(),\r
2951         w = box.width / this.dayCount,\r
2952         h = box.height / this.getWeekCount();\r
2953 \r
2954         if (contentOnly) {\r
2955             var hd = this.el.select('.ext-cal-dtitle').first().parent('tr');\r
2956             h = hd ? h - hd.getHeight(true) : h;\r
2957         }\r
2958         return {\r
2959             height: h,\r
2960             width: w\r
2961         };\r
2962     },\r
2963 \r
2964     // private\r
2965     getEventHeight: function() {\r
2966         if (!this.eventHeight) {\r
2967             var evt = this.el.select('.ext-cal-evt').first();\r
2968             this.eventHeight = evt ? evt.parent('tr').getHeight() : 18;\r
2969         }\r
2970         return this.eventHeight;\r
2971     },\r
2972 \r
2973     // private\r
2974     getMaxEventsPerDay: function() {\r
2975         var dayHeight = this.getDaySize(true).height,\r
2976             h = this.getEventHeight(),\r
2977             max = Math.max(Math.floor((dayHeight - h) / h), 0);\r
2978 \r
2979         return max;\r
2980     },\r
2981 \r
2982     // private\r
2983     getDayAt: function(x, y) {\r
2984         var box = this.el.getBox(),\r
2985             daySize = this.getDaySize(),\r
2986             dayL = Math.floor(((x - box.x) / daySize.width)),\r
2987             dayT = Math.floor(((y - box.y) / daySize.height)),\r
2988             days = (dayT * 7) + dayL,\r
2989             dt = this.viewStart.add(Date.DAY, days);\r
2990         return {\r
2991             date: dt,\r
2992             el: this.getDayEl(dt)\r
2993         };\r
2994     },\r
2995 \r
2996     // inherited docs\r
2997     moveNext: function() {\r
2998         return this.moveMonths(1);\r
2999     },\r
3000 \r
3001     // inherited docs\r
3002     movePrev: function() {\r
3003         return this.moveMonths( - 1);\r
3004     },\r
3005 \r
3006     // private\r
3007     onInitDrag: function() {\r
3008         Ext.calendar.MonthView.superclass.onInitDrag.call(this);\r
3009         Ext.select(this.daySelector).removeClass(this.dayOverClass);\r
3010         if (this.detailPanel) {\r
3011             this.detailPanel.hide();\r
3012         }\r
3013     },\r
3014 \r
3015     // private\r
3016     onMoreClick: function(dt) {\r
3017         if (!this.detailPanel) {\r
3018             this.detailPanel = new Ext.Panel({\r
3019                 id: this.id + '-details-panel',\r
3020                 title: dt.format('F j'),\r
3021                 layout: 'fit',\r
3022                 floating: true,\r
3023                 renderTo: Ext.getBody(),\r
3024                 tools: [{\r
3025                     id: 'close',\r
3026                     handler: function(e, t, p) {\r
3027                         p.hide();\r
3028                     }\r
3029                 }],\r
3030                 items: {\r
3031                     xtype: 'monthdaydetailview',\r
3032                     id: this.id + '-details-view',\r
3033                     date: dt,\r
3034                     view: this,\r
3035                     store: this.store,\r
3036                     listeners: {\r
3037                         'eventsrendered': this.onDetailViewUpdated.createDelegate(this)\r
3038                     }\r
3039                 }\r
3040             });\r
3041         }\r
3042         else {\r
3043             this.detailPanel.setTitle(dt.format('F j'));\r
3044         }\r
3045         this.detailPanel.getComponent(this.id + '-details-view').update(dt);\r
3046     },\r
3047 \r
3048     // private\r
3049     onDetailViewUpdated: function(view, dt, numEvents) {\r
3050         var p = this.detailPanel,\r
3051         frameH = p.getFrameHeight(),\r
3052         evtH = this.getEventHeight(),\r
3053         bodyH = frameH + (numEvents * evtH) + 3,\r
3054         dayEl = this.getDayEl(dt),\r
3055         box = dayEl.getBox();\r
3056 \r
3057         p.updateBox(box);\r
3058         p.setHeight(bodyH);\r
3059         p.setWidth(Math.max(box.width, 220));\r
3060         p.show();\r
3061         p.getPositionEl().alignTo(dayEl, 't-t?');\r
3062     },\r
3063 \r
3064     // private\r
3065     onHide: function() {\r
3066         Ext.calendar.MonthView.superclass.onHide.call(this);\r
3067         if (this.detailPanel) {\r
3068             this.detailPanel.hide();\r
3069         }\r
3070     },\r
3071 \r
3072     // private\r
3073     onClick: function(e, t) {\r
3074         if (this.detailPanel) {\r
3075             this.detailPanel.hide();\r
3076         }\r
3077         if (Ext.calendar.MonthView.superclass.onClick.apply(this, arguments)) {\r
3078             // The superclass handled the click already so exit\r
3079             return;\r
3080         }\r
3081         if (this.dropZone) {\r
3082             this.dropZone.clearShims();\r
3083         }\r
3084         var el = e.getTarget(this.weekLinkSelector, 3),\r
3085             dt,\r
3086             parts;\r
3087         if (el) {\r
3088             dt = el.id.split(this.weekLinkIdDelimiter)[1];\r
3089             this.fireEvent('weekclick', this, Date.parseDate(dt, 'Ymd'));\r
3090             return;\r
3091         }\r
3092         el = e.getTarget(this.moreSelector, 3);\r
3093         if (el) {\r
3094             dt = el.id.split(this.moreElIdDelimiter)[1];\r
3095             this.onMoreClick(Date.parseDate(dt, 'Ymd'));\r
3096             return;\r
3097         }\r
3098         el = e.getTarget('td', 3);\r
3099         if (el) {\r
3100             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {\r
3101                 parts = el.id.split(this.dayElIdDelimiter);\r
3102                 dt = parts[parts.length - 1];\r
3103 \r
3104                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), false, Ext.get(this.getDayId(dt)));\r
3105                 return;\r
3106             }\r
3107         }\r
3108     },\r
3109 \r
3110     // private\r
3111     handleDayMouseEvent: function(e, t, type) {\r
3112         var el = e.getTarget(this.weekLinkSelector, 3, true);\r
3113         if (el) {\r
3114             el[type == 'over' ? 'addClass': 'removeClass'](this.weekLinkOverClass);\r
3115             return;\r
3116         }\r
3117         Ext.calendar.MonthView.superclass.handleDayMouseEvent.apply(this, arguments);\r
3118     }\r
3119 });\r
3120 \r
3121 Ext.reg('monthview', Ext.calendar.MonthView);\r
3122 /**
3123  * @class Ext.calendar.DayHeaderView
3124  * @extends Ext.calendar.MonthView
3125  * <p>This is the header area container within the day and week views where all-day events are displayed.
3126  * Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}
3127  * which aggregates this class and the {@link Ext.calendar.DayBodyView DayBodyView} into the single unified view
3128  * presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>
3129  * @constructor
3130  * @param {Object} config The config object
3131  */
3132 Ext.calendar.DayHeaderView = Ext.extend(Ext.calendar.MonthView, {
3133     // private configs
3134     weekCount: 1,
3135     dayCount: 1,
3136     allDayOnly: true,
3137     monitorResize: false,
3138
3139     /**
3140      * @event dayclick
3141      * Fires after the user clicks within the day view container and not on an event element
3142      * @param {Ext.calendar.DayBodyView} this
3143      * @param {Date} dt The date/time that was clicked on
3144      * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the 
3145      * DayHeaderView always return true for this param.
3146      * @param {Ext.Element} el The Element that was clicked on
3147      */
3148
3149     // private
3150     afterRender: function() {
3151         if (!this.tpl) {
3152             this.tpl = new Ext.calendar.DayHeaderTemplate({
3153                 id: this.id,
3154                 showTodayText: this.showTodayText,
3155                 todayText: this.todayText,
3156                 showTime: this.showTime
3157             });
3158         }
3159         this.tpl.compile();
3160         this.addClass('ext-cal-day-header');
3161
3162         Ext.calendar.DayHeaderView.superclass.afterRender.call(this);
3163     },
3164
3165     // private
3166     forceSize: Ext.emptyFn,
3167
3168     // private
3169     refresh: function() {
3170         Ext.calendar.DayHeaderView.superclass.refresh.call(this);
3171         this.recalcHeaderBox();
3172     },
3173
3174     // private
3175     recalcHeaderBox: function() {
3176         var tbl = this.el.child('.ext-cal-evt-tbl'),
3177         h = tbl.getHeight();
3178
3179         this.el.setHeight(h + 7);
3180
3181         if (Ext.isIE && Ext.isStrict) {
3182             this.el.child('.ext-cal-hd-ad-inner').setHeight(h + 4);
3183         }
3184         if (Ext.isOpera) {
3185             //TODO: figure out why Opera refuses to refresh height when
3186             //the new height is lower than the previous one
3187             //            var ct = this.el.child('.ext-cal-hd-ct');
3188             //            ct.repaint();
3189             }
3190     },
3191
3192     // private
3193     moveNext: function(noRefresh) {
3194         this.moveDays(this.dayCount, noRefresh);
3195     },
3196
3197     // private
3198     movePrev: function(noRefresh) {
3199         this.moveDays( - this.dayCount, noRefresh);
3200     },
3201
3202     // private
3203     onClick: function(e, t) {
3204         var el = e.getTarget('td', 3),
3205             parts,
3206             dt;
3207         if (el) {
3208             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
3209                 parts = el.id.split(this.dayElIdDelimiter);
3210                 dt = parts[parts.length - 1];
3211
3212                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt)));
3213                 return;
3214             }
3215         }
3216         Ext.calendar.DayHeaderView.superclass.onClick.apply(this, arguments);
3217     }
3218 });
3219
3220 Ext.reg('dayheaderview', Ext.calendar.DayHeaderView);
3221 /**S
3222  * @class Ext.calendar.DayBodyView
3223  * @extends Ext.calendar.CalendarView
3224  * <p>This is the scrolling container within the day and week views where non-all-day events are displayed.
3225  * Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}
3226  * which aggregates this class and the {@link Ext.calendar.DayHeaderView DayHeaderView} into the single unified view
3227  * presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>
3228  * @constructor
3229  * @param {Object} config The config object
3230  */
3231 Ext.calendar.DayBodyView = Ext.extend(Ext.calendar.CalendarView, {
3232     //private
3233     dayColumnElIdDelimiter: '-day-col-',
3234
3235     //private
3236     initComponent: function() {
3237         Ext.calendar.DayBodyView.superclass.initComponent.call(this);
3238
3239         this.addEvents({
3240             /**
3241              * @event eventresize
3242              * Fires after the user drags the resize handle of an event to resize it
3243              * @param {Ext.calendar.DayBodyView} this
3244              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
3245              * containing the updated start and end dates
3246              */
3247             eventresize: true,
3248             /**
3249              * @event dayclick
3250              * Fires after the user clicks within the day view container and not on an event element
3251              * @param {Ext.calendar.DayBodyView} this
3252              * @param {Date} dt The date/time that was clicked on
3253              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the 
3254              * DayBodyView always return false for this param.
3255              * @param {Ext.Element} el The Element that was clicked on
3256              */
3257             dayclick: true
3258         });
3259     },
3260
3261     //private
3262     initDD: function() {
3263         var cfg = {
3264             createText: this.ddCreateEventText,
3265             moveText: this.ddMoveEventText,
3266             resizeText: this.ddResizeEventText
3267         };
3268
3269         this.el.ddScrollConfig = {
3270             // scrolling is buggy in IE/Opera for some reason.  A larger vthresh
3271             // makes it at least functional if not perfect
3272             vthresh: Ext.isIE || Ext.isOpera ? 100: 40,
3273             hthresh: -1,
3274             frequency: 50,
3275             increment: 100,
3276             ddGroup: 'DayViewDD'
3277         };
3278         this.dragZone = new Ext.calendar.DayViewDragZone(this.el, Ext.apply({
3279             view: this,
3280             containerScroll: true
3281         },
3282         cfg));
3283
3284         this.dropZone = new Ext.calendar.DayViewDropZone(this.el, Ext.apply({
3285             view: this
3286         },
3287         cfg));
3288     },
3289
3290     //private
3291     refresh: function() {
3292         var top = this.el.getScroll().top;
3293         this.prepareData();
3294         this.renderTemplate();
3295         this.renderItems();
3296
3297         // skip this if the initial render scroll position has not yet been set.
3298         // necessary since IE/Opera must be deferred, so the first refresh will
3299         // override the initial position by default and always set it to 0.
3300         if (this.scrollReady) {
3301             this.scrollTo(top);
3302         }
3303     },
3304
3305     /**
3306      * Scrolls the container to the specified vertical position. If the view is large enough that
3307      * there is no scroll overflow then this method will have no affect.
3308      * @param {Number} y The new vertical scroll position in pixels 
3309      * @param {Boolean} defer (optional) <p>True to slightly defer the call, false to execute immediately.</p> 
3310      * <p>This method will automatically defer itself for IE and Opera (even if you pass false) otherwise
3311      * the scroll position will not update in those browsers. You can optionally pass true, however, to
3312      * force the defer in all browsers, or use your own custom conditions to determine whether this is needed.</p>
3313      * <p>Note that this method should not generally need to be called directly as scroll position is managed internally.</p>
3314      */
3315     scrollTo: function(y, defer) {
3316         defer = defer || (Ext.isIE || Ext.isOpera);
3317         if (defer) {
3318             (function() {
3319                 this.el.scrollTo('top', y);
3320                 this.scrollReady = true;
3321             }).defer(10, this);
3322         }
3323         else {
3324             this.el.scrollTo('top', y);
3325             this.scrollReady = true;
3326         }
3327     },
3328
3329     // private
3330     afterRender: function() {
3331         if (!this.tpl) {
3332             this.tpl = new Ext.calendar.DayBodyTemplate({
3333                 id: this.id,
3334                 dayCount: this.dayCount,
3335                 showTodayText: this.showTodayText,
3336                 todayText: this.todayText,
3337                 showTime: this.showTime
3338             });
3339         }
3340         this.tpl.compile();
3341
3342         this.addClass('ext-cal-body-ct');
3343
3344         Ext.calendar.DayBodyView.superclass.afterRender.call(this);
3345
3346         // default scroll position to 7am:
3347         this.scrollTo(7 * 42);
3348     },
3349
3350     // private
3351     forceSize: Ext.emptyFn,
3352
3353     // private
3354     onEventResize: function(rec, data) {
3355         var D = Ext.calendar.Date,
3356         start = Ext.calendar.EventMappings.StartDate.name,
3357         end = Ext.calendar.EventMappings.EndDate.name;
3358
3359         if (D.compare(rec.data[start], data.StartDate) === 0 &&
3360         D.compare(rec.data[end], data.EndDate) === 0) {
3361             // no changes
3362             return;
3363         }
3364         rec.set(start, data.StartDate);
3365         rec.set(end, data.EndDate);
3366
3367         this.fireEvent('eventresize', this, rec);
3368     },
3369
3370     // inherited docs
3371     getEventBodyMarkup: function() {
3372         if (!this.eventBodyMarkup) {
3373             this.eventBodyMarkup = ['{Title}',
3374             '<tpl if="_isReminder">',
3375             '<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',
3376             '</tpl>',
3377             '<tpl if="_isRecurring">',
3378             '<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',
3379             '</tpl>'
3380             //                '<tpl if="spanLeft">',
3381             //                    '<i class="ext-cal-spl">&nbsp;</i>',
3382             //                '</tpl>',
3383             //                '<tpl if="spanRight">',
3384             //                    '<i class="ext-cal-spr">&nbsp;</i>',
3385             //                '</tpl>'
3386             ].join('');
3387         }
3388         return this.eventBodyMarkup;
3389     },
3390
3391     // inherited docs
3392     getEventTemplate: function() {
3393         if (!this.eventTpl) {
3394             this.eventTpl = !(Ext.isIE || Ext.isOpera) ?
3395             new Ext.XTemplate(
3396             '<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
3397             '<div class="ext-evt-bd">', this.getEventBodyMarkup(), '</div>',
3398             '<div class="ext-evt-rsz"><div class="ext-evt-rsz-h">&nbsp;</div></div>',
3399             '</div>'
3400             )
3401             : new Ext.XTemplate(
3402             '<div id="{_elId}" class="ext-cal-evt {_selectorCls} {_colorCls}-x" style="left: {_left}%; width: {_width}%; top: {_top}px;">',
3403             '<div class="ext-cal-evb">&nbsp;</div>',
3404             '<dl style="height: {_height}px;" class="ext-cal-evdm">',
3405             '<dd class="ext-evt-bd">',
3406             this.getEventBodyMarkup(),
3407             '</dd>',
3408             '<div class="ext-evt-rsz"><div class="ext-evt-rsz-h">&nbsp;</div></div>',
3409             '</dl>',
3410             '<div class="ext-cal-evb">&nbsp;</div>',
3411             '</div>'
3412             );
3413             this.eventTpl.compile();
3414         }
3415         return this.eventTpl;
3416     },
3417
3418     /**
3419      * <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type
3420      * {@link Ext.calendar.EventRecord}) to populate the calendar views with <strong>all-day</strong> events. 
3421      * Internally this method by default generates different markup for browsers that support CSS border radius 
3422      * and those that don't. This method can be overridden as needed to customize the markup generated.</p>
3423      * <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately
3424      * from the surrounding container markup.  This provdes the flexibility to customize what's in the body without
3425      * having to override the entire XTemplate. If you do override this method, you should make sure that your 
3426      * overridden version also does the same.</p>
3427      * @return {Ext.XTemplate} The event XTemplate
3428      */
3429     getEventAllDayTemplate: function() {
3430         if (!this.eventAllDayTpl) {
3431             var tpl,
3432             body = this.getEventBodyMarkup();
3433
3434             tpl = !(Ext.isIE || Ext.isOpera) ?
3435             new Ext.XTemplate(
3436             '<div id="{_elId}" class="{_selectorCls} {_colorCls} {values.spanCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
3437             body,
3438             '</div>'
3439             )
3440             : new Ext.XTemplate(
3441             '<div id="{_elId}" class="ext-cal-evt" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
3442             '<div class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evo">',
3443             '<div class="ext-cal-evm">',
3444             '<div class="ext-cal-evi">',
3445             body,
3446             '</div>',
3447             '</div>',
3448             '</div></div>'
3449             );
3450             tpl.compile();
3451             this.eventAllDayTpl = tpl;
3452         }
3453         return this.eventAllDayTpl;
3454     },
3455
3456     // private
3457     getTemplateEventData: function(evt) {
3458         var selector = this.getEventSelectorCls(evt[Ext.calendar.EventMappings.EventId.name]),
3459         data = {},
3460         M = Ext.calendar.EventMappings;
3461
3462         this.getTemplateEventBox(evt);
3463
3464         data._selectorCls = selector;
3465         data._colorCls = 'ext-color-' + evt[M.CalendarId.name] + (evt._renderAsAllDay ? '-ad': '');
3466         data._elId = selector + (evt._weekIndex ? '-' + evt._weekIndex: '');
3467         data._isRecurring = evt.Recurrence && evt.Recurrence != '';
3468         data._isReminder = evt[M.Reminder.name] && evt[M.Reminder.name] != '';
3469         var title = evt[M.Title.name];
3470         data.Title = (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title);
3471
3472         return Ext.applyIf(data, evt);
3473     },
3474
3475     // private
3476     getTemplateEventBox: function(evt) {
3477         var heightFactor = 0.7,
3478             start = evt[Ext.calendar.EventMappings.StartDate.name],
3479             end = evt[Ext.calendar.EventMappings.EndDate.name],
3480             startMins = start.getHours() * 60 + start.getMinutes(),
3481             endMins = end.getHours() * 60 + end.getMinutes(),
3482             diffMins = endMins - startMins;
3483
3484         evt._left = 0;
3485         evt._width = 100;
3486         evt._top = Math.round(startMins * heightFactor) + 1;
3487         evt._height = Math.max((diffMins * heightFactor) - 2, 15);
3488     },
3489
3490     // private
3491     renderItems: function() {
3492         var day = 0,
3493             evts = [],
3494             ev,
3495             d,
3496             ct,
3497             item,
3498             i,
3499             j,
3500             l,
3501             overlapCols,
3502             prevCol,
3503             colWidth,
3504             evtWidth,
3505             markup,
3506             target;
3507         for (; day < this.dayCount; day++) {
3508             ev = emptyCells = skipped = 0;
3509             d = this.eventGrid[0][day];
3510             ct = d ? d.length: 0;
3511
3512             for (; ev < ct; ev++) {
3513                 evt = d[ev];
3514                 if (!evt) {
3515                     continue;
3516                 }
3517                 item = evt.data || evt.event.data;
3518                 if (item._renderAsAllDay) {
3519                     continue;
3520                 }
3521                 Ext.apply(item, {
3522                     cls: 'ext-cal-ev',
3523                     _positioned: true
3524                 });
3525                 evts.push({
3526                     data: this.getTemplateEventData(item),
3527                     date: this.viewStart.add(Date.DAY, day)
3528                 });
3529             }
3530         }
3531
3532         // overlapping event pre-processing loop
3533         i = j = overlapCols = prevCol = 0;
3534         l = evts.length;
3535         for (; i < l; i++) {
3536             evt = evts[i].data;
3537             evt2 = null;
3538             prevCol = overlapCols;
3539             for (j = 0; j < l; j++) {
3540                 if (i == j) {
3541                     continue;
3542                 }
3543                 evt2 = evts[j].data;
3544                 if (this.isOverlapping(evt, evt2)) {
3545                     evt._overlap = evt._overlap == undefined ? 1: evt._overlap + 1;
3546                     if (i < j) {
3547                         if (evt._overcol === undefined) {
3548                             evt._overcol = 0;
3549                         }
3550                         evt2._overcol = evt._overcol + 1;
3551                         overlapCols = Math.max(overlapCols, evt2._overcol);
3552                     }
3553                 }
3554             }
3555         }
3556
3557         // rendering loop
3558         for (i = 0; i < l; i++) {
3559             evt = evts[i].data;
3560             if (evt._overlap !== undefined) {
3561                 colWidth = 100 / (overlapCols + 1);
3562                 evtWidth = 100 - (colWidth * evt._overlap);
3563
3564                 evt._width = colWidth;
3565                 evt._left = colWidth * evt._overcol;
3566             }
3567             markup = this.getEventTemplate().apply(evt);
3568             target = this.id + '-day-col-' + evts[i].date.format('Ymd');
3569
3570             Ext.DomHelper.append(target, markup);
3571         }
3572
3573         this.fireEvent('eventsrendered', this);
3574     },
3575
3576     // private
3577     getDayEl: function(dt) {
3578         return Ext.get(this.getDayId(dt));
3579     },
3580
3581     // private
3582     getDayId: function(dt) {
3583         if (Ext.isDate(dt)) {
3584             dt = dt.format('Ymd');
3585         }
3586         return this.id + this.dayColumnElIdDelimiter + dt;
3587     },
3588
3589     // private
3590     getDaySize: function() {
3591         var box = this.el.child('.ext-cal-day-col-inner').getBox();
3592         return {
3593             height: box.height,
3594             width: box.width
3595         };
3596     },
3597
3598     // private
3599     getDayAt: function(x, y) {
3600         var sel = '.ext-cal-body-ct',
3601         xoffset = this.el.child('.ext-cal-day-times').getWidth(),
3602         viewBox = this.el.getBox(),
3603         daySize = this.getDaySize(false),
3604         relX = x - viewBox.x - xoffset,
3605         dayIndex = Math.floor(relX / daySize.width),
3606         // clicked col index
3607         scroll = this.el.getScroll(),
3608         row = this.el.child('.ext-cal-bg-row'),
3609         // first avail row, just to calc size
3610         rowH = row.getHeight() / 2,
3611         // 30 minute increment since a row is 60 minutes
3612         relY = y - viewBox.y - rowH + scroll.top,
3613         rowIndex = Math.max(0, Math.ceil(relY / rowH)),
3614         mins = rowIndex * 30,
3615         dt = this.viewStart.add(Date.DAY, dayIndex).add(Date.MINUTE, mins),
3616         el = this.getDayEl(dt),
3617         timeX = x;
3618
3619         if (el) {
3620             timeX = el.getLeft();
3621         }
3622
3623         return {
3624             date: dt,
3625             el: el,
3626             // this is the box for the specific time block in the day that was clicked on:
3627             timeBox: {
3628                 x: timeX,
3629                 y: (rowIndex * 21) + viewBox.y - scroll.top,
3630                 width: daySize.width,
3631                 height: rowH
3632             }
3633         };
3634     },
3635
3636     // private
3637     onClick: function(e, t) {
3638         if (this.dragPending || Ext.calendar.DayBodyView.superclass.onClick.apply(this, arguments)) {
3639             // The superclass handled the click already so exit
3640             return;
3641         }
3642         if (e.getTarget('.ext-cal-day-times', 3) !== null) {
3643             // ignore clicks on the times-of-day gutter
3644             return;
3645         }
3646         var el = e.getTarget('td', 3);
3647         if (el) {
3648             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
3649                 var dt = this.getDateFromId(el.id, this.dayElIdDelimiter);
3650                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt, true)));
3651                 return;
3652             }
3653         }
3654         var day = this.getDayAt(e.xy[0], e.xy[1]);
3655         if (day && day.date) {
3656             this.fireEvent('dayclick', this, day.date, false, null);
3657         }
3658     }
3659 });
3660
3661 Ext.reg('daybodyview', Ext.calendar.DayBodyView);
3662 /**
3663  * @class Ext.calendar.DayView
3664  * @extends Ext.Container
3665  * <p>Unlike other calendar views, is not actually a subclass of {@link Ext.calendar.CalendarView CalendarView}.
3666  * Instead it is a {@link Ext.Container Container} subclass that internally creates and manages the layouts of
3667  * a {@link Ext.calendar.DayHeaderView DayHeaderView} and a {@link Ext.calendar.DayBodyView DayBodyView}. As such
3668  * DayView accepts any config values that are valid for DayHeaderView and DayBodyView and passes those through
3669  * to the contained views. It also supports the interface required of any calendar view and in turn calls methods
3670  * on the contained views as necessary.</p>
3671  * @constructor
3672  * @param {Object} config The config object
3673  */
3674 Ext.calendar.DayView = Ext.extend(Ext.Container, {
3675     /**
3676      * @cfg {Boolean} showTime
3677      * True to display the current time in today's box in the calendar, false to not display it (defautls to true)
3678      */
3679     showTime: true,
3680     /**
3681      * @cfg {Boolean} showTodayText
3682      * True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)
3683      */
3684     showTodayText: true,
3685     /**
3686      * @cfg {String} todayText
3687      * The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')
3688      */
3689     todayText: 'Today',
3690     /**
3691      * @cfg {String} ddCreateEventText
3692      * The text to display inside the drag proxy while dragging over the calendar to create a new event (defaults to 
3693      * 'Create event for {0}' where {0} is a date range supplied by the view)
3694      */
3695     ddCreateEventText: 'Create event for {0}',
3696     /**
3697      * @cfg {String} ddMoveEventText
3698      * The text to display inside the drag proxy while dragging an event to reposition it (defaults to 
3699      * 'Move event to {0}' where {0} is the updated event start date/time supplied by the view)
3700      */
3701     ddMoveEventText: 'Move event to {0}',
3702     /**
3703      * @cfg {Number} dayCount
3704      * The number of days to display in the view (defaults to 1)
3705      */
3706     dayCount: 1,
3707     
3708     // private
3709     initComponent : function(){
3710         // rendering more than 7 days per view is not supported
3711         this.dayCount = this.dayCount > 7 ? 7 : this.dayCount;
3712         
3713         var cfg = Ext.apply({}, this.initialConfig);
3714         cfg.showTime = this.showTime;
3715         cfg.showTodatText = this.showTodayText;
3716         cfg.todayText = this.todayText;
3717         cfg.dayCount = this.dayCount;
3718         cfg.wekkCount = 1; 
3719         
3720         var header = Ext.applyIf({
3721             xtype: 'dayheaderview',
3722             id: this.id+'-hd'
3723         }, cfg);
3724         
3725         var body = Ext.applyIf({
3726             xtype: 'daybodyview',
3727             id: this.id+'-bd'
3728         }, cfg);
3729         
3730         this.items = [header, body];
3731         this.addClass('ext-cal-dayview ext-cal-ct');
3732         
3733         Ext.calendar.DayView.superclass.initComponent.call(this);
3734     },
3735     
3736     // private
3737     afterRender : function(){
3738         Ext.calendar.DayView.superclass.afterRender.call(this);
3739         
3740         this.header = Ext.getCmp(this.id+'-hd');
3741         this.body = Ext.getCmp(this.id+'-bd');
3742         this.body.on('eventsrendered', this.forceSize, this);
3743     },
3744     
3745     // private
3746     refresh : function(){
3747         this.header.refresh();
3748         this.body.refresh();
3749     },
3750     
3751     // private
3752     forceSize: function(){
3753         // The defer call is mainly for good ol' IE, but it doesn't hurt in
3754         // general to make sure that the window resize is good and done first
3755         // so that we can properly calculate sizes.
3756         (function(){
3757             var ct = this.el.up('.x-panel-body'),
3758                 hd = this.el.child('.ext-cal-day-header'),
3759                 h = ct.getHeight() - hd.getHeight();
3760             
3761             this.el.child('.ext-cal-body-ct').setHeight(h);
3762         }).defer(10, this);
3763     },
3764     
3765     // private
3766     onResize : function(){
3767         this.forceSize();
3768     },
3769     
3770     // private
3771     getViewBounds : function(){
3772         return this.header.getViewBounds();
3773     },
3774     
3775     /**
3776      * Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not 
3777      * be the first date displayed in the rendered calendar -- to get the start and end dates displayed
3778      * to the user use {@link #getViewBounds}.
3779      * @return {Date} The start date
3780      */
3781     getStartDate : function(){
3782         return this.header.getStartDate();
3783     },
3784
3785     /**
3786      * Sets the start date used to calculate the view boundaries to display. The displayed view will be the 
3787      * earliest and latest dates that match the view requirements and contain the date passed to this function.
3788      * @param {Date} dt The date used to calculate the new view boundaries
3789      */
3790     setStartDate: function(dt){
3791         this.header.setStartDate(dt, true);
3792         this.body.setStartDate(dt, true);
3793     },
3794
3795     // private
3796     renderItems: function(){
3797         this.header.renderItems();
3798         this.body.renderItems();
3799     },
3800     
3801     /**
3802      * Returns true if the view is currently displaying today's date, else false.
3803      * @return {Boolean} True or false
3804      */
3805     isToday : function(){
3806         return this.header.isToday();
3807     },
3808     
3809     /**
3810      * Updates the view to contain the passed date
3811      * @param {Date} dt The date to display
3812      */
3813     moveTo : function(dt, noRefresh){
3814         this.header.moveTo(dt, noRefresh);
3815         this.body.moveTo(dt, noRefresh);
3816     },
3817     
3818     /**
3819      * Updates the view to the next consecutive date(s)
3820      */
3821     moveNext : function(noRefresh){
3822         this.header.moveNext(noRefresh);
3823         this.body.moveNext(noRefresh);
3824     },
3825     
3826     /**
3827      * Updates the view to the previous consecutive date(s)
3828      */
3829     movePrev : function(noRefresh){
3830         this.header.movePrev(noRefresh);
3831         this.body.movePrev(noRefresh);
3832     },
3833
3834     /**
3835      * Shifts the view by the passed number of days relative to the currently set date
3836      * @param {Number} value The number of days (positive or negative) by which to shift the view
3837      */
3838     moveDays : function(value, noRefresh){
3839         this.header.moveDays(value, noRefresh);
3840         this.body.moveDays(value, noRefresh);
3841     },
3842     
3843     /**
3844      * Updates the view to show today
3845      */
3846     moveToday : function(noRefresh){
3847         this.header.moveToday(noRefresh);
3848         this.body.moveToday(noRefresh);
3849     }
3850 });
3851
3852 Ext.reg('dayview', Ext.calendar.DayView);
3853 /**
3854  * @class Ext.calendar.WeekView
3855  * @extends Ext.calendar.DayView
3856  * <p>Displays a calendar view by week. This class does not usually need ot be used directly as you can
3857  * use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including
3858  * the week view.</p>
3859  * @constructor
3860  * @param {Object} config The config object
3861  */
3862 Ext.calendar.WeekView = Ext.extend(Ext.calendar.DayView, {
3863     /**
3864      * @cfg {Number} dayCount
3865      * The number of days to display in the view (defaults to 7)
3866      */
3867     dayCount: 7
3868 });
3869
3870 Ext.reg('weekview', Ext.calendar.WeekView);/**
3871  * @class Ext.calendar.DateRangeField
3872  * @extends Ext.form.Field
3873  * <p>A combination field that includes start and end dates and times, as well as an optional all-day checkbox.</p>
3874  * @constructor
3875  * @param {Object} config The config object
3876  */
3877 Ext.calendar.DateRangeField = Ext.extend(Ext.form.Field, {
3878     /**
3879      * @cfg {String} toText
3880      * The text to display in between the date/time fields (defaults to 'to')
3881      */
3882     toText: 'to',
3883     /**
3884      * @cfg {String} toText
3885      * The text to display as the label for the all day checkbox (defaults to 'All day')
3886      */
3887     allDayText: 'All day',
3888
3889     // private
3890     onRender: function(ct, position) {
3891         if (!this.el) {
3892             this.startDate = new Ext.form.DateField({
3893                 id: this.id + '-start-date',
3894                 format: 'n/j/Y',
3895                 width: 100,
3896                 listeners: {
3897                     'change': {
3898                         fn: function() {
3899                             this.checkDates('date', 'start');
3900                         },
3901                         scope: this
3902                     }
3903                 }
3904             });
3905             this.startTime = new Ext.form.TimeField({
3906                 id: this.id + '-start-time',
3907                 hidden: this.showTimes === false,
3908                 labelWidth: 0,
3909                 hideLabel: true,
3910                 width: 90,
3911                 listeners: {
3912                     'select': {
3913                         fn: function() {
3914                             this.checkDates('time', 'start');
3915                         },
3916                         scope: this
3917                     }
3918                 }
3919             });
3920             this.endTime = new Ext.form.TimeField({
3921                 id: this.id + '-end-time',
3922                 hidden: this.showTimes === false,
3923                 labelWidth: 0,
3924                 hideLabel: true,
3925                 width: 90,
3926                 listeners: {
3927                     'select': {
3928                         fn: function() {
3929                             this.checkDates('time', 'end');
3930                         },
3931                         scope: this
3932                     }
3933                 }
3934             });
3935             this.endDate = new Ext.form.DateField({
3936                 id: this.id + '-end-date',
3937                 format: 'n/j/Y',
3938                 hideLabel: true,
3939                 width: 100,
3940                 listeners: {
3941                     'change': {
3942                         fn: function() {
3943                             this.checkDates('date', 'end');
3944                         },
3945                         scope: this
3946                     }
3947                 }
3948             });
3949             this.allDay = new Ext.form.Checkbox({
3950                 id: this.id + '-allday',
3951                 hidden: this.showTimes === false || this.showAllDay === false,
3952                 boxLabel: this.allDayText,
3953                 handler: function(chk, checked) {
3954                     this.startTime.setVisible(!checked);
3955                     this.endTime.setVisible(!checked);
3956                 },
3957                 scope: this
3958             });
3959             this.toLabel = new Ext.form.Label({
3960                 xtype: 'label',
3961                 id: this.id + '-to-label',
3962                 text: this.toText
3963             });
3964
3965             this.fieldCt = new Ext.Container({
3966                 autoEl: {
3967                     id: this.id
3968                 },
3969                 //make sure the container el has the field's id
3970                 cls: 'ext-dt-range',
3971                 renderTo: ct,
3972                 layout: 'table',
3973                 layoutConfig: {
3974                     columns: 6
3975                 },
3976                 defaults: {
3977                     hideParent: true
3978                 },
3979                 items: [
3980                 this.startDate,
3981                 this.startTime,
3982                 this.toLabel,
3983                 this.endTime,
3984                 this.endDate,
3985                 this.allDay
3986                 ]
3987             });
3988
3989             this.fieldCt.ownerCt = this;
3990             this.el = this.fieldCt.getEl();
3991             this.items = new Ext.util.MixedCollection();
3992             this.items.addAll([this.startDate, this.endDate, this.toLabel, this.startTime, this.endTime, this.allDay]);
3993         }
3994         Ext.calendar.DateRangeField.superclass.onRender.call(this, ct, position);
3995     },
3996
3997     // private
3998     checkDates: function(type, startend) {
3999         var startField = Ext.getCmp(this.id + '-start-' + type),
4000         endField = Ext.getCmp(this.id + '-end-' + type),
4001         startValue = this.getDT('start'),
4002         endValue = this.getDT('end');
4003
4004         if (startValue > endValue) {
4005             if (startend == 'start') {
4006                 endField.setValue(startValue);
4007             } else {
4008                 startField.setValue(endValue);
4009                 this.checkDates(type, 'start');
4010             }
4011         }
4012         if (type == 'date') {
4013             this.checkDates('time', startend);
4014         }
4015     },
4016
4017     /**
4018      * Returns an array containing the following values in order:<div class="mdetail-params"><ul>
4019      * <li><b><code>DateTime</code></b> : <div class="sub-desc">The start date/time</div></li>
4020      * <li><b><code>DateTime</code></b> : <div class="sub-desc">The end date/time</div></li>
4021      * <li><b><code>Boolean</code></b> : <div class="sub-desc">True if the dates are all-day, false 
4022      * if the time values should be used</div></li><ul></div>
4023      * @return {Array} The array of return values
4024      */
4025     getValue: function() {
4026         return [
4027         this.getDT('start'),
4028         this.getDT('end'),
4029         this.allDay.getValue()
4030         ];
4031     },
4032
4033     // private getValue helper
4034     getDT: function(startend) {
4035         var time = this[startend + 'Time'].getValue(),
4036         dt = this[startend + 'Date'].getValue();
4037
4038         if (Ext.isDate(dt)) {
4039             dt = dt.format(this[startend + 'Date'].format);
4040         }
4041         else {
4042             return null;
4043         };
4044         if (time != '' && this[startend + 'Time'].isVisible()) {
4045             return Date.parseDate(dt + ' ' + time, this[startend + 'Date'].format + ' ' + this[startend + 'Time'].format);
4046         }
4047         return Date.parseDate(dt, this[startend + 'Date'].format);
4048
4049     },
4050
4051     /**
4052      * Sets the values to use in the date range.
4053      * @param {Array/Date/Object} v The value(s) to set into the field. Valid types are as follows:<div class="mdetail-params"><ul>
4054      * <li><b><code>Array</code></b> : <div class="sub-desc">An array containing, in order, a start date, end date and all-day flag.
4055      * This array should exactly match the return type as specified by {@link #getValue}.</div></li>
4056      * <li><b><code>DateTime</code></b> : <div class="sub-desc">A single Date object, which will be used for both the start and
4057      * end dates in the range.  The all-day flag will be defaulted to false.</div></li>
4058      * <li><b><code>Object</code></b> : <div class="sub-desc">An object containing properties for StartDate, EndDate and IsAllDay
4059      * as defined in {@link Ext.calendar.EventMappings}.</div></li><ul></div>
4060      */
4061     setValue: function(v) {
4062         if (Ext.isArray(v)) {
4063             this.setDT(v[0], 'start');
4064             this.setDT(v[1], 'end');
4065             this.allDay.setValue( !! v[2]);
4066         }
4067         else if (Ext.isDate(v)) {
4068             this.setDT(v, 'start');
4069             this.setDT(v, 'end');
4070             this.allDay.setValue(false);
4071         }
4072         else if (v[Ext.calendar.EventMappings.StartDate.name]) {
4073             //object
4074             this.setDT(v[Ext.calendar.EventMappings.StartDate.name], 'start');
4075             if (!this.setDT(v[Ext.calendar.EventMappings.EndDate.name], 'end')) {
4076                 this.setDT(v[Ext.calendar.EventMappings.StartDate.name], 'end');
4077             }
4078             this.allDay.setValue( !! v[Ext.calendar.EventMappings.IsAllDay.name]);
4079         }
4080     },
4081
4082     // private setValue helper
4083     setDT: function(dt, startend) {
4084         if (dt && Ext.isDate(dt)) {
4085             this[startend + 'Date'].setValue(dt);
4086             this[startend + 'Time'].setValue(dt.format(this[startend + 'Time'].format));
4087             return true;
4088         }
4089     },
4090
4091     // inherited docs
4092     isDirty: function() {
4093         var dirty = false;
4094         if (this.rendered && !this.disabled) {
4095             this.items.each(function(item) {
4096                 if (item.isDirty()) {
4097                     dirty = true;
4098                     return false;
4099                 }
4100             });
4101         }
4102         return dirty;
4103     },
4104
4105     // private
4106     onDisable: function() {
4107         this.delegateFn('disable');
4108     },
4109
4110     // private
4111     onEnable: function() {
4112         this.delegateFn('enable');
4113     },
4114
4115     // inherited docs
4116     reset: function() {
4117         this.delegateFn('reset');
4118     },
4119
4120     // private
4121     delegateFn: function(fn) {
4122         this.items.each(function(item) {
4123             if (item[fn]) {
4124                 item[fn]();
4125             }
4126         });
4127     },
4128
4129     // private
4130     beforeDestroy: function() {
4131         Ext.destroy(this.fieldCt);
4132         Ext.calendar.DateRangeField.superclass.beforeDestroy.call(this);
4133     },
4134
4135     /**
4136      * @method getRawValue
4137      * @hide
4138      */
4139     getRawValue: Ext.emptyFn,
4140     /**
4141      * @method setRawValue
4142      * @hide
4143      */
4144     setRawValue: Ext.emptyFn
4145 });
4146
4147 Ext.reg('daterangefield', Ext.calendar.DateRangeField);
4148 /**
4149  * @class Ext.calendar.ReminderField
4150  * @extends Ext.form.ComboBox
4151  * <p>A custom combo used for choosing a reminder setting for an event.</p>
4152  * <p>This is pretty much a standard combo that is simply pre-configured for the options needed by the
4153  * calendar components. The default configs are as follows:<pre><code>
4154     width: 200,
4155     fieldLabel: 'Reminder',
4156     mode: 'local',
4157     triggerAction: 'all',
4158     forceSelection: true,
4159     displayField: 'desc',
4160     valueField: 'value'
4161 </code></pre>
4162  * @constructor
4163  * @param {Object} config The config object
4164  */
4165 Ext.calendar.ReminderField = Ext.extend(Ext.form.ComboBox, {
4166     width: 200,
4167     fieldLabel: 'Reminder',
4168     mode: 'local',
4169     triggerAction: 'all',
4170     forceSelection: true,
4171     displayField: 'desc',
4172     valueField: 'value',
4173
4174     // private
4175     initComponent: function() {
4176         Ext.calendar.ReminderField.superclass.initComponent.call(this);
4177
4178         this.store = this.store || new Ext.data.ArrayStore({
4179             fields: ['value', 'desc'],
4180             idIndex: 0,
4181             data: [
4182             ['', 'None'],
4183             ['0', 'At start time'],
4184             ['5', '5 minutes before start'],
4185             ['15', '15 minutes before start'],
4186             ['30', '30 minutes before start'],
4187             ['60', '1 hour before start'],
4188             ['90', '1.5 hours before start'],
4189             ['120', '2 hours before start'],
4190             ['180', '3 hours before start'],
4191             ['360', '6 hours before start'],
4192             ['720', '12 hours before start'],
4193             ['1440', '1 day before start'],
4194             ['2880', '2 days before start'],
4195             ['4320', '3 days before start'],
4196             ['5760', '4 days before start'],
4197             ['7200', '5 days before start'],
4198             ['10080', '1 week before start'],
4199             ['20160', '2 weeks before start']
4200             ]
4201         });
4202     },
4203
4204     // inherited docs
4205     initValue: function() {
4206         if (this.value !== undefined) {
4207             this.setValue(this.value);
4208         }
4209         else {
4210             this.setValue('');
4211         }
4212         this.originalValue = this.getValue();
4213     }
4214 });
4215
4216 Ext.reg('reminderfield', Ext.calendar.ReminderField);
4217 /**
4218  * @class Ext.calendar.EventEditForm
4219  * @extends Ext.form.FormPanel
4220  * <p>A custom form used for detailed editing of events.</p>
4221  * <p>This is pretty much a standard form that is simply pre-configured for the options needed by the
4222  * calendar components. It is also configured to automatically bind records of type {@link Ext.calendar.EventRecord}
4223  * to and from the form.</p>
4224  * <p>This form also provides custom events specific to the calendar so that other calendar components can be easily
4225  * notified when an event has been edited via this component.</p>
4226  * <p>The default configs are as follows:</p><pre><code>
4227     labelWidth: 65,
4228     title: 'Event Form',
4229     titleTextAdd: 'Add Event',
4230     titleTextEdit: 'Edit Event',
4231     bodyStyle: 'background:transparent;padding:20px 20px 10px;',
4232     border: false,
4233     buttonAlign: 'center',
4234     autoHeight: true,
4235     cls: 'ext-evt-edit-form',
4236 </code></pre>
4237  * @constructor
4238  * @param {Object} config The config object
4239  */
4240 Ext.calendar.EventEditForm = Ext.extend(Ext.form.FormPanel, {
4241     labelWidth: 65,
4242     title: 'Event Form',
4243     titleTextAdd: 'Add Event',
4244     titleTextEdit: 'Edit Event',
4245     bodyStyle: 'background:transparent;padding:20px 20px 10px;',
4246     border: false,
4247     buttonAlign: 'center',
4248     autoHeight: true,
4249     // to allow for the notes field to autogrow
4250     cls: 'ext-evt-edit-form',
4251
4252     // private properties:
4253     newId: 10000,
4254     layout: 'column',
4255
4256     // private
4257     initComponent: function() {
4258
4259         this.addEvents({
4260             /**
4261              * @event eventadd
4262              * Fires after a new event is added
4263              * @param {Ext.calendar.EventEditForm} this
4264              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
4265              */
4266             eventadd: true,
4267             /**
4268              * @event eventupdate
4269              * Fires after an existing event is updated
4270              * @param {Ext.calendar.EventEditForm} this
4271              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
4272              */
4273             eventupdate: true,
4274             /**
4275              * @event eventdelete
4276              * Fires after an event is deleted
4277              * @param {Ext.calendar.EventEditForm} this
4278              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted
4279              */
4280             eventdelete: true,
4281             /**
4282              * @event eventcancel
4283              * Fires after an event add/edit operation is canceled by the user and no store update took place
4284              * @param {Ext.calendar.EventEditForm} this
4285              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
4286              */
4287             eventcancel: true
4288         });
4289
4290         this.titleField = new Ext.form.TextField({
4291             fieldLabel: 'Title',
4292             name: Ext.calendar.EventMappings.Title.name,
4293             anchor: '90%'
4294         });
4295         this.dateRangeField = new Ext.calendar.DateRangeField({
4296             fieldLabel: 'When',
4297             anchor: '90%'
4298         });
4299         this.reminderField = new Ext.calendar.ReminderField({
4300             name: 'Reminder'
4301         });
4302         this.notesField = new Ext.form.TextArea({
4303             fieldLabel: 'Notes',
4304             name: Ext.calendar.EventMappings.Notes.name,
4305             grow: true,
4306             growMax: 150,
4307             anchor: '100%'
4308         });
4309         this.locationField = new Ext.form.TextField({
4310             fieldLabel: 'Location',
4311             name: Ext.calendar.EventMappings.Location.name,
4312             anchor: '100%'
4313         });
4314         this.urlField = new Ext.form.TextField({
4315             fieldLabel: 'Web Link',
4316             name: Ext.calendar.EventMappings.Url.name,
4317             anchor: '100%'
4318         });
4319
4320         var leftFields = [this.titleField, this.dateRangeField, this.reminderField],
4321         rightFields = [this.notesField, this.locationField, this.urlField];
4322
4323         if (this.calendarStore) {
4324             this.calendarField = new Ext.calendar.CalendarPicker({
4325                 store: this.calendarStore,
4326                 name: Ext.calendar.EventMappings.CalendarId.name
4327             });
4328             leftFields.splice(2, 0, this.calendarField);
4329         };
4330
4331         this.items = [{
4332             id: 'left-col',
4333             columnWidth: 0.65,
4334             layout: 'form',
4335             border: false,
4336             items: leftFields
4337         },
4338         {
4339             id: 'right-col',
4340             columnWidth: 0.35,
4341             layout: 'form',
4342             border: false,
4343             items: rightFields
4344         }];
4345
4346         this.fbar = [{
4347             text: 'Save',
4348             scope: this,
4349             handler: this.onSave
4350         },
4351         {
4352             cls: 'ext-del-btn',
4353             text: 'Delete',
4354             scope: this,
4355             handler: this.onDelete
4356         },
4357         {
4358             text: 'Cancel',
4359             scope: this,
4360             handler: this.onCancel
4361         }];
4362
4363         Ext.calendar.EventEditForm.superclass.initComponent.call(this);
4364     },
4365
4366     // inherited docs
4367     loadRecord: function(rec) {
4368         this.form.loadRecord.apply(this.form, arguments);
4369         this.activeRecord = rec;
4370         this.dateRangeField.setValue(rec.data);
4371         if (this.calendarStore) {
4372             this.form.setValues({
4373                 'calendar': rec.data[Ext.calendar.EventMappings.CalendarId.name]
4374             });
4375         }
4376         this.isAdd = !!rec.data[Ext.calendar.EventMappings.IsNew.name];
4377         if (this.isAdd) {
4378             rec.markDirty();
4379             this.setTitle(this.titleTextAdd);
4380             Ext.select('.ext-del-btn').setDisplayed(false);
4381         }
4382         else {
4383             this.setTitle(this.titleTextEdit);
4384             Ext.select('.ext-del-btn').setDisplayed(true);
4385         }
4386         this.titleField.focus();
4387     },
4388
4389     // inherited docs
4390     updateRecord: function() {
4391         var dates = this.dateRangeField.getValue();
4392
4393         this.form.updateRecord(this.activeRecord);
4394         this.activeRecord.set(Ext.calendar.EventMappings.StartDate.name, dates[0]);
4395         this.activeRecord.set(Ext.calendar.EventMappings.EndDate.name, dates[1]);
4396         this.activeRecord.set(Ext.calendar.EventMappings.IsAllDay.name, dates[2]);
4397     },
4398
4399     // private
4400     onCancel: function() {
4401         this.cleanup(true);
4402         this.fireEvent('eventcancel', this, this.activeRecord);
4403     },
4404
4405     // private
4406     cleanup: function(hide) {
4407         if (this.activeRecord && this.activeRecord.dirty) {
4408             this.activeRecord.reject();
4409         }
4410         delete this.activeRecord;
4411
4412         if (this.form.isDirty()) {
4413             this.form.reset();
4414         }
4415     },
4416
4417     // private
4418     onSave: function() {
4419         if (!this.form.isValid()) {
4420             return;
4421         }
4422         this.updateRecord();
4423
4424         if (!this.activeRecord.dirty) {
4425             this.onCancel();
4426             return;
4427         }
4428
4429         this.fireEvent(this.isAdd ? 'eventadd': 'eventupdate', this, this.activeRecord);
4430     },
4431
4432     // private
4433     onDelete: function() {
4434         this.fireEvent('eventdelete', this, this.activeRecord);
4435     }
4436 });
4437
4438 Ext.reg('eventeditform', Ext.calendar.EventEditForm);
4439 /**
4440  * @class Ext.calendar.EventEditWindow
4441  * @extends Ext.Window
4442  * <p>A custom window containing a basic edit form used for quick editing of events.</p>
4443  * <p>This window also provides custom events specific to the calendar so that other calendar components can be easily
4444  * notified when an event has been edited via this component.</p>
4445  * @constructor
4446  * @param {Object} config The config object
4447  */
4448 Ext.calendar.EventEditWindow = function(config) {
4449     var formPanelCfg = {
4450         xtype: 'form',
4451         labelWidth: 65,
4452         frame: false,
4453         bodyStyle: 'background:transparent;padding:5px 10px 10px;',
4454         bodyBorder: false,
4455         border: false,
4456         items: [{
4457             id: 'title',
4458             name: Ext.calendar.EventMappings.Title.name,
4459             fieldLabel: 'Title',
4460             xtype: 'textfield',
4461             anchor: '100%'
4462         },
4463         {
4464             xtype: 'daterangefield',
4465             id: 'date-range',
4466             anchor: '100%',
4467             fieldLabel: 'When'
4468         }]
4469     };
4470
4471     if (config.calendarStore) {
4472         this.calendarStore = config.calendarStore;
4473         delete config.calendarStore;
4474
4475         formPanelCfg.items.push({
4476             xtype: 'calendarpicker',
4477             id: 'calendar',
4478             name: 'calendar',
4479             anchor: '100%',
4480             store: this.calendarStore
4481         });
4482     }
4483
4484     Ext.calendar.EventEditWindow.superclass.constructor.call(this, Ext.apply({
4485         titleTextAdd: 'Add Event',
4486         titleTextEdit: 'Edit Event',
4487         width: 600,
4488         autocreate: true,
4489         border: true,
4490         closeAction: 'hide',
4491         modal: false,
4492         resizable: false,
4493         buttonAlign: 'left',
4494         savingMessage: 'Saving changes...',
4495         deletingMessage: 'Deleting event...',
4496
4497         fbar: [{
4498             xtype: 'tbtext',
4499             text: '<a href="#" id="tblink">Edit Details...</a>'
4500         },
4501         '->', {
4502             text: 'Save',
4503             disabled: false,
4504             handler: this.onSave,
4505             scope: this
4506         },
4507         {
4508             id: 'delete-btn',
4509             text: 'Delete',
4510             disabled: false,
4511             handler: this.onDelete,
4512             scope: this,
4513             hideMode: 'offsets'
4514         },
4515         {
4516             text: 'Cancel',
4517             disabled: false,
4518             handler: this.onCancel,
4519             scope: this
4520         }],
4521         items: formPanelCfg
4522     },
4523     config));
4524 };
4525
4526 Ext.extend(Ext.calendar.EventEditWindow, Ext.Window, {
4527     // private
4528     newId: 10000,
4529
4530     // private
4531     initComponent: function() {
4532         Ext.calendar.EventEditWindow.superclass.initComponent.call(this);
4533
4534         this.formPanel = this.items.items[0];
4535
4536         this.addEvents({
4537             /**
4538              * @event eventadd
4539              * Fires after a new event is added
4540              * @param {Ext.calendar.EventEditWindow} this
4541              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
4542              */
4543             eventadd: true,
4544             /**
4545              * @event eventupdate
4546              * Fires after an existing event is updated
4547              * @param {Ext.calendar.EventEditWindow} this
4548              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
4549              */
4550             eventupdate: true,
4551             /**
4552              * @event eventdelete
4553              * Fires after an event is deleted
4554              * @param {Ext.calendar.EventEditWindow} this
4555              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted
4556              */
4557             eventdelete: true,
4558             /**
4559              * @event eventcancel
4560              * Fires after an event add/edit operation is canceled by the user and no store update took place
4561              * @param {Ext.calendar.EventEditWindow} this
4562              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
4563              */
4564             eventcancel: true,
4565             /**
4566              * @event editdetails
4567              * Fires when the user selects the option in this window to continue editing in the detailed edit form
4568              * (by default, an instance of {@link Ext.calendar.EventEditForm}. Handling code should hide this window
4569              * and transfer the current event record to the appropriate instance of the detailed form by showing it
4570              * and calling {@link Ext.calendar.EventEditForm#loadRecord loadRecord}.
4571              * @param {Ext.calendar.EventEditWindow} this
4572              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} that is currently being edited
4573              */
4574             editdetails: true
4575         });
4576     },
4577
4578     // private
4579     afterRender: function() {
4580         Ext.calendar.EventEditWindow.superclass.afterRender.call(this);
4581
4582         this.el.addClass('ext-cal-event-win');
4583
4584         Ext.get('tblink').on('click',
4585         function(e) {
4586             e.stopEvent();
4587             this.updateRecord();
4588             this.fireEvent('editdetails', this, this.activeRecord);
4589         },
4590         this);
4591     },
4592
4593     /**
4594      * Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden.
4595          * @param {Ext.data.Record/Object} o Either a {@link Ext.data.Record} if showing the form
4596          * for an existing event in edit mode, or a plain object containing a StartDate property (and 
4597          * optionally an EndDate property) for showing the form in add mode. 
4598      * @param {String/Element} animateTarget (optional) The target element or id from which the window should
4599      * animate while opening (defaults to null with no animation)
4600      * @return {Ext.Window} this
4601      */
4602     show: function(o, animateTarget) {
4603         // Work around the CSS day cell height hack needed for initial render in IE8/strict:
4604         var anim = (Ext.isIE8 && Ext.isStrict) ? null: animateTarget;
4605
4606         Ext.calendar.EventEditWindow.superclass.show.call(this, anim,
4607         function() {
4608             Ext.getCmp('title').focus(false, 100);
4609         });
4610         Ext.getCmp('delete-btn')[o.data && o.data[Ext.calendar.EventMappings.EventId.name] ? 'show': 'hide']();
4611
4612         var rec,
4613         f = this.formPanel.form;
4614
4615         if (o.data) {
4616             rec = o;
4617             this.isAdd = !!rec.data[Ext.calendar.EventMappings.IsNew.name];
4618             if (this.isAdd) {
4619                 // Enable adding the default record that was passed in
4620                 // if it's new even if the user makes no changes
4621                 rec.markDirty();
4622                 this.setTitle(this.titleTextAdd);
4623             }
4624             else {
4625                 this.setTitle(this.titleTextEdit);
4626             }
4627
4628             f.loadRecord(rec);
4629         }
4630         else {
4631             this.isAdd = true;
4632             this.setTitle(this.titleTextAdd);
4633
4634             var M = Ext.calendar.EventMappings,
4635             eventId = M.EventId.name,
4636             start = o[M.StartDate.name],
4637             end = o[M.EndDate.name] || start.add('h', 1);
4638
4639             rec = new Ext.calendar.EventRecord();
4640             rec.data[M.EventId.name] = this.newId++;
4641             rec.data[M.StartDate.name] = start;
4642             rec.data[M.EndDate.name] = end;
4643             rec.data[M.IsAllDay.name] = !!o[M.IsAllDay.name] || start.getDate() != end.clone().add(Date.MILLI, 1).getDate();
4644             rec.data[M.IsNew.name] = true;
4645
4646             f.reset();
4647             f.loadRecord(rec);
4648         }
4649
4650         if (this.calendarStore) {
4651             Ext.getCmp('calendar').setValue(rec.data[Ext.calendar.EventMappings.CalendarId.name]);
4652         }
4653         Ext.getCmp('date-range').setValue(rec.data);
4654         this.activeRecord = rec;
4655
4656         return this;
4657     },
4658
4659     // private
4660     roundTime: function(dt, incr) {
4661         incr = incr || 15;
4662         var m = parseInt(dt.getMinutes(), 10);
4663         return dt.add('mi', incr - (m % incr));
4664     },
4665
4666     // private
4667     onCancel: function() {
4668         this.cleanup(true);
4669         this.fireEvent('eventcancel', this);
4670     },
4671
4672     // private
4673     cleanup: function(hide) {
4674         if (this.activeRecord && this.activeRecord.dirty) {
4675             this.activeRecord.reject();
4676         }
4677         delete this.activeRecord;
4678
4679         if (hide === true) {
4680             // Work around the CSS day cell height hack needed for initial render in IE8/strict:
4681             //var anim = afterDelete || (Ext.isIE8 && Ext.isStrict) ? null : this.animateTarget;
4682             this.hide();
4683         }
4684     },
4685
4686     // private
4687     updateRecord: function() {
4688         var f = this.formPanel.form,
4689         dates = Ext.getCmp('date-range').getValue(),
4690         M = Ext.calendar.EventMappings;
4691
4692         f.updateRecord(this.activeRecord);
4693         this.activeRecord.set(M.StartDate.name, dates[0]);
4694         this.activeRecord.set(M.EndDate.name, dates[1]);
4695         this.activeRecord.set(M.IsAllDay.name, dates[2]);
4696         this.activeRecord.set(M.CalendarId.name, this.formPanel.form.findField('calendar').getValue());
4697     },
4698
4699     // private
4700     onSave: function() {
4701         if (!this.formPanel.form.isValid()) {
4702             return;
4703         }
4704         this.updateRecord();
4705
4706         if (!this.activeRecord.dirty) {
4707             this.onCancel();
4708             return;
4709         }
4710
4711         this.fireEvent(this.isAdd ? 'eventadd': 'eventupdate', this, this.activeRecord);
4712     },
4713
4714     // private
4715     onDelete: function() {
4716         this.fireEvent('eventdelete', this, this.activeRecord);
4717     }
4718 });/**
4719  * @class Ext.calendar.CalendarPanel
4720  * @extends Ext.Panel
4721  * <p>This is the default container for Ext calendar views. It supports day, week and month views as well
4722  * as a built-in event edit form. The only requirement for displaying a calendar is passing in a valid
4723  * {@link #calendarStore} config containing records of type {@link Ext.calendar.EventRecord EventRecord}. In order
4724  * to make the calendar interactive (enable editing, drag/drop, etc.) you can handle any of the various
4725  * events fired by the underlying views and exposed through the CalendarPanel.</p>
4726  * {@link #layoutConfig} option if needed.</p>
4727  * @constructor
4728  * @param {Object} config The config object
4729  * @xtype calendarpanel
4730  */
4731 Ext.calendar.CalendarPanel = Ext.extend(Ext.Panel, {
4732     /**
4733      * @cfg {Boolean} showDayView
4734      * True to include the day view (and toolbar button), false to hide them (defaults to true).
4735      */
4736     showDayView: true,
4737     /**
4738      * @cfg {Boolean} showWeekView
4739      * True to include the week view (and toolbar button), false to hide them (defaults to true).
4740      */
4741     showWeekView: true,
4742     /**
4743      * @cfg {Boolean} showMonthView
4744      * True to include the month view (and toolbar button), false to hide them (defaults to true).
4745      * If the day and week views are both hidden, the month view will show by default even if
4746      * this config is false.
4747      */
4748     showMonthView: true,
4749     /**
4750      * @cfg {Boolean} showNavBar
4751      * True to display the calendar navigation toolbar, false to hide it (defaults to true). Note that
4752      * if you hide the default navigation toolbar you'll have to provide an alternate means of navigating the calendar.
4753      */
4754     showNavBar: true,
4755     /**
4756      * @cfg {String} todayText
4757      * Alternate text to use for the 'Today' nav bar button.
4758      */
4759     todayText: 'Today',
4760     /**
4761      * @cfg {Boolean} showTodayText
4762      * True to show the value of {@link #todayText} instead of today's date in the calendar's current day box,
4763      * false to display the day number(defaults to true).
4764      */
4765     showTodayText: true,
4766     /**
4767      * @cfg {Boolean} showTime
4768      * True to display the current time next to the date in the calendar's current day box, false to not show it 
4769      * (defaults to true).
4770      */
4771     showTime: true,
4772     /**
4773      * @cfg {String} dayText
4774      * Alternate text to use for the 'Day' nav bar button.
4775      */
4776     dayText: 'Day',
4777     /**
4778      * @cfg {String} weekText
4779      * Alternate text to use for the 'Week' nav bar button.
4780      */
4781     weekText: 'Week',
4782     /**
4783      * @cfg {String} monthText
4784      * Alternate text to use for the 'Month' nav bar button.
4785      */
4786     monthText: 'Month',
4787
4788     // private
4789     layoutConfig: {
4790         layoutOnCardChange: true,
4791         deferredRender: true
4792     },
4793
4794     // private property
4795     startDate: new Date(),
4796
4797     // private
4798     initComponent: function() {
4799         this.tbar = {
4800             cls: 'ext-cal-toolbar',
4801             border: true,
4802             buttonAlign: 'center',
4803             items: [{
4804                 id: this.id + '-tb-prev',
4805                 handler: this.onPrevClick,
4806                 scope: this,
4807                 iconCls: 'x-tbar-page-prev'
4808             }]
4809         };
4810
4811         this.viewCount = 0;
4812
4813         if (this.showDayView) {
4814             this.tbar.items.push({
4815                 id: this.id + '-tb-day',
4816                 text: this.dayText,
4817                 handler: this.onDayClick,
4818                 scope: this,
4819                 toggleGroup: 'tb-views'
4820             });
4821             this.viewCount++;
4822         }
4823         if (this.showWeekView) {
4824             this.tbar.items.push({
4825                 id: this.id + '-tb-week',
4826                 text: this.weekText,
4827                 handler: this.onWeekClick,
4828                 scope: this,
4829                 toggleGroup: 'tb-views'
4830             });
4831             this.viewCount++;
4832         }
4833         if (this.showMonthView || this.viewCount == 0) {
4834             this.tbar.items.push({
4835                 id: this.id + '-tb-month',
4836                 text: this.monthText,
4837                 handler: this.onMonthClick,
4838                 scope: this,
4839                 toggleGroup: 'tb-views'
4840             });
4841             this.viewCount++;
4842             this.showMonthView = true;
4843         }
4844         this.tbar.items.push({
4845             id: this.id + '-tb-next',
4846             handler: this.onNextClick,
4847             scope: this,
4848             iconCls: 'x-tbar-page-next'
4849         });
4850         this.tbar.items.push('->');
4851
4852         var idx = this.viewCount - 1;
4853         this.activeItem = this.activeItem === undefined ? idx: (this.activeItem > idx ? idx: this.activeItem);
4854
4855         if (this.showNavBar === false) {
4856             delete this.tbar;
4857             this.addClass('x-calendar-nonav');
4858         }
4859
4860         Ext.calendar.CalendarPanel.superclass.initComponent.call(this);
4861
4862         this.addEvents({
4863             /**
4864              * @event eventadd
4865              * Fires after a new event is added to the underlying store
4866              * @param {Ext.calendar.CalendarPanel} this
4867              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
4868              */
4869             eventadd: true,
4870             /**
4871              * @event eventupdate
4872              * Fires after an existing event is updated
4873              * @param {Ext.calendar.CalendarPanel} this
4874              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
4875              */
4876             eventupdate: true,
4877             /**
4878              * @event eventdelete
4879              * Fires after an event is removed from the underlying store
4880              * @param {Ext.calendar.CalendarPanel} this
4881              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was removed
4882              */
4883             eventdelete: true,
4884             /**
4885              * @event eventcancel
4886              * Fires after an event add/edit operation is canceled by the user and no store update took place
4887              * @param {Ext.calendar.CalendarPanel} this
4888              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
4889              */
4890             eventcancel: true,
4891             /**
4892              * @event viewchange
4893              * Fires after a different calendar view is activated (but not when the event edit form is activated)
4894              * @param {Ext.calendar.CalendarPanel} this
4895              * @param {Ext.CalendarView} view The view being activated (any valid {@link Ext.calendar.CalendarView CalendarView} subclass)
4896              * @param {Object} info Extra information about the newly activated view. This is a plain object 
4897              * with following properties:<div class="mdetail-params"><ul>
4898              * <li><b><code>activeDate</code></b> : <div class="sub-desc">The currently-selected date</div></li>
4899              * <li><b><code>viewStart</code></b> : <div class="sub-desc">The first date in the new view range</div></li>
4900              * <li><b><code>viewEnd</code></b> : <div class="sub-desc">The last date in the new view range</div></li>
4901              * </ul></div>
4902              */
4903             viewchange: true
4904
4905             //
4906             // NOTE: CalendarPanel also relays the following events from contained views as if they originated from this:
4907             //
4908             /**
4909              * @event eventsrendered
4910              * Fires after events are finished rendering in the view
4911              * @param {Ext.calendar.CalendarPanel} this 
4912              */
4913             /**
4914              * @event eventclick
4915              * Fires after the user clicks on an event element
4916              * @param {Ext.calendar.CalendarPanel} this
4917              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on
4918              * @param {HTMLNode} el The DOM node that was clicked on
4919              */
4920             /**
4921              * @event eventover
4922              * Fires anytime the mouse is over an event element
4923              * @param {Ext.calendar.CalendarPanel} this
4924              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over
4925              * @param {HTMLNode} el The DOM node that is being moused over
4926              */
4927             /**
4928              * @event eventout
4929              * Fires anytime the mouse exits an event element
4930              * @param {Ext.calendar.CalendarPanel} this
4931              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited
4932              * @param {HTMLNode} el The DOM node that was exited
4933              */
4934             /**
4935              * @event datechange
4936              * Fires after the start date of the view changes
4937              * @param {Ext.calendar.CalendarPanel} this
4938              * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}
4939              * @param {Date} viewStart The first displayed date in the view
4940              * @param {Date} viewEnd The last displayed date in the view
4941              */
4942             /**
4943              * @event rangeselect
4944              * Fires after the user drags on the calendar to select a range of dates/times in which to create an event
4945              * @param {Ext.calendar.CalendarPanel} this
4946              * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected
4947              * @param {Function} callback A callback function that MUST be called after the event handling is complete so that
4948              * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the
4949              * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard
4950              * function call (e.g., callback()).
4951              */
4952             /**
4953              * @event eventmove
4954              * Fires after an event element is dragged by the user and dropped in a new position
4955              * @param {Ext.calendar.CalendarPanel} this
4956              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with
4957              * updated start and end dates
4958              */
4959             /**
4960              * @event initdrag
4961              * Fires when a drag operation is initiated in the view
4962              * @param {Ext.calendar.CalendarPanel} this
4963              */
4964             /**
4965              * @event eventresize
4966              * Fires after the user drags the resize handle of an event to resize it
4967              * @param {Ext.calendar.CalendarPanel} this
4968              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
4969              * containing the updated start and end dates
4970              */
4971             /**
4972              * @event dayclick
4973              * Fires after the user clicks within a day/week view container and not on an event element
4974              * @param {Ext.calendar.CalendarPanel} this
4975              * @param {Date} dt The date/time that was clicked on
4976              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false.
4977              * @param {Ext.Element} el The Element that was clicked on
4978              */
4979         });
4980
4981         this.layout = 'card';
4982         // do not allow override
4983         if (this.showDayView) {
4984             var day = Ext.apply({
4985                 xtype: 'dayview',
4986                 title: this.dayText,
4987                 showToday: this.showToday,
4988                 showTodayText: this.showTodayText,
4989                 showTime: this.showTime
4990             },
4991             this.dayViewCfg);
4992
4993             day.id = this.id + '-day';
4994             day.store = day.store || this.eventStore;
4995             this.initEventRelay(day);
4996             this.add(day);
4997         }
4998         if (this.showWeekView) {
4999             var wk = Ext.applyIf({
5000                 xtype: 'weekview',
5001                 title: this.weekText,
5002                 showToday: this.showToday,
5003                 showTodayText: this.showTodayText,
5004                 showTime: this.showTime
5005             },
5006             this.weekViewCfg);
5007
5008             wk.id = this.id + '-week';
5009             wk.store = wk.store || this.eventStore;
5010             this.initEventRelay(wk);
5011             this.add(wk);
5012         }
5013         if (this.showMonthView) {
5014             var month = Ext.applyIf({
5015                 xtype: 'monthview',
5016                 title: this.monthText,
5017                 showToday: this.showToday,
5018                 showTodayText: this.showTodayText,
5019                 showTime: this.showTime,
5020                 listeners: {
5021                     'weekclick': {
5022                         fn: function(vw, dt) {
5023                             this.showWeek(dt);
5024                         },
5025                         scope: this
5026                     }
5027                 }
5028             },
5029             this.monthViewCfg);
5030
5031             month.id = this.id + '-month';
5032             month.store = month.store || this.eventStore;
5033             this.initEventRelay(month);
5034             this.add(month);
5035         }
5036
5037         this.add(Ext.applyIf({
5038             xtype: 'eventeditform',
5039             id: this.id + '-edit',
5040             calendarStore: this.calendarStore,
5041             listeners: {
5042                 'eventadd': {
5043                     scope: this,
5044                     fn: this.onEventAdd
5045                 },
5046                 'eventupdate': {
5047                     scope: this,
5048                     fn: this.onEventUpdate
5049                 },
5050                 'eventdelete': {
5051                     scope: this,
5052                     fn: this.onEventDelete
5053                 },
5054                 'eventcancel': {
5055                     scope: this,
5056                     fn: this.onEventCancel
5057                 }
5058             }
5059         },
5060         this.editViewCfg));
5061     },
5062
5063     // private
5064     initEventRelay: function(cfg) {
5065         cfg.listeners = cfg.listeners || {};
5066         cfg.listeners.afterrender = {
5067             fn: function(c) {
5068                 // relay the view events so that app code only has to handle them in one place
5069                 this.relayEvents(c, ['eventsrendered', 'eventclick', 'eventover', 'eventout', 'dayclick',
5070                 'eventmove', 'datechange', 'rangeselect', 'eventdelete', 'eventresize', 'initdrag']);
5071             },
5072             scope: this,
5073             single: true
5074         };
5075     },
5076
5077     // private
5078     afterRender: function() {
5079         Ext.calendar.CalendarPanel.superclass.afterRender.call(this);
5080         this.fireViewChange();
5081     },
5082
5083     // private
5084     onLayout: function() {
5085         Ext.calendar.CalendarPanel.superclass.onLayout.call(this);
5086         if (!this.navInitComplete) {
5087             this.updateNavState();
5088             this.navInitComplete = true;
5089         }
5090     },
5091
5092     // private
5093     onEventAdd: function(form, rec) {
5094         rec.data[Ext.calendar.EventMappings.IsNew.name] = false;
5095         this.eventStore.add(rec);
5096         this.hideEditForm();
5097         this.fireEvent('eventadd', this, rec);
5098     },
5099
5100     // private
5101     onEventUpdate: function(form, rec) {
5102         rec.commit();
5103         this.hideEditForm();
5104         this.fireEvent('eventupdate', this, rec);
5105     },
5106
5107     // private
5108     onEventDelete: function(form, rec) {
5109         this.eventStore.remove(rec);
5110         this.hideEditForm();
5111         this.fireEvent('eventdelete', this, rec);
5112     },
5113
5114     // private
5115     onEventCancel: function(form, rec) {
5116         this.hideEditForm();
5117         this.fireEvent('eventcancel', this, rec);
5118     },
5119
5120     /**
5121      * Shows the built-in event edit form for the passed in event record.  This method automatically
5122      * hides the calendar views and navigation toolbar.  To return to the calendar, call {@link #hideEditForm}.
5123      * @param {Ext.calendar.EventRecord} record The event record to edit
5124      * @return {Ext.calendar.CalendarPanel} this
5125      */
5126     showEditForm: function(rec) {
5127         this.preEditView = this.layout.activeItem.id;
5128         this.setActiveView(this.id + '-edit');
5129         this.layout.activeItem.loadRecord(rec);
5130         return this;
5131     },
5132
5133     /**
5134      * Hides the built-in event edit form and returns to the previous calendar view. If the edit form is
5135      * not currently visible this method has no effect.
5136      * @return {Ext.calendar.CalendarPanel} this
5137      */
5138     hideEditForm: function() {
5139         if (this.preEditView) {
5140             this.setActiveView(this.preEditView);
5141             delete this.preEditView;
5142         }
5143         return this;
5144     },
5145
5146     // private
5147     setActiveView: function(id) {
5148         var l = this.layout;
5149         l.setActiveItem(id);
5150
5151         if (id == this.id + '-edit') {
5152             this.getTopToolbar().hide();
5153             this.doLayout();
5154         }
5155         else {
5156             l.activeItem.refresh();
5157             this.getTopToolbar().show();
5158             this.updateNavState();
5159         }
5160         this.activeView = l.activeItem;
5161         this.fireViewChange();
5162     },
5163
5164     // private
5165     fireViewChange: function() {
5166         var info = null,
5167             view = this.layout.activeItem;
5168
5169         if (view.getViewBounds) {
5170             vb = view.getViewBounds();
5171             info = {
5172                 activeDate: view.getStartDate(),
5173                 viewStart: vb.start,
5174                 viewEnd: vb.end
5175             };
5176         };
5177         this.fireEvent('viewchange', this, view, info);
5178     },
5179
5180     // private
5181     updateNavState: function() {
5182         if (this.showNavBar !== false) {
5183             var item = this.layout.activeItem,
5184             suffix = item.id.split(this.id + '-')[1];
5185
5186             var btn = Ext.getCmp(this.id + '-tb-' + suffix);
5187             btn.toggle(true);
5188         }
5189     },
5190
5191     /**
5192      * Sets the start date for the currently-active calendar view.
5193      * @param {Date} dt
5194      */
5195     setStartDate: function(dt) {
5196         this.layout.activeItem.setStartDate(dt, true);
5197         this.updateNavState();
5198         this.fireViewChange();
5199     },
5200
5201     // private
5202     showWeek: function(dt) {
5203         this.setActiveView(this.id + '-week');
5204         this.setStartDate(dt);
5205     },
5206
5207     // private
5208     onPrevClick: function() {
5209         this.startDate = this.layout.activeItem.movePrev();
5210         this.updateNavState();
5211         this.fireViewChange();
5212     },
5213
5214     // private
5215     onNextClick: function() {
5216         this.startDate = this.layout.activeItem.moveNext();
5217         this.updateNavState();
5218         this.fireViewChange();
5219     },
5220
5221     // private
5222     onDayClick: function() {
5223         this.setActiveView(this.id + '-day');
5224     },
5225
5226     // private
5227     onWeekClick: function() {
5228         this.setActiveView(this.id + '-week');
5229     },
5230
5231     // private
5232     onMonthClick: function() {
5233         this.setActiveView(this.id + '-month');
5234     },
5235
5236     /**
5237      * Return the calendar view that is currently active, which will be a subclass of
5238      * {@link Ext.calendar.CalendarView CalendarView}.
5239      * @return {Ext.calendar.CalendarView} The active view
5240      */
5241     getActiveView: function() {
5242         return this.layout.activeItem;
5243     }
5244 });
5245
5246 Ext.reg('calendarpanel', Ext.calendar.CalendarPanel);