0b218ef804ba988ca738a48eb02cbb46d4ad2e5e
[extjs.git] / examples / calendar / calendar-all-debug.js
1 /*!
2  * Ext JS Library 3.3.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.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 \r
2360             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
2361 \r
2362             // start from current month start, not view start:\r
2363             var end = start.add(Date.MONTH, 1).add(Date.SECOND, -1);\r
2364             // fill out to the end of the week:\r
2365             this.viewEnd = end.add(Date.DAY, 6 - end.getDay());\r
2366             return;\r
2367 \r
2368         default:\r
2369             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
2370             this.viewEnd = this.viewStart.add(Date.DAY, this.weekCount * 7).add(Date.SECOND, -1);\r
2371         }\r
2372     },\r
2373 \r
2374     // private\r
2375     getViewBounds: function() {\r
2376         return {\r
2377             start: this.viewStart,\r
2378             end: this.viewEnd\r
2379         };\r
2380     },\r
2381 \r
2382     /* private\r
2383      * Sort events for a single day for display in the calendar.  This sorts allday\r
2384      * events first, then non-allday events are sorted either based on event start\r
2385      * priority or span priority based on the value of {@link #spansHavePriority} \r
2386      * (defaults to event start priority).\r
2387      * @param {MixedCollection} evts A {@link Ext.util.MixedCollection MixedCollection}  \r
2388      * of {@link #Ext.calendar.EventRecord EventRecord} objects\r
2389      */\r
2390     sortEventRecordsForDay: function(evts) {\r
2391         if (evts.length < 2) {\r
2392             return;\r
2393         }\r
2394         evts.sort('ASC',\r
2395         function(evtA, evtB) {\r
2396             var a = evtA.data,\r
2397             b = evtB.data,\r
2398             M = Ext.calendar.EventMappings;\r
2399 \r
2400             // Always sort all day events before anything else\r
2401             if (a[M.IsAllDay.name]) {\r
2402                 return - 1;\r
2403             }\r
2404             else if (b[M.IsAllDay.name]) {\r
2405                 return 1;\r
2406             }\r
2407             if (this.spansHavePriority) {\r
2408                 // This logic always weights span events higher than non-span events\r
2409                 // (at the possible expense of start time order). This seems to\r
2410                 // be the approach used by Google calendar and can lead to a more\r
2411                 // visually appealing layout in complex cases, but event order is\r
2412                 // not guaranteed to be consistent.\r
2413                 var diff = Ext.calendar.Date.diffDays;\r
2414                 if (diff(a[M.StartDate.name], a[M.EndDate.name]) > 0) {\r
2415                     if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
2416                         // Both events are multi-day\r
2417                         if (a[M.StartDate.name].getTime() == b[M.StartDate.name].getTime()) {\r
2418                             // If both events start at the same time, sort the one\r
2419                             // that ends later (potentially longer span bar) first\r
2420                             return b[M.EndDate.name].getTime() - a[M.EndDate.name].getTime();\r
2421                         }\r
2422                         return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
2423                     }\r
2424                     return - 1;\r
2425                 }\r
2426                 else if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
2427                     return 1;\r
2428                 }\r
2429                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
2430             }\r
2431             else {\r
2432                 // Doing this allows span and non-span events to intermingle but\r
2433                 // remain sorted sequentially by start time. This seems more proper\r
2434                 // but can make for a less visually-compact layout when there are\r
2435                 // many such events mixed together closely on the calendar.\r
2436                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
2437             }\r
2438         }.createDelegate(this));\r
2439     },\r
2440 \r
2441     /**\r
2442      * Updates the view to contain the passed date\r
2443      * @param {Date} dt The date to display\r
2444      */\r
2445     moveTo: function(dt, noRefresh) {\r
2446         if (Ext.isDate(dt)) {\r
2447             this.setStartDate(dt);\r
2448             if (noRefresh !== false) {\r
2449                 this.refresh();\r
2450             }\r
2451             return this.startDate;\r
2452         }\r
2453         return dt;\r
2454     },\r
2455 \r
2456     /**\r
2457      * Updates the view to the next consecutive date(s)\r
2458      */\r
2459     moveNext: function(noRefresh) {\r
2460         return this.moveTo(this.viewEnd.add(Date.DAY, 1));\r
2461     },\r
2462 \r
2463     /**\r
2464      * Updates the view to the previous consecutive date(s)\r
2465      */\r
2466     movePrev: function(noRefresh) {\r
2467         var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd) + 1;\r
2468         return this.moveDays( - days, noRefresh);\r
2469     },\r
2470 \r
2471     /**\r
2472      * Shifts the view by the passed number of months relative to the currently set date\r
2473      * @param {Number} value The number of months (positive or negative) by which to shift the view\r
2474      */\r
2475     moveMonths: function(value, noRefresh) {\r
2476         return this.moveTo(this.startDate.add(Date.MONTH, value), noRefresh);\r
2477     },\r
2478 \r
2479     /**\r
2480      * Shifts the view by the passed number of weeks relative to the currently set date\r
2481      * @param {Number} value The number of weeks (positive or negative) by which to shift the view\r
2482      */\r
2483     moveWeeks: function(value, noRefresh) {\r
2484         return this.moveTo(this.startDate.add(Date.DAY, value * 7), noRefresh);\r
2485     },\r
2486 \r
2487     /**\r
2488      * Shifts the view by the passed number of days relative to the currently set date\r
2489      * @param {Number} value The number of days (positive or negative) by which to shift the view\r
2490      */\r
2491     moveDays: function(value, noRefresh) {\r
2492         return this.moveTo(this.startDate.add(Date.DAY, value), noRefresh);\r
2493     },\r
2494 \r
2495     /**\r
2496      * Updates the view to show today\r
2497      */\r
2498     moveToday: function(noRefresh) {\r
2499         return this.moveTo(new Date(), noRefresh);\r
2500     },\r
2501 \r
2502     /**\r
2503      * Sets the event store used by the calendar to display {@link Ext.calendar.EventRecord events}.\r
2504      * @param {Ext.data.Store} store\r
2505      */\r
2506     setStore: function(store, initial) {\r
2507         if (!initial && this.store) {\r
2508             this.store.un("datachanged", this.onDataChanged, this);\r
2509             this.store.un("add", this.onAdd, this);\r
2510             this.store.un("remove", this.onRemove, this);\r
2511             this.store.un("update", this.onUpdate, this);\r
2512             this.store.un("clear", this.refresh, this);\r
2513         }\r
2514         if (store) {\r
2515             store.on("datachanged", this.onDataChanged, this);\r
2516             store.on("add", this.onAdd, this);\r
2517             store.on("remove", this.onRemove, this);\r
2518             store.on("update", this.onUpdate, this);\r
2519             store.on("clear", this.refresh, this);\r
2520         }\r
2521         this.store = store;\r
2522         if (store && store.getCount() > 0) {\r
2523             this.refresh();\r
2524         }\r
2525     },\r
2526 \r
2527     getEventRecord: function(id) {\r
2528         var idx = this.store.find(Ext.calendar.EventMappings.EventId.name, id);\r
2529         return this.store.getAt(idx);\r
2530     },\r
2531 \r
2532     getEventRecordFromEl: function(el) {\r
2533         return this.getEventRecord(this.getEventIdFromEl(el));\r
2534     },\r
2535 \r
2536     // private\r
2537     getParams: function() {\r
2538         return {\r
2539             viewStart: this.viewStart,\r
2540             viewEnd: this.viewEnd,\r
2541             startDate: this.startDate,\r
2542             dayCount: this.dayCount,\r
2543             weekCount: this.weekCount,\r
2544             title: this.getTitle()\r
2545         };\r
2546     },\r
2547 \r
2548     getTitle: function() {\r
2549         return this.startDate.format('F Y');\r
2550     },\r
2551 \r
2552     /*\r
2553      * Shared click handling.  Each specific view also provides view-specific\r
2554      * click handling that calls this first.  This method returns true if it\r
2555      * can handle the click (and so the subclass should ignore it) else false.\r
2556      */\r
2557     onClick: function(e, t) {\r
2558         var el = e.getTarget(this.eventSelector, 5);\r
2559         if (el) {\r
2560             var id = this.getEventIdFromEl(el);\r
2561             this.fireEvent('eventclick', this, this.getEventRecord(id), el);\r
2562             return true;\r
2563         }\r
2564     },\r
2565 \r
2566     // private\r
2567     onMouseOver: function(e, t) {\r
2568         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
2569             if (!this.handleEventMouseEvent(e, t, 'over')) {\r
2570                 this.handleDayMouseEvent(e, t, 'over');\r
2571             }\r
2572         }\r
2573     },\r
2574 \r
2575     // private\r
2576     onMouseOut: function(e, t) {\r
2577         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
2578             if (!this.handleEventMouseEvent(e, t, 'out')) {\r
2579                 this.handleDayMouseEvent(e, t, 'out');\r
2580             }\r
2581         }\r
2582     },\r
2583 \r
2584     // private\r
2585     handleEventMouseEvent: function(e, t, type) {\r
2586         var el = e.getTarget(this.eventSelector, 5, true),\r
2587             rel,\r
2588             els,\r
2589             evtId;\r
2590         if (el) {\r
2591             rel = Ext.get(e.getRelatedTarget());\r
2592             if (el == rel || el.contains(rel)) {\r
2593                 return true;\r
2594             }\r
2595 \r
2596             evtId = this.getEventIdFromEl(el);\r
2597 \r
2598             if (this.eventOverClass != '') {\r
2599                 els = this.getEventEls(evtId);\r
2600                 els[type == 'over' ? 'addClass': 'removeClass'](this.eventOverClass);\r
2601             }\r
2602             this.fireEvent('event' + type, this, this.getEventRecord(evtId), el);\r
2603             return true;\r
2604         }\r
2605         return false;\r
2606     },\r
2607 \r
2608     // private\r
2609     getDateFromId: function(id, delim) {\r
2610         var parts = id.split(delim);\r
2611         return parts[parts.length - 1];\r
2612     },\r
2613 \r
2614     // private\r
2615     handleDayMouseEvent: function(e, t, type) {\r
2616         t = e.getTarget('td', 3);\r
2617         if (t) {\r
2618             if (t.id && t.id.indexOf(this.dayElIdDelimiter) > -1) {\r
2619                 var dt = this.getDateFromId(t.id, this.dayElIdDelimiter),\r
2620                 rel = Ext.get(e.getRelatedTarget()),\r
2621                 relTD,\r
2622                 relDate;\r
2623 \r
2624                 if (rel) {\r
2625                     relTD = rel.is('td') ? rel: rel.up('td', 3);\r
2626                     relDate = relTD && relTD.id ? this.getDateFromId(relTD.id, this.dayElIdDelimiter) : '';\r
2627                 }\r
2628                 if (!rel || dt != relDate) {\r
2629                     var el = this.getDayEl(dt);\r
2630                     if (el && this.dayOverClass != '') {\r
2631                         el[type == 'over' ? 'addClass': 'removeClass'](this.dayOverClass);\r
2632                     }\r
2633                     this.fireEvent('day' + type, this, Date.parseDate(dt, "Ymd"), el);\r
2634                 }\r
2635             }\r
2636         }\r
2637     },\r
2638 \r
2639     // private\r
2640     renderItems: function() {\r
2641         throw 'This method must be implemented by a subclass';\r
2642     }\r
2643 });/**\r
2644  * @class Ext.calendar.MonthView\r
2645  * @extends Ext.calendar.CalendarView\r
2646  * <p>Displays a calendar view by month. This class does not usually need ot be used directly as you can\r
2647  * use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including\r
2648  * the month view.</p>\r
2649  * @constructor\r
2650  * @param {Object} config The config object\r
2651  */\r
2652 Ext.calendar.MonthView = Ext.extend(Ext.calendar.CalendarView, {\r
2653     /**\r
2654      * @cfg {Boolean} showTime\r
2655      * True to display the current time in today's box in the calendar, false to not display it (defautls to true)\r
2656      */\r
2657     showTime: true,\r
2658     /**\r
2659      * @cfg {Boolean} showTodayText\r
2660      * True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)\r
2661      */\r
2662     showTodayText: true,\r
2663     /**\r
2664      * @cfg {String} todayText\r
2665      * The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')\r
2666      */\r
2667     todayText: 'Today',\r
2668     /**\r
2669      * @cfg {Boolean} showHeader\r
2670      * True to display a header beneath the navigation bar containing the week names above each week's column, false not to \r
2671      * show it and instead display the week names in the first row of days in the calendar (defaults to false).\r
2672      */\r
2673     showHeader: false,\r
2674     /**\r
2675      * @cfg {Boolean} showWeekLinks\r
2676      * True to display an extra column before the first day in the calendar that links to the {@link Ext.calendar.WeekView view}\r
2677      * for each individual week, false to not show it (defaults to false). If true, the week links can also contain the week \r
2678      * number depending on the value of {@link #showWeekNumbers}.\r
2679      */\r
2680     showWeekLinks: false,\r
2681     /**\r
2682      * @cfg {Boolean} showWeekNumbers\r
2683      * 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
2684      * Note that if {@link #showWeekLinks} is false this config will have no affect even if true.\r
2685      */\r
2686     showWeekNumbers: false,\r
2687     /**\r
2688      * @cfg {String} weekLinkOverClass\r
2689      * The CSS class name applied when the mouse moves over a week link element (only applies when {@link #showWeekLinks} is true,\r
2690      * defaults to 'ext-week-link-over').\r
2691      */\r
2692     weekLinkOverClass: 'ext-week-link-over',\r
2693 \r
2694     //private properties -- do not override:\r
2695     daySelector: '.ext-cal-day',\r
2696     moreSelector: '.ext-cal-ev-more',\r
2697     weekLinkSelector: '.ext-cal-week-link',\r
2698     weekCount: -1,\r
2699     // defaults to auto by month\r
2700     dayCount: 7,\r
2701     moreElIdDelimiter: '-more-',\r
2702     weekLinkIdDelimiter: 'ext-cal-week-',\r
2703 \r
2704     // private\r
2705     initComponent: function() {\r
2706         Ext.calendar.MonthView.superclass.initComponent.call(this);\r
2707         this.addEvents({\r
2708             /**\r
2709              * @event dayclick\r
2710              * Fires after the user clicks within the view container and not on an event element\r
2711              * @param {Ext.calendar.MonthView} this\r
2712              * @param {Date} dt The date/time that was clicked on\r
2713              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the \r
2714              * MonthView always return true for this param.\r
2715              * @param {Ext.Element} el The Element that was clicked on\r
2716              */\r
2717             dayclick: true,\r
2718             /**\r
2719              * @event weekclick\r
2720              * Fires after the user clicks within a week link (when {@link #showWeekLinks is true)\r
2721              * @param {Ext.calendar.MonthView} this\r
2722              * @param {Date} dt The start date of the week that was clicked on\r
2723              */\r
2724             weekclick: true,\r
2725             // inherited docs\r
2726             dayover: true,\r
2727             // inherited docs\r
2728             dayout: true\r
2729         });\r
2730     },\r
2731 \r
2732     // private\r
2733     initDD: function() {\r
2734         var cfg = {\r
2735             view: this,\r
2736             createText: this.ddCreateEventText,\r
2737             moveText: this.ddMoveEventText,\r
2738             ddGroup: 'MonthViewDD'\r
2739         };\r
2740 \r
2741         this.dragZone = new Ext.calendar.DragZone(this.el, cfg);\r
2742         this.dropZone = new Ext.calendar.DropZone(this.el, cfg);\r
2743     },\r
2744 \r
2745     // private\r
2746     onDestroy: function() {\r
2747         Ext.destroy(this.ddSelector);\r
2748         Ext.destroy(this.dragZone);\r
2749         Ext.destroy(this.dropZone);\r
2750         Ext.calendar.MonthView.superclass.onDestroy.call(this);\r
2751     },\r
2752 \r
2753     // private\r
2754     afterRender: function() {\r
2755         if (!this.tpl) {\r
2756             this.tpl = new Ext.calendar.MonthViewTemplate({\r
2757                 id: this.id,\r
2758                 showTodayText: this.showTodayText,\r
2759                 todayText: this.todayText,\r
2760                 showTime: this.showTime,\r
2761                 showHeader: this.showHeader,\r
2762                 showWeekLinks: this.showWeekLinks,\r
2763                 showWeekNumbers: this.showWeekNumbers\r
2764             });\r
2765         }\r
2766         this.tpl.compile();\r
2767         this.addClass('ext-cal-monthview ext-cal-ct');\r
2768 \r
2769         Ext.calendar.MonthView.superclass.afterRender.call(this);\r
2770     },\r
2771 \r
2772     // private\r
2773     onResize: function() {\r
2774         if (this.monitorResize) {\r
2775             this.maxEventsPerDay = this.getMaxEventsPerDay();\r
2776             this.refresh();\r
2777         }\r
2778     },\r
2779 \r
2780     // private\r
2781     forceSize: function() {\r
2782         // Compensate for the week link gutter width if visible\r
2783         if (this.showWeekLinks && this.el && this.el.child) {\r
2784             var hd = this.el.select('.ext-cal-hd-days-tbl'),\r
2785             bgTbl = this.el.select('.ext-cal-bg-tbl'),\r
2786             evTbl = this.el.select('.ext-cal-evt-tbl'),\r
2787             wkLinkW = this.el.child('.ext-cal-week-link').getWidth(),\r
2788             w = this.el.getWidth() - wkLinkW;\r
2789 \r
2790             hd.setWidth(w);\r
2791             bgTbl.setWidth(w);\r
2792             evTbl.setWidth(w);\r
2793         }\r
2794         Ext.calendar.MonthView.superclass.forceSize.call(this);\r
2795     },\r
2796 \r
2797     //private\r
2798     initClock: function() {\r
2799         if (Ext.fly(this.id + '-clock') !== null) {\r
2800             this.prevClockDay = new Date().getDay();\r
2801             if (this.clockTask) {\r
2802                 Ext.TaskMgr.stop(this.clockTask);\r
2803             }\r
2804             this.clockTask = Ext.TaskMgr.start({\r
2805                 run: function() {\r
2806                     var el = Ext.fly(this.id + '-clock'),\r
2807                     t = new Date();\r
2808 \r
2809                     if (t.getDay() == this.prevClockDay) {\r
2810                         if (el) {\r
2811                             el.update(t.format('g:i a'));\r
2812                         }\r
2813                     }\r
2814                     else {\r
2815                         this.prevClockDay = t.getDay();\r
2816                         this.moveTo(t);\r
2817                     }\r
2818                 },\r
2819                 scope: this,\r
2820                 interval: 1000\r
2821             });\r
2822         }\r
2823     },\r
2824 \r
2825     // inherited docs\r
2826     getEventBodyMarkup: function() {\r
2827         if (!this.eventBodyMarkup) {\r
2828             this.eventBodyMarkup = ['{Title}',\r
2829             '<tpl if="_isReminder">',\r
2830             '<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',\r
2831             '</tpl>',\r
2832             '<tpl if="_isRecurring">',\r
2833             '<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',\r
2834             '</tpl>',\r
2835             '<tpl if="spanLeft">',\r
2836             '<i class="ext-cal-spl">&nbsp;</i>',\r
2837             '</tpl>',\r
2838             '<tpl if="spanRight">',\r
2839             '<i class="ext-cal-spr">&nbsp;</i>',\r
2840             '</tpl>'\r
2841             ].join('');\r
2842         }\r
2843         return this.eventBodyMarkup;\r
2844     },\r
2845 \r
2846     // inherited docs\r
2847     getEventTemplate: function() {\r
2848         if (!this.eventTpl) {\r
2849             var tpl,\r
2850             body = this.getEventBodyMarkup();\r
2851 \r
2852             tpl = !(Ext.isIE || Ext.isOpera) ?\r
2853             new Ext.XTemplate(\r
2854             '<div id="{_elId}" class="{_selectorCls} {_colorCls} {values.spanCls} ext-cal-evt ext-cal-evr">',\r
2855             body,\r
2856             '</div>'\r
2857             )\r
2858             : new Ext.XTemplate(\r
2859             '<tpl if="_renderAsAllDay">',\r
2860             '<div id="{_elId}" class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evt ext-cal-evo">',\r
2861             '<div class="ext-cal-evm">',\r
2862             '<div class="ext-cal-evi">',\r
2863             '</tpl>',\r
2864             '<tpl if="!_renderAsAllDay">',\r
2865             '<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr">',\r
2866             '</tpl>',\r
2867             body,\r
2868             '<tpl if="_renderAsAllDay">',\r
2869             '</div>',\r
2870             '</div>',\r
2871             '</tpl>',\r
2872             '</div>'\r
2873             );\r
2874             tpl.compile();\r
2875             this.eventTpl = tpl;\r
2876         }\r
2877         return this.eventTpl;\r
2878     },\r
2879 \r
2880     // private\r
2881     getTemplateEventData: function(evt) {\r
2882         var M = Ext.calendar.EventMappings,\r
2883         selector = this.getEventSelectorCls(evt[M.EventId.name]),\r
2884         title = evt[M.Title.name];\r
2885 \r
2886         return Ext.applyIf({\r
2887             _selectorCls: selector,\r
2888             _colorCls: 'ext-color-' + (evt[M.CalendarId.name] ?\r
2889             evt[M.CalendarId.name] : 'default') + (evt._renderAsAllDay ? '-ad': ''),\r
2890             _elId: selector + '-' + evt._weekIndex,\r
2891             _isRecurring: evt.Recurrence && evt.Recurrence != '',\r
2892             _isReminder: evt[M.Reminder.name] && evt[M.Reminder.name] != '',\r
2893             Title: (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title)\r
2894         },\r
2895         evt);\r
2896     },\r
2897 \r
2898     // private\r
2899     refresh: function() {\r
2900         if (this.detailPanel) {\r
2901             this.detailPanel.hide();\r
2902         }\r
2903         Ext.calendar.MonthView.superclass.refresh.call(this);\r
2904 \r
2905         if (this.showTime !== false) {\r
2906             this.initClock();\r
2907         }\r
2908     },\r
2909 \r
2910     // private\r
2911     renderItems: function() {\r
2912         Ext.calendar.WeekEventRenderer.render({\r
2913             eventGrid: this.allDayOnly ? this.allDayGrid: this.eventGrid,\r
2914             viewStart: this.viewStart,\r
2915             tpl: this.getEventTemplate(),\r
2916             maxEventsPerDay: this.maxEventsPerDay,\r
2917             id: this.id,\r
2918             templateDataFn: this.getTemplateEventData.createDelegate(this),\r
2919             evtMaxCount: this.evtMaxCount,\r
2920             weekCount: this.weekCount,\r
2921             dayCount: this.dayCount\r
2922         });\r
2923         this.fireEvent('eventsrendered', this);\r
2924     },\r
2925 \r
2926     // private\r
2927     getDayEl: function(dt) {\r
2928         return Ext.get(this.getDayId(dt));\r
2929     },\r
2930 \r
2931     // private\r
2932     getDayId: function(dt) {\r
2933         if (Ext.isDate(dt)) {\r
2934             dt = dt.format('Ymd');\r
2935         }\r
2936         return this.id + this.dayElIdDelimiter + dt;\r
2937     },\r
2938 \r
2939     // private\r
2940     getWeekIndex: function(dt) {\r
2941         var el = this.getDayEl(dt).up('.ext-cal-wk-ct');\r
2942         return parseInt(el.id.split('-wk-')[1], 10);\r
2943     },\r
2944 \r
2945     // private\r
2946     getDaySize: function(contentOnly) {\r
2947         var box = this.el.getBox(),\r
2948         w = box.width / this.dayCount,\r
2949         h = box.height / this.getWeekCount();\r
2950 \r
2951         if (contentOnly) {\r
2952             var hd = this.el.select('.ext-cal-dtitle').first().parent('tr');\r
2953             h = hd ? h - hd.getHeight(true) : h;\r
2954         }\r
2955         return {\r
2956             height: h,\r
2957             width: w\r
2958         };\r
2959     },\r
2960 \r
2961     // private\r
2962     getEventHeight: function() {\r
2963         if (!this.eventHeight) {\r
2964             var evt = this.el.select('.ext-cal-evt').first();\r
2965             this.eventHeight = evt ? evt.parent('tr').getHeight() : 18;\r
2966         }\r
2967         return this.eventHeight;\r
2968     },\r
2969 \r
2970     // private\r
2971     getMaxEventsPerDay: function() {\r
2972         var dayHeight = this.getDaySize(true).height,\r
2973             h = this.getEventHeight(),\r
2974             max = Math.max(Math.floor((dayHeight - h) / h), 0);\r
2975 \r
2976         return max;\r
2977     },\r
2978 \r
2979     // private\r
2980     getDayAt: function(x, y) {\r
2981         var box = this.el.getBox(),\r
2982             daySize = this.getDaySize(),\r
2983             dayL = Math.floor(((x - box.x) / daySize.width)),\r
2984             dayT = Math.floor(((y - box.y) / daySize.height)),\r
2985             days = (dayT * 7) + dayL,\r
2986             dt = this.viewStart.add(Date.DAY, days);\r
2987         return {\r
2988             date: dt,\r
2989             el: this.getDayEl(dt)\r
2990         };\r
2991     },\r
2992 \r
2993     // inherited docs\r
2994     moveNext: function() {\r
2995         return this.moveMonths(1);\r
2996     },\r
2997 \r
2998     // inherited docs\r
2999     movePrev: function() {\r
3000         return this.moveMonths( - 1);\r
3001     },\r
3002 \r
3003     // private\r
3004     onInitDrag: function() {\r
3005         Ext.calendar.MonthView.superclass.onInitDrag.call(this);\r
3006         Ext.select(this.daySelector).removeClass(this.dayOverClass);\r
3007         if (this.detailPanel) {\r
3008             this.detailPanel.hide();\r
3009         }\r
3010     },\r
3011 \r
3012     // private\r
3013     onMoreClick: function(dt) {\r
3014         if (!this.detailPanel) {\r
3015             this.detailPanel = new Ext.Panel({\r
3016                 id: this.id + '-details-panel',\r
3017                 title: dt.format('F j'),\r
3018                 layout: 'fit',\r
3019                 floating: true,\r
3020                 renderTo: Ext.getBody(),\r
3021                 tools: [{\r
3022                     id: 'close',\r
3023                     handler: function(e, t, p) {\r
3024                         p.hide();\r
3025                     }\r
3026                 }],\r
3027                 items: {\r
3028                     xtype: 'monthdaydetailview',\r
3029                     id: this.id + '-details-view',\r
3030                     date: dt,\r
3031                     view: this,\r
3032                     store: this.store,\r
3033                     listeners: {\r
3034                         'eventsrendered': this.onDetailViewUpdated.createDelegate(this)\r
3035                     }\r
3036                 }\r
3037             });\r
3038         }\r
3039         else {\r
3040             this.detailPanel.setTitle(dt.format('F j'));\r
3041         }\r
3042         this.detailPanel.getComponent(this.id + '-details-view').update(dt);\r
3043     },\r
3044 \r
3045     // private\r
3046     onDetailViewUpdated: function(view, dt, numEvents) {\r
3047         var p = this.detailPanel,\r
3048         frameH = p.getFrameHeight(),\r
3049         evtH = this.getEventHeight(),\r
3050         bodyH = frameH + (numEvents * evtH) + 3,\r
3051         dayEl = this.getDayEl(dt),\r
3052         box = dayEl.getBox();\r
3053 \r
3054         p.updateBox(box);\r
3055         p.setHeight(bodyH);\r
3056         p.setWidth(Math.max(box.width, 220));\r
3057         p.show();\r
3058         p.getPositionEl().alignTo(dayEl, 't-t?');\r
3059     },\r
3060 \r
3061     // private\r
3062     onHide: function() {\r
3063         Ext.calendar.MonthView.superclass.onHide.call(this);\r
3064         if (this.detailPanel) {\r
3065             this.detailPanel.hide();\r
3066         }\r
3067     },\r
3068 \r
3069     // private\r
3070     onClick: function(e, t) {\r
3071         if (this.detailPanel) {\r
3072             this.detailPanel.hide();\r
3073         }\r
3074         if (Ext.calendar.MonthView.superclass.onClick.apply(this, arguments)) {\r
3075             // The superclass handled the click already so exit\r
3076             return;\r
3077         }\r
3078         if (this.dropZone) {\r
3079             this.dropZone.clearShims();\r
3080         }\r
3081         var el = e.getTarget(this.weekLinkSelector, 3),\r
3082             dt,\r
3083             parts;\r
3084         if (el) {\r
3085             dt = el.id.split(this.weekLinkIdDelimiter)[1];\r
3086             this.fireEvent('weekclick', this, Date.parseDate(dt, 'Ymd'));\r
3087             return;\r
3088         }\r
3089         el = e.getTarget(this.moreSelector, 3);\r
3090         if (el) {\r
3091             dt = el.id.split(this.moreElIdDelimiter)[1];\r
3092             this.onMoreClick(Date.parseDate(dt, 'Ymd'));\r
3093             return;\r
3094         }\r
3095         el = e.getTarget('td', 3);\r
3096         if (el) {\r
3097             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {\r
3098                 parts = el.id.split(this.dayElIdDelimiter);\r
3099                 dt = parts[parts.length - 1];\r
3100 \r
3101                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), false, Ext.get(this.getDayId(dt)));\r
3102                 return;\r
3103             }\r
3104         }\r
3105     },\r
3106 \r
3107     // private\r
3108     handleDayMouseEvent: function(e, t, type) {\r
3109         var el = e.getTarget(this.weekLinkSelector, 3, true);\r
3110         if (el) {\r
3111             el[type == 'over' ? 'addClass': 'removeClass'](this.weekLinkOverClass);\r
3112             return;\r
3113         }\r
3114         Ext.calendar.MonthView.superclass.handleDayMouseEvent.apply(this, arguments);\r
3115     }\r
3116 });\r
3117 \r
3118 Ext.reg('monthview', Ext.calendar.MonthView);\r
3119 /**
3120  * @class Ext.calendar.DayHeaderView
3121  * @extends Ext.calendar.MonthView
3122  * <p>This is the header area container within the day and week views where all-day events are displayed.
3123  * Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}
3124  * which aggregates this class and the {@link Ext.calendar.DayBodyView DayBodyView} into the single unified view
3125  * presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>
3126  * @constructor
3127  * @param {Object} config The config object
3128  */
3129 Ext.calendar.DayHeaderView = Ext.extend(Ext.calendar.MonthView, {
3130     // private configs
3131     weekCount: 1,
3132     dayCount: 1,
3133     allDayOnly: true,
3134     monitorResize: false,
3135
3136     /**
3137      * @event dayclick
3138      * Fires after the user clicks within the day view container and not on an event element
3139      * @param {Ext.calendar.DayBodyView} this
3140      * @param {Date} dt The date/time that was clicked on
3141      * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the 
3142      * DayHeaderView always return true for this param.
3143      * @param {Ext.Element} el The Element that was clicked on
3144      */
3145
3146     // private
3147     afterRender: function() {
3148         if (!this.tpl) {
3149             this.tpl = new Ext.calendar.DayHeaderTemplate({
3150                 id: this.id,
3151                 showTodayText: this.showTodayText,
3152                 todayText: this.todayText,
3153                 showTime: this.showTime
3154             });
3155         }
3156         this.tpl.compile();
3157         this.addClass('ext-cal-day-header');
3158
3159         Ext.calendar.DayHeaderView.superclass.afterRender.call(this);
3160     },
3161
3162     // private
3163     forceSize: Ext.emptyFn,
3164
3165     // private
3166     refresh: function() {
3167         Ext.calendar.DayHeaderView.superclass.refresh.call(this);
3168         this.recalcHeaderBox();
3169     },
3170
3171     // private
3172     recalcHeaderBox: function() {
3173         var tbl = this.el.child('.ext-cal-evt-tbl'),
3174         h = tbl.getHeight();
3175
3176         this.el.setHeight(h + 7);
3177
3178         if (Ext.isIE && Ext.isStrict) {
3179             this.el.child('.ext-cal-hd-ad-inner').setHeight(h + 4);
3180         }
3181         if (Ext.isOpera) {
3182             //TODO: figure out why Opera refuses to refresh height when
3183             //the new height is lower than the previous one
3184             //            var ct = this.el.child('.ext-cal-hd-ct');
3185             //            ct.repaint();
3186             }
3187     },
3188
3189     // private
3190     moveNext: function(noRefresh) {
3191         this.moveDays(this.dayCount, noRefresh);
3192     },
3193
3194     // private
3195     movePrev: function(noRefresh) {
3196         this.moveDays( - this.dayCount, noRefresh);
3197     },
3198
3199     // private
3200     onClick: function(e, t) {
3201         var el = e.getTarget('td', 3),
3202             parts,
3203             dt;
3204         if (el) {
3205             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
3206                 parts = el.id.split(this.dayElIdDelimiter);
3207                 dt = parts[parts.length - 1];
3208
3209                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt)));
3210                 return;
3211             }
3212         }
3213         Ext.calendar.DayHeaderView.superclass.onClick.apply(this, arguments);
3214     }
3215 });
3216
3217 Ext.reg('dayheaderview', Ext.calendar.DayHeaderView);
3218 /**S
3219  * @class Ext.calendar.DayBodyView
3220  * @extends Ext.calendar.CalendarView
3221  * <p>This is the scrolling container within the day and week views where non-all-day events are displayed.
3222  * Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}
3223  * which aggregates this class and the {@link Ext.calendar.DayHeaderView DayHeaderView} into the single unified view
3224  * presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>
3225  * @constructor
3226  * @param {Object} config The config object
3227  */
3228 Ext.calendar.DayBodyView = Ext.extend(Ext.calendar.CalendarView, {
3229     //private
3230     dayColumnElIdDelimiter: '-day-col-',
3231
3232     //private
3233     initComponent: function() {
3234         Ext.calendar.DayBodyView.superclass.initComponent.call(this);
3235
3236         this.addEvents({
3237             /**
3238              * @event eventresize
3239              * Fires after the user drags the resize handle of an event to resize it
3240              * @param {Ext.calendar.DayBodyView} this
3241              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
3242              * containing the updated start and end dates
3243              */
3244             eventresize: true,
3245             /**
3246              * @event dayclick
3247              * Fires after the user clicks within the day view container and not on an event element
3248              * @param {Ext.calendar.DayBodyView} this
3249              * @param {Date} dt The date/time that was clicked on
3250              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the 
3251              * DayBodyView always return false for this param.
3252              * @param {Ext.Element} el The Element that was clicked on
3253              */
3254             dayclick: true
3255         });
3256     },
3257
3258     //private
3259     initDD: function() {
3260         var cfg = {
3261             createText: this.ddCreateEventText,
3262             moveText: this.ddMoveEventText,
3263             resizeText: this.ddResizeEventText
3264         };
3265
3266         this.el.ddScrollConfig = {
3267             // scrolling is buggy in IE/Opera for some reason.  A larger vthresh
3268             // makes it at least functional if not perfect
3269             vthresh: Ext.isIE || Ext.isOpera ? 100: 40,
3270             hthresh: -1,
3271             frequency: 50,
3272             increment: 100,
3273             ddGroup: 'DayViewDD'
3274         };
3275         this.dragZone = new Ext.calendar.DayViewDragZone(this.el, Ext.apply({
3276             view: this,
3277             containerScroll: true
3278         },
3279         cfg));
3280
3281         this.dropZone = new Ext.calendar.DayViewDropZone(this.el, Ext.apply({
3282             view: this
3283         },
3284         cfg));
3285     },
3286
3287     //private
3288     refresh: function() {
3289         var top = this.el.getScroll().top;
3290         this.prepareData();
3291         this.renderTemplate();
3292         this.renderItems();
3293
3294         // skip this if the initial render scroll position has not yet been set.
3295         // necessary since IE/Opera must be deferred, so the first refresh will
3296         // override the initial position by default and always set it to 0.
3297         if (this.scrollReady) {
3298             this.scrollTo(top);
3299         }
3300     },
3301
3302     /**
3303      * Scrolls the container to the specified vertical position. If the view is large enough that
3304      * there is no scroll overflow then this method will have no affect.
3305      * @param {Number} y The new vertical scroll position in pixels 
3306      * @param {Boolean} defer (optional) <p>True to slightly defer the call, false to execute immediately.</p> 
3307      * <p>This method will automatically defer itself for IE and Opera (even if you pass false) otherwise
3308      * the scroll position will not update in those browsers. You can optionally pass true, however, to
3309      * force the defer in all browsers, or use your own custom conditions to determine whether this is needed.</p>
3310      * <p>Note that this method should not generally need to be called directly as scroll position is managed internally.</p>
3311      */
3312     scrollTo: function(y, defer) {
3313         defer = defer || (Ext.isIE || Ext.isOpera);
3314         if (defer) {
3315             (function() {
3316                 this.el.scrollTo('top', y);
3317                 this.scrollReady = true;
3318             }).defer(10, this);
3319         }
3320         else {
3321             this.el.scrollTo('top', y);
3322             this.scrollReady = true;
3323         }
3324     },
3325
3326     // private
3327     afterRender: function() {
3328         if (!this.tpl) {
3329             this.tpl = new Ext.calendar.DayBodyTemplate({
3330                 id: this.id,
3331                 dayCount: this.dayCount,
3332                 showTodayText: this.showTodayText,
3333                 todayText: this.todayText,
3334                 showTime: this.showTime
3335             });
3336         }
3337         this.tpl.compile();
3338
3339         this.addClass('ext-cal-body-ct');
3340
3341         Ext.calendar.DayBodyView.superclass.afterRender.call(this);
3342
3343         // default scroll position to 7am:
3344         this.scrollTo(7 * 42);
3345     },
3346
3347     // private
3348     forceSize: Ext.emptyFn,
3349
3350     // private
3351     onEventResize: function(rec, data) {
3352         var D = Ext.calendar.Date,
3353         start = Ext.calendar.EventMappings.StartDate.name,
3354         end = Ext.calendar.EventMappings.EndDate.name;
3355
3356         if (D.compare(rec.data[start], data.StartDate) === 0 &&
3357         D.compare(rec.data[end], data.EndDate) === 0) {
3358             // no changes
3359             return;
3360         }
3361         rec.set(start, data.StartDate);
3362         rec.set(end, data.EndDate);
3363
3364         this.fireEvent('eventresize', this, rec);
3365     },
3366
3367     // inherited docs
3368     getEventBodyMarkup: function() {
3369         if (!this.eventBodyMarkup) {
3370             this.eventBodyMarkup = ['{Title}',
3371             '<tpl if="_isReminder">',
3372             '<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',
3373             '</tpl>',
3374             '<tpl if="_isRecurring">',
3375             '<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',
3376             '</tpl>'
3377             //                '<tpl if="spanLeft">',
3378             //                    '<i class="ext-cal-spl">&nbsp;</i>',
3379             //                '</tpl>',
3380             //                '<tpl if="spanRight">',
3381             //                    '<i class="ext-cal-spr">&nbsp;</i>',
3382             //                '</tpl>'
3383             ].join('');
3384         }
3385         return this.eventBodyMarkup;
3386     },
3387
3388     // inherited docs
3389     getEventTemplate: function() {
3390         if (!this.eventTpl) {
3391             this.eventTpl = !(Ext.isIE || Ext.isOpera) ?
3392             new Ext.XTemplate(
3393             '<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
3394             '<div class="ext-evt-bd">', this.getEventBodyMarkup(), '</div>',
3395             '<div class="ext-evt-rsz"><div class="ext-evt-rsz-h">&nbsp;</div></div>',
3396             '</div>'
3397             )
3398             : new Ext.XTemplate(
3399             '<div id="{_elId}" class="ext-cal-evt {_selectorCls} {_colorCls}-x" style="left: {_left}%; width: {_width}%; top: {_top}px;">',
3400             '<div class="ext-cal-evb">&nbsp;</div>',
3401             '<dl style="height: {_height}px;" class="ext-cal-evdm">',
3402             '<dd class="ext-evt-bd">',
3403             this.getEventBodyMarkup(),
3404             '</dd>',
3405             '<div class="ext-evt-rsz"><div class="ext-evt-rsz-h">&nbsp;</div></div>',
3406             '</dl>',
3407             '<div class="ext-cal-evb">&nbsp;</div>',
3408             '</div>'
3409             );
3410             this.eventTpl.compile();
3411         }
3412         return this.eventTpl;
3413     },
3414
3415     /**
3416      * <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type
3417      * {@link Ext.calendar.EventRecord}) to populate the calendar views with <strong>all-day</strong> events. 
3418      * Internally this method by default generates different markup for browsers that support CSS border radius 
3419      * and those that don't. This method can be overridden as needed to customize the markup generated.</p>
3420      * <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately
3421      * from the surrounding container markup.  This provdes the flexibility to customize what's in the body without
3422      * having to override the entire XTemplate. If you do override this method, you should make sure that your 
3423      * overridden version also does the same.</p>
3424      * @return {Ext.XTemplate} The event XTemplate
3425      */
3426     getEventAllDayTemplate: function() {
3427         if (!this.eventAllDayTpl) {
3428             var tpl,
3429             body = this.getEventBodyMarkup();
3430
3431             tpl = !(Ext.isIE || Ext.isOpera) ?
3432             new Ext.XTemplate(
3433             '<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;">',
3434             body,
3435             '</div>'
3436             )
3437             : new Ext.XTemplate(
3438             '<div id="{_elId}" class="ext-cal-evt" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
3439             '<div class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evo">',
3440             '<div class="ext-cal-evm">',
3441             '<div class="ext-cal-evi">',
3442             body,
3443             '</div>',
3444             '</div>',
3445             '</div></div>'
3446             );
3447             tpl.compile();
3448             this.eventAllDayTpl = tpl;
3449         }
3450         return this.eventAllDayTpl;
3451     },
3452
3453     // private
3454     getTemplateEventData: function(evt) {
3455         var selector = this.getEventSelectorCls(evt[Ext.calendar.EventMappings.EventId.name]),
3456         data = {},
3457         M = Ext.calendar.EventMappings;
3458
3459         this.getTemplateEventBox(evt);
3460
3461         data._selectorCls = selector;
3462         data._colorCls = 'ext-color-' + evt[M.CalendarId.name] + (evt._renderAsAllDay ? '-ad': '');
3463         data._elId = selector + (evt._weekIndex ? '-' + evt._weekIndex: '');
3464         data._isRecurring = evt.Recurrence && evt.Recurrence != '';
3465         data._isReminder = evt[M.Reminder.name] && evt[M.Reminder.name] != '';
3466         var title = evt[M.Title.name];
3467         data.Title = (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title);
3468
3469         return Ext.applyIf(data, evt);
3470     },
3471
3472     // private
3473     getTemplateEventBox: function(evt) {
3474         var heightFactor = 0.7,
3475             start = evt[Ext.calendar.EventMappings.StartDate.name],
3476             end = evt[Ext.calendar.EventMappings.EndDate.name],
3477             startMins = start.getHours() * 60 + start.getMinutes(),
3478             endMins = end.getHours() * 60 + end.getMinutes(),
3479             diffMins = endMins - startMins;
3480
3481         evt._left = 0;
3482         evt._width = 100;
3483         evt._top = Math.round(startMins * heightFactor) + 1;
3484         evt._height = Math.max((diffMins * heightFactor) - 2, 15);
3485     },
3486
3487     // private
3488     renderItems: function() {
3489         var day = 0,
3490             evts = [],
3491             ev,
3492             d,
3493             ct,
3494             item,
3495             i,
3496             j,
3497             l,
3498             overlapCols,
3499             prevCol,
3500             colWidth,
3501             evtWidth,
3502             markup,
3503             target;
3504         for (; day < this.dayCount; day++) {
3505             ev = emptyCells = skipped = 0;
3506             d = this.eventGrid[0][day];
3507             ct = d ? d.length: 0;
3508
3509             for (; ev < ct; ev++) {
3510                 evt = d[ev];
3511                 if (!evt) {
3512                     continue;
3513                 }
3514                 item = evt.data || evt.event.data;
3515                 if (item._renderAsAllDay) {
3516                     continue;
3517                 }
3518                 Ext.apply(item, {
3519                     cls: 'ext-cal-ev',
3520                     _positioned: true
3521                 });
3522                 evts.push({
3523                     data: this.getTemplateEventData(item),
3524                     date: this.viewStart.add(Date.DAY, day)
3525                 });
3526             }
3527         }
3528
3529         // overlapping event pre-processing loop
3530         i = j = overlapCols = prevCol = 0;
3531         l = evts.length;
3532         for (; i < l; i++) {
3533             evt = evts[i].data;
3534             evt2 = null;
3535             prevCol = overlapCols;
3536             for (j = 0; j < l; j++) {
3537                 if (i == j) {
3538                     continue;
3539                 }
3540                 evt2 = evts[j].data;
3541                 if (this.isOverlapping(evt, evt2)) {
3542                     evt._overlap = evt._overlap == undefined ? 1: evt._overlap + 1;
3543                     if (i < j) {
3544                         if (evt._overcol === undefined) {
3545                             evt._overcol = 0;
3546                         }
3547                         evt2._overcol = evt._overcol + 1;
3548                         overlapCols = Math.max(overlapCols, evt2._overcol);
3549                     }
3550                 }
3551             }
3552         }
3553
3554         // rendering loop
3555         for (i = 0; i < l; i++) {
3556             evt = evts[i].data;
3557             if (evt._overlap !== undefined) {
3558                 colWidth = 100 / (overlapCols + 1);
3559                 evtWidth = 100 - (colWidth * evt._overlap);
3560
3561                 evt._width = colWidth;
3562                 evt._left = colWidth * evt._overcol;
3563             }
3564             markup = this.getEventTemplate().apply(evt);
3565             target = this.id + '-day-col-' + evts[i].date.format('Ymd');
3566
3567             Ext.DomHelper.append(target, markup);
3568         }
3569
3570         this.fireEvent('eventsrendered', this);
3571     },
3572
3573     // private
3574     getDayEl: function(dt) {
3575         return Ext.get(this.getDayId(dt));
3576     },
3577
3578     // private
3579     getDayId: function(dt) {
3580         if (Ext.isDate(dt)) {
3581             dt = dt.format('Ymd');
3582         }
3583         return this.id + this.dayColumnElIdDelimiter + dt;
3584     },
3585
3586     // private
3587     getDaySize: function() {
3588         var box = this.el.child('.ext-cal-day-col-inner').getBox();
3589         return {
3590             height: box.height,
3591             width: box.width
3592         };
3593     },
3594
3595     // private
3596     getDayAt: function(x, y) {
3597         var sel = '.ext-cal-body-ct',
3598         xoffset = this.el.child('.ext-cal-day-times').getWidth(),
3599         viewBox = this.el.getBox(),
3600         daySize = this.getDaySize(false),
3601         relX = x - viewBox.x - xoffset,
3602         dayIndex = Math.floor(relX / daySize.width),
3603         // clicked col index
3604         scroll = this.el.getScroll(),
3605         row = this.el.child('.ext-cal-bg-row'),
3606         // first avail row, just to calc size
3607         rowH = row.getHeight() / 2,
3608         // 30 minute increment since a row is 60 minutes
3609         relY = y - viewBox.y - rowH + scroll.top,
3610         rowIndex = Math.max(0, Math.ceil(relY / rowH)),
3611         mins = rowIndex * 30,
3612         dt = this.viewStart.add(Date.DAY, dayIndex).add(Date.MINUTE, mins),
3613         el = this.getDayEl(dt),
3614         timeX = x;
3615
3616         if (el) {
3617             timeX = el.getLeft();
3618         }
3619
3620         return {
3621             date: dt,
3622             el: el,
3623             // this is the box for the specific time block in the day that was clicked on:
3624             timeBox: {
3625                 x: timeX,
3626                 y: (rowIndex * 21) + viewBox.y - scroll.top,
3627                 width: daySize.width,
3628                 height: rowH
3629             }
3630         };
3631     },
3632
3633     // private
3634     onClick: function(e, t) {
3635         if (this.dragPending || Ext.calendar.DayBodyView.superclass.onClick.apply(this, arguments)) {
3636             // The superclass handled the click already so exit
3637             return;
3638         }
3639         if (e.getTarget('.ext-cal-day-times', 3) !== null) {
3640             // ignore clicks on the times-of-day gutter
3641             return;
3642         }
3643         var el = e.getTarget('td', 3);
3644         if (el) {
3645             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
3646                 var dt = this.getDateFromId(el.id, this.dayElIdDelimiter);
3647                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt, true)));
3648                 return;
3649             }
3650         }
3651         var day = this.getDayAt(e.xy[0], e.xy[1]);
3652         if (day && day.date) {
3653             this.fireEvent('dayclick', this, day.date, false, null);
3654         }
3655     }
3656 });
3657
3658 Ext.reg('daybodyview', Ext.calendar.DayBodyView);
3659 /**
3660  * @class Ext.calendar.DayView
3661  * @extends Ext.Container
3662  * <p>Unlike other calendar views, is not actually a subclass of {@link Ext.calendar.CalendarView CalendarView}.
3663  * Instead it is a {@link Ext.Container Container} subclass that internally creates and manages the layouts of
3664  * a {@link Ext.calendar.DayHeaderView DayHeaderView} and a {@link Ext.calendar.DayBodyView DayBodyView}. As such
3665  * DayView accepts any config values that are valid for DayHeaderView and DayBodyView and passes those through
3666  * to the contained views. It also supports the interface required of any calendar view and in turn calls methods
3667  * on the contained views as necessary.</p>
3668  * @constructor
3669  * @param {Object} config The config object
3670  */
3671 Ext.calendar.DayView = Ext.extend(Ext.Container, {
3672     /**
3673      * @cfg {Boolean} showTime
3674      * True to display the current time in today's box in the calendar, false to not display it (defautls to true)
3675      */
3676     showTime: true,
3677     /**
3678      * @cfg {Boolean} showTodayText
3679      * True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)
3680      */
3681     showTodayText: true,
3682     /**
3683      * @cfg {String} todayText
3684      * The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')
3685      */
3686     todayText: 'Today',
3687     /**
3688      * @cfg {String} ddCreateEventText
3689      * The text to display inside the drag proxy while dragging over the calendar to create a new event (defaults to 
3690      * 'Create event for {0}' where {0} is a date range supplied by the view)
3691      */
3692     ddCreateEventText: 'Create event for {0}',
3693     /**
3694      * @cfg {String} ddMoveEventText
3695      * The text to display inside the drag proxy while dragging an event to reposition it (defaults to 
3696      * 'Move event to {0}' where {0} is the updated event start date/time supplied by the view)
3697      */
3698     ddMoveEventText: 'Move event to {0}',
3699     /**
3700      * @cfg {Number} dayCount
3701      * The number of days to display in the view (defaults to 1)
3702      */
3703     dayCount: 1,
3704     
3705     // private
3706     initComponent : function(){
3707         // rendering more than 7 days per view is not supported
3708         this.dayCount = this.dayCount > 7 ? 7 : this.dayCount;
3709         
3710         var cfg = Ext.apply({}, this.initialConfig);
3711         cfg.showTime = this.showTime;
3712         cfg.showTodatText = this.showTodayText;
3713         cfg.todayText = this.todayText;
3714         cfg.dayCount = this.dayCount;
3715         cfg.wekkCount = 1; 
3716         
3717         var header = Ext.applyIf({
3718             xtype: 'dayheaderview',
3719             id: this.id+'-hd'
3720         }, cfg);
3721         
3722         var body = Ext.applyIf({
3723             xtype: 'daybodyview',
3724             id: this.id+'-bd'
3725         }, cfg);
3726         
3727         this.items = [header, body];
3728         this.addClass('ext-cal-dayview ext-cal-ct');
3729         
3730         Ext.calendar.DayView.superclass.initComponent.call(this);
3731     },
3732     
3733     // private
3734     afterRender : function(){
3735         Ext.calendar.DayView.superclass.afterRender.call(this);
3736         
3737         this.header = Ext.getCmp(this.id+'-hd');
3738         this.body = Ext.getCmp(this.id+'-bd');
3739         this.body.on('eventsrendered', this.forceSize, this);
3740     },
3741     
3742     // private
3743     refresh : function(){
3744         this.header.refresh();
3745         this.body.refresh();
3746     },
3747     
3748     // private
3749     forceSize: function(){
3750         // The defer call is mainly for good ol' IE, but it doesn't hurt in
3751         // general to make sure that the window resize is good and done first
3752         // so that we can properly calculate sizes.
3753         (function(){
3754             var ct = this.el.up('.x-panel-body'),
3755                 hd = this.el.child('.ext-cal-day-header'),
3756                 h = ct.getHeight() - hd.getHeight();
3757             
3758             this.el.child('.ext-cal-body-ct').setHeight(h);
3759         }).defer(10, this);
3760     },
3761     
3762     // private
3763     onResize : function(){
3764         this.forceSize();
3765     },
3766     
3767     // private
3768     getViewBounds : function(){
3769         return this.header.getViewBounds();
3770     },
3771     
3772     /**
3773      * Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not 
3774      * be the first date displayed in the rendered calendar -- to get the start and end dates displayed
3775      * to the user use {@link #getViewBounds}.
3776      * @return {Date} The start date
3777      */
3778     getStartDate : function(){
3779         return this.header.getStartDate();
3780     },
3781
3782     /**
3783      * Sets the start date used to calculate the view boundaries to display. The displayed view will be the 
3784      * earliest and latest dates that match the view requirements and contain the date passed to this function.
3785      * @param {Date} dt The date used to calculate the new view boundaries
3786      */
3787     setStartDate: function(dt){
3788         this.header.setStartDate(dt, true);
3789         this.body.setStartDate(dt, true);
3790     },
3791
3792     // private
3793     renderItems: function(){
3794         this.header.renderItems();
3795         this.body.renderItems();
3796     },
3797     
3798     /**
3799      * Returns true if the view is currently displaying today's date, else false.
3800      * @return {Boolean} True or false
3801      */
3802     isToday : function(){
3803         return this.header.isToday();
3804     },
3805     
3806     /**
3807      * Updates the view to contain the passed date
3808      * @param {Date} dt The date to display
3809      */
3810     moveTo : function(dt, noRefresh){
3811         this.header.moveTo(dt, noRefresh);
3812         this.body.moveTo(dt, noRefresh);
3813     },
3814     
3815     /**
3816      * Updates the view to the next consecutive date(s)
3817      */
3818     moveNext : function(noRefresh){
3819         this.header.moveNext(noRefresh);
3820         this.body.moveNext(noRefresh);
3821     },
3822     
3823     /**
3824      * Updates the view to the previous consecutive date(s)
3825      */
3826     movePrev : function(noRefresh){
3827         this.header.movePrev(noRefresh);
3828         this.body.movePrev(noRefresh);
3829     },
3830
3831     /**
3832      * Shifts the view by the passed number of days relative to the currently set date
3833      * @param {Number} value The number of days (positive or negative) by which to shift the view
3834      */
3835     moveDays : function(value, noRefresh){
3836         this.header.moveDays(value, noRefresh);
3837         this.body.moveDays(value, noRefresh);
3838     },
3839     
3840     /**
3841      * Updates the view to show today
3842      */
3843     moveToday : function(noRefresh){
3844         this.header.moveToday(noRefresh);
3845         this.body.moveToday(noRefresh);
3846     }
3847 });
3848
3849 Ext.reg('dayview', Ext.calendar.DayView);
3850 /**
3851  * @class Ext.calendar.WeekView
3852  * @extends Ext.calendar.DayView
3853  * <p>Displays a calendar view by week. This class does not usually need ot be used directly as you can
3854  * use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including
3855  * the week view.</p>
3856  * @constructor
3857  * @param {Object} config The config object
3858  */
3859 Ext.calendar.WeekView = Ext.extend(Ext.calendar.DayView, {
3860     /**
3861      * @cfg {Number} dayCount
3862      * The number of days to display in the view (defaults to 7)
3863      */
3864     dayCount: 7
3865 });
3866
3867 Ext.reg('weekview', Ext.calendar.WeekView);/**
3868  * @class Ext.calendar.DateRangeField
3869  * @extends Ext.form.Field
3870  * <p>A combination field that includes start and end dates and times, as well as an optional all-day checkbox.</p>
3871  * @constructor
3872  * @param {Object} config The config object
3873  */
3874 Ext.calendar.DateRangeField = Ext.extend(Ext.form.Field, {
3875     /**
3876      * @cfg {String} toText
3877      * The text to display in between the date/time fields (defaults to 'to')
3878      */
3879     toText: 'to',
3880     /**
3881      * @cfg {String} toText
3882      * The text to display as the label for the all day checkbox (defaults to 'All day')
3883      */
3884     allDayText: 'All day',
3885
3886     // private
3887     onRender: function(ct, position) {
3888         if (!this.el) {
3889             this.startDate = new Ext.form.DateField({
3890                 id: this.id + '-start-date',
3891                 format: 'n/j/Y',
3892                 width: 100,
3893                 listeners: {
3894                     'change': {
3895                         fn: function() {
3896                             this.checkDates('date', 'start');
3897                         },
3898                         scope: this
3899                     }
3900                 }
3901             });
3902             this.startTime = new Ext.form.TimeField({
3903                 id: this.id + '-start-time',
3904                 hidden: this.showTimes === false,
3905                 labelWidth: 0,
3906                 hideLabel: true,
3907                 width: 90,
3908                 listeners: {
3909                     'select': {
3910                         fn: function() {
3911                             this.checkDates('time', 'start');
3912                         },
3913                         scope: this
3914                     }
3915                 }
3916             });
3917             this.endTime = new Ext.form.TimeField({
3918                 id: this.id + '-end-time',
3919                 hidden: this.showTimes === false,
3920                 labelWidth: 0,
3921                 hideLabel: true,
3922                 width: 90,
3923                 listeners: {
3924                     'select': {
3925                         fn: function() {
3926                             this.checkDates('time', 'end');
3927                         },
3928                         scope: this
3929                     }
3930                 }
3931             });
3932             this.endDate = new Ext.form.DateField({
3933                 id: this.id + '-end-date',
3934                 format: 'n/j/Y',
3935                 hideLabel: true,
3936                 width: 100,
3937                 listeners: {
3938                     'change': {
3939                         fn: function() {
3940                             this.checkDates('date', 'end');
3941                         },
3942                         scope: this
3943                     }
3944                 }
3945             });
3946             this.allDay = new Ext.form.Checkbox({
3947                 id: this.id + '-allday',
3948                 hidden: this.showTimes === false || this.showAllDay === false,
3949                 boxLabel: this.allDayText,
3950                 handler: function(chk, checked) {
3951                     this.startTime.setVisible(!checked);
3952                     this.endTime.setVisible(!checked);
3953                 },
3954                 scope: this
3955             });
3956             this.toLabel = new Ext.form.Label({
3957                 xtype: 'label',
3958                 id: this.id + '-to-label',
3959                 text: this.toText
3960             });
3961
3962             this.fieldCt = new Ext.Container({
3963                 autoEl: {
3964                     id: this.id
3965                 },
3966                 //make sure the container el has the field's id
3967                 cls: 'ext-dt-range',
3968                 renderTo: ct,
3969                 layout: 'table',
3970                 layoutConfig: {
3971                     columns: 6
3972                 },
3973                 defaults: {
3974                     hideParent: true
3975                 },
3976                 items: [
3977                 this.startDate,
3978                 this.startTime,
3979                 this.toLabel,
3980                 this.endTime,
3981                 this.endDate,
3982                 this.allDay
3983                 ]
3984             });
3985
3986             this.fieldCt.ownerCt = this;
3987             this.el = this.fieldCt.getEl();
3988             this.items = new Ext.util.MixedCollection();
3989             this.items.addAll([this.startDate, this.endDate, this.toLabel, this.startTime, this.endTime, this.allDay]);
3990         }
3991         Ext.calendar.DateRangeField.superclass.onRender.call(this, ct, position);
3992     },
3993
3994     // private
3995     checkDates: function(type, startend) {
3996         var startField = Ext.getCmp(this.id + '-start-' + type),
3997         endField = Ext.getCmp(this.id + '-end-' + type),
3998         startValue = this.getDT('start'),
3999         endValue = this.getDT('end');
4000
4001         if (startValue > endValue) {
4002             if (startend == 'start') {
4003                 endField.setValue(startValue);
4004             } else {
4005                 startField.setValue(endValue);
4006                 this.checkDates(type, 'start');
4007             }
4008         }
4009         if (type == 'date') {
4010             this.checkDates('time', startend);
4011         }
4012     },
4013
4014     /**
4015      * Returns an array containing the following values in order:<div class="mdetail-params"><ul>
4016      * <li><b><code>DateTime</code></b> : <div class="sub-desc">The start date/time</div></li>
4017      * <li><b><code>DateTime</code></b> : <div class="sub-desc">The end date/time</div></li>
4018      * <li><b><code>Boolean</code></b> : <div class="sub-desc">True if the dates are all-day, false 
4019      * if the time values should be used</div></li><ul></div>
4020      * @return {Array} The array of return values
4021      */
4022     getValue: function() {
4023         return [
4024         this.getDT('start'),
4025         this.getDT('end'),
4026         this.allDay.getValue()
4027         ];
4028     },
4029
4030     // private getValue helper
4031     getDT: function(startend) {
4032         var time = this[startend + 'Time'].getValue(),
4033         dt = this[startend + 'Date'].getValue();
4034
4035         if (Ext.isDate(dt)) {
4036             dt = dt.format(this[startend + 'Date'].format);
4037         }
4038         else {
4039             return null;
4040         };
4041         if (time != '' && this[startend + 'Time'].isVisible()) {
4042             return Date.parseDate(dt + ' ' + time, this[startend + 'Date'].format + ' ' + this[startend + 'Time'].format);
4043         }
4044         return Date.parseDate(dt, this[startend + 'Date'].format);
4045
4046     },
4047
4048     /**
4049      * Sets the values to use in the date range.
4050      * @param {Array/Date/Object} v The value(s) to set into the field. Valid types are as follows:<div class="mdetail-params"><ul>
4051      * <li><b><code>Array</code></b> : <div class="sub-desc">An array containing, in order, a start date, end date and all-day flag.
4052      * This array should exactly match the return type as specified by {@link #getValue}.</div></li>
4053      * <li><b><code>DateTime</code></b> : <div class="sub-desc">A single Date object, which will be used for both the start and
4054      * end dates in the range.  The all-day flag will be defaulted to false.</div></li>
4055      * <li><b><code>Object</code></b> : <div class="sub-desc">An object containing properties for StartDate, EndDate and IsAllDay
4056      * as defined in {@link Ext.calendar.EventMappings}.</div></li><ul></div>
4057      */
4058     setValue: function(v) {
4059         if (Ext.isArray(v)) {
4060             this.setDT(v[0], 'start');
4061             this.setDT(v[1], 'end');
4062             this.allDay.setValue( !! v[2]);
4063         }
4064         else if (Ext.isDate(v)) {
4065             this.setDT(v, 'start');
4066             this.setDT(v, 'end');
4067             this.allDay.setValue(false);
4068         }
4069         else if (v[Ext.calendar.EventMappings.StartDate.name]) {
4070             //object
4071             this.setDT(v[Ext.calendar.EventMappings.StartDate.name], 'start');
4072             if (!this.setDT(v[Ext.calendar.EventMappings.EndDate.name], 'end')) {
4073                 this.setDT(v[Ext.calendar.EventMappings.StartDate.name], 'end');
4074             }
4075             this.allDay.setValue( !! v[Ext.calendar.EventMappings.IsAllDay.name]);
4076         }
4077     },
4078
4079     // private setValue helper
4080     setDT: function(dt, startend) {
4081         if (dt && Ext.isDate(dt)) {
4082             this[startend + 'Date'].setValue(dt);
4083             this[startend + 'Time'].setValue(dt.format(this[startend + 'Time'].format));
4084             return true;
4085         }
4086     },
4087
4088     // inherited docs
4089     isDirty: function() {
4090         var dirty = false;
4091         if (this.rendered && !this.disabled) {
4092             this.items.each(function(item) {
4093                 if (item.isDirty()) {
4094                     dirty = true;
4095                     return false;
4096                 }
4097             });
4098         }
4099         return dirty;
4100     },
4101
4102     // private
4103     onDisable: function() {
4104         this.delegateFn('disable');
4105     },
4106
4107     // private
4108     onEnable: function() {
4109         this.delegateFn('enable');
4110     },
4111
4112     // inherited docs
4113     reset: function() {
4114         this.delegateFn('reset');
4115     },
4116
4117     // private
4118     delegateFn: function(fn) {
4119         this.items.each(function(item) {
4120             if (item[fn]) {
4121                 item[fn]();
4122             }
4123         });
4124     },
4125
4126     // private
4127     beforeDestroy: function() {
4128         Ext.destroy(this.fieldCt);
4129         Ext.calendar.DateRangeField.superclass.beforeDestroy.call(this);
4130     },
4131
4132     /**
4133      * @method getRawValue
4134      * @hide
4135      */
4136     getRawValue: Ext.emptyFn,
4137     /**
4138      * @method setRawValue
4139      * @hide
4140      */
4141     setRawValue: Ext.emptyFn
4142 });
4143
4144 Ext.reg('daterangefield', Ext.calendar.DateRangeField);
4145 /**
4146  * @class Ext.calendar.ReminderField
4147  * @extends Ext.form.ComboBox
4148  * <p>A custom combo used for choosing a reminder setting for an event.</p>
4149  * <p>This is pretty much a standard combo that is simply pre-configured for the options needed by the
4150  * calendar components. The default configs are as follows:<pre><code>
4151     width: 200,
4152     fieldLabel: 'Reminder',
4153     mode: 'local',
4154     triggerAction: 'all',
4155     forceSelection: true,
4156     displayField: 'desc',
4157     valueField: 'value'
4158 </code></pre>
4159  * @constructor
4160  * @param {Object} config The config object
4161  */
4162 Ext.calendar.ReminderField = Ext.extend(Ext.form.ComboBox, {
4163     width: 200,
4164     fieldLabel: 'Reminder',
4165     mode: 'local',
4166     triggerAction: 'all',
4167     forceSelection: true,
4168     displayField: 'desc',
4169     valueField: 'value',
4170
4171     // private
4172     initComponent: function() {
4173         Ext.calendar.ReminderField.superclass.initComponent.call(this);
4174
4175         this.store = this.store || new Ext.data.ArrayStore({
4176             fields: ['value', 'desc'],
4177             idIndex: 0,
4178             data: [
4179             ['', 'None'],
4180             ['0', 'At start time'],
4181             ['5', '5 minutes before start'],
4182             ['15', '15 minutes before start'],
4183             ['30', '30 minutes before start'],
4184             ['60', '1 hour before start'],
4185             ['90', '1.5 hours before start'],
4186             ['120', '2 hours before start'],
4187             ['180', '3 hours before start'],
4188             ['360', '6 hours before start'],
4189             ['720', '12 hours before start'],
4190             ['1440', '1 day before start'],
4191             ['2880', '2 days before start'],
4192             ['4320', '3 days before start'],
4193             ['5760', '4 days before start'],
4194             ['7200', '5 days before start'],
4195             ['10080', '1 week before start'],
4196             ['20160', '2 weeks before start']
4197             ]
4198         });
4199     },
4200
4201     // inherited docs
4202     initValue: function() {
4203         if (this.value !== undefined) {
4204             this.setValue(this.value);
4205         }
4206         else {
4207             this.setValue('');
4208         }
4209         this.originalValue = this.getValue();
4210     }
4211 });
4212
4213 Ext.reg('reminderfield', Ext.calendar.ReminderField);
4214 /**
4215  * @class Ext.calendar.EventEditForm
4216  * @extends Ext.form.FormPanel
4217  * <p>A custom form used for detailed editing of events.</p>
4218  * <p>This is pretty much a standard form that is simply pre-configured for the options needed by the
4219  * calendar components. It is also configured to automatically bind records of type {@link Ext.calendar.EventRecord}
4220  * to and from the form.</p>
4221  * <p>This form also provides custom events specific to the calendar so that other calendar components can be easily
4222  * notified when an event has been edited via this component.</p>
4223  * <p>The default configs are as follows:</p><pre><code>
4224     labelWidth: 65,
4225     title: 'Event Form',
4226     titleTextAdd: 'Add Event',
4227     titleTextEdit: 'Edit Event',
4228     bodyStyle: 'background:transparent;padding:20px 20px 10px;',
4229     border: false,
4230     buttonAlign: 'center',
4231     autoHeight: true,
4232     cls: 'ext-evt-edit-form',
4233 </code></pre>
4234  * @constructor
4235  * @param {Object} config The config object
4236  */
4237 Ext.calendar.EventEditForm = Ext.extend(Ext.form.FormPanel, {
4238     labelWidth: 65,
4239     title: 'Event Form',
4240     titleTextAdd: 'Add Event',
4241     titleTextEdit: 'Edit Event',
4242     bodyStyle: 'background:transparent;padding:20px 20px 10px;',
4243     border: false,
4244     buttonAlign: 'center',
4245     autoHeight: true,
4246     // to allow for the notes field to autogrow
4247     cls: 'ext-evt-edit-form',
4248
4249     // private properties:
4250     newId: 10000,
4251     layout: 'column',
4252
4253     // private
4254     initComponent: function() {
4255
4256         this.addEvents({
4257             /**
4258              * @event eventadd
4259              * Fires after a new event is added
4260              * @param {Ext.calendar.EventEditForm} this
4261              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
4262              */
4263             eventadd: true,
4264             /**
4265              * @event eventupdate
4266              * Fires after an existing event is updated
4267              * @param {Ext.calendar.EventEditForm} this
4268              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
4269              */
4270             eventupdate: true,
4271             /**
4272              * @event eventdelete
4273              * Fires after an event is deleted
4274              * @param {Ext.calendar.EventEditForm} this
4275              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted
4276              */
4277             eventdelete: true,
4278             /**
4279              * @event eventcancel
4280              * Fires after an event add/edit operation is canceled by the user and no store update took place
4281              * @param {Ext.calendar.EventEditForm} this
4282              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
4283              */
4284             eventcancel: true
4285         });
4286
4287         this.titleField = new Ext.form.TextField({
4288             fieldLabel: 'Title',
4289             name: Ext.calendar.EventMappings.Title.name,
4290             anchor: '90%'
4291         });
4292         this.dateRangeField = new Ext.calendar.DateRangeField({
4293             fieldLabel: 'When',
4294             anchor: '90%'
4295         });
4296         this.reminderField = new Ext.calendar.ReminderField({
4297             name: 'Reminder'
4298         });
4299         this.notesField = new Ext.form.TextArea({
4300             fieldLabel: 'Notes',
4301             name: Ext.calendar.EventMappings.Notes.name,
4302             grow: true,
4303             growMax: 150,
4304             anchor: '100%'
4305         });
4306         this.locationField = new Ext.form.TextField({
4307             fieldLabel: 'Location',
4308             name: Ext.calendar.EventMappings.Location.name,
4309             anchor: '100%'
4310         });
4311         this.urlField = new Ext.form.TextField({
4312             fieldLabel: 'Web Link',
4313             name: Ext.calendar.EventMappings.Url.name,
4314             anchor: '100%'
4315         });
4316
4317         var leftFields = [this.titleField, this.dateRangeField, this.reminderField],
4318         rightFields = [this.notesField, this.locationField, this.urlField];
4319
4320         if (this.calendarStore) {
4321             this.calendarField = new Ext.calendar.CalendarPicker({
4322                 store: this.calendarStore,
4323                 name: Ext.calendar.EventMappings.CalendarId.name
4324             });
4325             leftFields.splice(2, 0, this.calendarField);
4326         };
4327
4328         this.items = [{
4329             id: 'left-col',
4330             columnWidth: 0.65,
4331             layout: 'form',
4332             border: false,
4333             items: leftFields
4334         },
4335         {
4336             id: 'right-col',
4337             columnWidth: 0.35,
4338             layout: 'form',
4339             border: false,
4340             items: rightFields
4341         }];
4342
4343         this.fbar = [{
4344             text: 'Save',
4345             scope: this,
4346             handler: this.onSave
4347         },
4348         {
4349             cls: 'ext-del-btn',
4350             text: 'Delete',
4351             scope: this,
4352             handler: this.onDelete
4353         },
4354         {
4355             text: 'Cancel',
4356             scope: this,
4357             handler: this.onCancel
4358         }];
4359
4360         Ext.calendar.EventEditForm.superclass.initComponent.call(this);
4361     },
4362
4363     // inherited docs
4364     loadRecord: function(rec) {
4365         this.form.loadRecord.apply(this.form, arguments);
4366         this.activeRecord = rec;
4367         this.dateRangeField.setValue(rec.data);
4368         if (this.calendarStore) {
4369             this.form.setValues({
4370                 'calendar': rec.data[Ext.calendar.EventMappings.CalendarId.name]
4371             });
4372         }
4373         this.isAdd = !!rec.data[Ext.calendar.EventMappings.IsNew.name];
4374         if (this.isAdd) {
4375             rec.markDirty();
4376             this.setTitle(this.titleTextAdd);
4377             Ext.select('.ext-del-btn').setDisplayed(false);
4378         }
4379         else {
4380             this.setTitle(this.titleTextEdit);
4381             Ext.select('.ext-del-btn').setDisplayed(true);
4382         }
4383         this.titleField.focus();
4384     },
4385
4386     // inherited docs
4387     updateRecord: function() {
4388         var dates = this.dateRangeField.getValue();
4389
4390         this.form.updateRecord(this.activeRecord);
4391         this.activeRecord.set(Ext.calendar.EventMappings.StartDate.name, dates[0]);
4392         this.activeRecord.set(Ext.calendar.EventMappings.EndDate.name, dates[1]);
4393         this.activeRecord.set(Ext.calendar.EventMappings.IsAllDay.name, dates[2]);
4394     },
4395
4396     // private
4397     onCancel: function() {
4398         this.cleanup(true);
4399         this.fireEvent('eventcancel', this, this.activeRecord);
4400     },
4401
4402     // private
4403     cleanup: function(hide) {
4404         if (this.activeRecord && this.activeRecord.dirty) {
4405             this.activeRecord.reject();
4406         }
4407         delete this.activeRecord;
4408
4409         if (this.form.isDirty()) {
4410             this.form.reset();
4411         }
4412     },
4413
4414     // private
4415     onSave: function() {
4416         if (!this.form.isValid()) {
4417             return;
4418         }
4419         this.updateRecord();
4420
4421         if (!this.activeRecord.dirty) {
4422             this.onCancel();
4423             return;
4424         }
4425
4426         this.fireEvent(this.isAdd ? 'eventadd': 'eventupdate', this, this.activeRecord);
4427     },
4428
4429     // private
4430     onDelete: function() {
4431         this.fireEvent('eventdelete', this, this.activeRecord);
4432     }
4433 });
4434
4435 Ext.reg('eventeditform', Ext.calendar.EventEditForm);
4436 /**
4437  * @class Ext.calendar.EventEditWindow
4438  * @extends Ext.Window
4439  * <p>A custom window containing a basic edit form used for quick editing of events.</p>
4440  * <p>This window also provides custom events specific to the calendar so that other calendar components can be easily
4441  * notified when an event has been edited via this component.</p>
4442  * @constructor
4443  * @param {Object} config The config object
4444  */
4445 Ext.calendar.EventEditWindow = function(config) {
4446     var formPanelCfg = {
4447         xtype: 'form',
4448         labelWidth: 65,
4449         frame: false,
4450         bodyStyle: 'background:transparent;padding:5px 10px 10px;',
4451         bodyBorder: false,
4452         border: false,
4453         items: [{
4454             id: 'title',
4455             name: Ext.calendar.EventMappings.Title.name,
4456             fieldLabel: 'Title',
4457             xtype: 'textfield',
4458             anchor: '100%'
4459         },
4460         {
4461             xtype: 'daterangefield',
4462             id: 'date-range',
4463             anchor: '100%',
4464             fieldLabel: 'When'
4465         }]
4466     };
4467
4468     if (config.calendarStore) {
4469         this.calendarStore = config.calendarStore;
4470         delete config.calendarStore;
4471
4472         formPanelCfg.items.push({
4473             xtype: 'calendarpicker',
4474             id: 'calendar',
4475             name: 'calendar',
4476             anchor: '100%',
4477             store: this.calendarStore
4478         });
4479     }
4480
4481     Ext.calendar.EventEditWindow.superclass.constructor.call(this, Ext.apply({
4482         titleTextAdd: 'Add Event',
4483         titleTextEdit: 'Edit Event',
4484         width: 600,
4485         autocreate: true,
4486         border: true,
4487         closeAction: 'hide',
4488         modal: false,
4489         resizable: false,
4490         buttonAlign: 'left',
4491         savingMessage: 'Saving changes...',
4492         deletingMessage: 'Deleting event...',
4493
4494         fbar: [{
4495             xtype: 'tbtext',
4496             text: '<a href="#" id="tblink">Edit Details...</a>'
4497         },
4498         '->', {
4499             text: 'Save',
4500             disabled: false,
4501             handler: this.onSave,
4502             scope: this
4503         },
4504         {
4505             id: 'delete-btn',
4506             text: 'Delete',
4507             disabled: false,
4508             handler: this.onDelete,
4509             scope: this,
4510             hideMode: 'offsets'
4511         },
4512         {
4513             text: 'Cancel',
4514             disabled: false,
4515             handler: this.onCancel,
4516             scope: this
4517         }],
4518         items: formPanelCfg
4519     },
4520     config));
4521 };
4522
4523 Ext.extend(Ext.calendar.EventEditWindow, Ext.Window, {
4524     // private
4525     newId: 10000,
4526
4527     // private
4528     initComponent: function() {
4529         Ext.calendar.EventEditWindow.superclass.initComponent.call(this);
4530
4531         this.formPanel = this.items.items[0];
4532
4533         this.addEvents({
4534             /**
4535              * @event eventadd
4536              * Fires after a new event is added
4537              * @param {Ext.calendar.EventEditWindow} this
4538              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
4539              */
4540             eventadd: true,
4541             /**
4542              * @event eventupdate
4543              * Fires after an existing event is updated
4544              * @param {Ext.calendar.EventEditWindow} this
4545              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
4546              */
4547             eventupdate: true,
4548             /**
4549              * @event eventdelete
4550              * Fires after an event is deleted
4551              * @param {Ext.calendar.EventEditWindow} this
4552              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted
4553              */
4554             eventdelete: true,
4555             /**
4556              * @event eventcancel
4557              * Fires after an event add/edit operation is canceled by the user and no store update took place
4558              * @param {Ext.calendar.EventEditWindow} this
4559              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
4560              */
4561             eventcancel: true,
4562             /**
4563              * @event editdetails
4564              * Fires when the user selects the option in this window to continue editing in the detailed edit form
4565              * (by default, an instance of {@link Ext.calendar.EventEditForm}. Handling code should hide this window
4566              * and transfer the current event record to the appropriate instance of the detailed form by showing it
4567              * and calling {@link Ext.calendar.EventEditForm#loadRecord loadRecord}.
4568              * @param {Ext.calendar.EventEditWindow} this
4569              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} that is currently being edited
4570              */
4571             editdetails: true
4572         });
4573     },
4574
4575     // private
4576     afterRender: function() {
4577         Ext.calendar.EventEditWindow.superclass.afterRender.call(this);
4578
4579         this.el.addClass('ext-cal-event-win');
4580
4581         Ext.get('tblink').on('click',
4582         function(e) {
4583             e.stopEvent();
4584             this.updateRecord();
4585             this.fireEvent('editdetails', this, this.activeRecord);
4586         },
4587         this);
4588     },
4589
4590     /**
4591      * Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden.
4592          * @param {Ext.data.Record/Object} o Either a {@link Ext.data.Record} if showing the form
4593          * for an existing event in edit mode, or a plain object containing a StartDate property (and 
4594          * optionally an EndDate property) for showing the form in add mode. 
4595      * @param {String/Element} animateTarget (optional) The target element or id from which the window should
4596      * animate while opening (defaults to null with no animation)
4597      * @return {Ext.Window} this
4598      */
4599     show: function(o, animateTarget) {
4600         // Work around the CSS day cell height hack needed for initial render in IE8/strict:
4601         var anim = (Ext.isIE8 && Ext.isStrict) ? null: animateTarget;
4602
4603         Ext.calendar.EventEditWindow.superclass.show.call(this, anim,
4604         function() {
4605             Ext.getCmp('title').focus(false, 100);
4606         });
4607         Ext.getCmp('delete-btn')[o.data && o.data[Ext.calendar.EventMappings.EventId.name] ? 'show': 'hide']();
4608
4609         var rec,
4610         f = this.formPanel.form;
4611
4612         if (o.data) {
4613             rec = o;
4614             this.isAdd = !!rec.data[Ext.calendar.EventMappings.IsNew.name];
4615             if (this.isAdd) {
4616                 // Enable adding the default record that was passed in
4617                 // if it's new even if the user makes no changes
4618                 rec.markDirty();
4619                 this.setTitle(this.titleTextAdd);
4620             }
4621             else {
4622                 this.setTitle(this.titleTextEdit);
4623             }
4624
4625             f.loadRecord(rec);
4626         }
4627         else {
4628             this.isAdd = true;
4629             this.setTitle(this.titleTextAdd);
4630
4631             var M = Ext.calendar.EventMappings,
4632             eventId = M.EventId.name,
4633             start = o[M.StartDate.name],
4634             end = o[M.EndDate.name] || start.add('h', 1);
4635
4636             rec = new Ext.calendar.EventRecord();
4637             rec.data[M.EventId.name] = this.newId++;
4638             rec.data[M.StartDate.name] = start;
4639             rec.data[M.EndDate.name] = end;
4640             rec.data[M.IsAllDay.name] = !!o[M.IsAllDay.name] || start.getDate() != end.clone().add(Date.MILLI, 1).getDate();
4641             rec.data[M.IsNew.name] = true;
4642
4643             f.reset();
4644             f.loadRecord(rec);
4645         }
4646
4647         if (this.calendarStore) {
4648             Ext.getCmp('calendar').setValue(rec.data[Ext.calendar.EventMappings.CalendarId.name]);
4649         }
4650         Ext.getCmp('date-range').setValue(rec.data);
4651         this.activeRecord = rec;
4652
4653         return this;
4654     },
4655
4656     // private
4657     roundTime: function(dt, incr) {
4658         incr = incr || 15;
4659         var m = parseInt(dt.getMinutes(), 10);
4660         return dt.add('mi', incr - (m % incr));
4661     },
4662
4663     // private
4664     onCancel: function() {
4665         this.cleanup(true);
4666         this.fireEvent('eventcancel', this);
4667     },
4668
4669     // private
4670     cleanup: function(hide) {
4671         if (this.activeRecord && this.activeRecord.dirty) {
4672             this.activeRecord.reject();
4673         }
4674         delete this.activeRecord;
4675
4676         if (hide === true) {
4677             // Work around the CSS day cell height hack needed for initial render in IE8/strict:
4678             //var anim = afterDelete || (Ext.isIE8 && Ext.isStrict) ? null : this.animateTarget;
4679             this.hide();
4680         }
4681     },
4682
4683     // private
4684     updateRecord: function() {
4685         var f = this.formPanel.form,
4686         dates = Ext.getCmp('date-range').getValue(),
4687         M = Ext.calendar.EventMappings;
4688
4689         f.updateRecord(this.activeRecord);
4690         this.activeRecord.set(M.StartDate.name, dates[0]);
4691         this.activeRecord.set(M.EndDate.name, dates[1]);
4692         this.activeRecord.set(M.IsAllDay.name, dates[2]);
4693         this.activeRecord.set(M.CalendarId.name, this.formPanel.form.findField('calendar').getValue());
4694     },
4695
4696     // private
4697     onSave: function() {
4698         if (!this.formPanel.form.isValid()) {
4699             return;
4700         }
4701         this.updateRecord();
4702
4703         if (!this.activeRecord.dirty) {
4704             this.onCancel();
4705             return;
4706         }
4707
4708         this.fireEvent(this.isAdd ? 'eventadd': 'eventupdate', this, this.activeRecord);
4709     },
4710
4711     // private
4712     onDelete: function() {
4713         this.fireEvent('eventdelete', this, this.activeRecord);
4714     }
4715 });/**
4716  * @class Ext.calendar.CalendarPanel
4717  * @extends Ext.Panel
4718  * <p>This is the default container for Ext calendar views. It supports day, week and month views as well
4719  * as a built-in event edit form. The only requirement for displaying a calendar is passing in a valid
4720  * {@link #calendarStore} config containing records of type {@link Ext.calendar.EventRecord EventRecord}. In order
4721  * to make the calendar interactive (enable editing, drag/drop, etc.) you can handle any of the various
4722  * events fired by the underlying views and exposed through the CalendarPanel.</p>
4723  * {@link #layoutConfig} option if needed.</p>
4724  * @constructor
4725  * @param {Object} config The config object
4726  * @xtype calendarpanel
4727  */
4728 Ext.calendar.CalendarPanel = Ext.extend(Ext.Panel, {
4729     /**
4730      * @cfg {Boolean} showDayView
4731      * True to include the day view (and toolbar button), false to hide them (defaults to true).
4732      */
4733     showDayView: true,
4734     /**
4735      * @cfg {Boolean} showWeekView
4736      * True to include the week view (and toolbar button), false to hide them (defaults to true).
4737      */
4738     showWeekView: true,
4739     /**
4740      * @cfg {Boolean} showMonthView
4741      * True to include the month view (and toolbar button), false to hide them (defaults to true).
4742      * If the day and week views are both hidden, the month view will show by default even if
4743      * this config is false.
4744      */
4745     showMonthView: true,
4746     /**
4747      * @cfg {Boolean} showNavBar
4748      * True to display the calendar navigation toolbar, false to hide it (defaults to true). Note that
4749      * if you hide the default navigation toolbar you'll have to provide an alternate means of navigating the calendar.
4750      */
4751     showNavBar: true,
4752     /**
4753      * @cfg {String} todayText
4754      * Alternate text to use for the 'Today' nav bar button.
4755      */
4756     todayText: 'Today',
4757     /**
4758      * @cfg {Boolean} showTodayText
4759      * True to show the value of {@link #todayText} instead of today's date in the calendar's current day box,
4760      * false to display the day number(defaults to true).
4761      */
4762     showTodayText: true,
4763     /**
4764      * @cfg {Boolean} showTime
4765      * True to display the current time next to the date in the calendar's current day box, false to not show it 
4766      * (defaults to true).
4767      */
4768     showTime: true,
4769     /**
4770      * @cfg {String} dayText
4771      * Alternate text to use for the 'Day' nav bar button.
4772      */
4773     dayText: 'Day',
4774     /**
4775      * @cfg {String} weekText
4776      * Alternate text to use for the 'Week' nav bar button.
4777      */
4778     weekText: 'Week',
4779     /**
4780      * @cfg {String} monthText
4781      * Alternate text to use for the 'Month' nav bar button.
4782      */
4783     monthText: 'Month',
4784
4785     // private
4786     layoutConfig: {
4787         layoutOnCardChange: true,
4788         deferredRender: true
4789     },
4790
4791     // private property
4792     startDate: new Date(),
4793
4794     // private
4795     initComponent: function() {
4796         this.tbar = {
4797             cls: 'ext-cal-toolbar',
4798             border: true,
4799             buttonAlign: 'center',
4800             items: [{
4801                 id: this.id + '-tb-prev',
4802                 handler: this.onPrevClick,
4803                 scope: this,
4804                 iconCls: 'x-tbar-page-prev'
4805             }]
4806         };
4807
4808         this.viewCount = 0;
4809
4810         if (this.showDayView) {
4811             this.tbar.items.push({
4812                 id: this.id + '-tb-day',
4813                 text: this.dayText,
4814                 handler: this.onDayClick,
4815                 scope: this,
4816                 toggleGroup: 'tb-views'
4817             });
4818             this.viewCount++;
4819         }
4820         if (this.showWeekView) {
4821             this.tbar.items.push({
4822                 id: this.id + '-tb-week',
4823                 text: this.weekText,
4824                 handler: this.onWeekClick,
4825                 scope: this,
4826                 toggleGroup: 'tb-views'
4827             });
4828             this.viewCount++;
4829         }
4830         if (this.showMonthView || this.viewCount == 0) {
4831             this.tbar.items.push({
4832                 id: this.id + '-tb-month',
4833                 text: this.monthText,
4834                 handler: this.onMonthClick,
4835                 scope: this,
4836                 toggleGroup: 'tb-views'
4837             });
4838             this.viewCount++;
4839             this.showMonthView = true;
4840         }
4841         this.tbar.items.push({
4842             id: this.id + '-tb-next',
4843             handler: this.onNextClick,
4844             scope: this,
4845             iconCls: 'x-tbar-page-next'
4846         });
4847         this.tbar.items.push('->');
4848
4849         var idx = this.viewCount - 1;
4850         this.activeItem = this.activeItem === undefined ? idx: (this.activeItem > idx ? idx: this.activeItem);
4851
4852         if (this.showNavBar === false) {
4853             delete this.tbar;
4854             this.addClass('x-calendar-nonav');
4855         }
4856
4857         Ext.calendar.CalendarPanel.superclass.initComponent.call(this);
4858
4859         this.addEvents({
4860             /**
4861              * @event eventadd
4862              * Fires after a new event is added to the underlying store
4863              * @param {Ext.calendar.CalendarPanel} this
4864              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
4865              */
4866             eventadd: true,
4867             /**
4868              * @event eventupdate
4869              * Fires after an existing event is updated
4870              * @param {Ext.calendar.CalendarPanel} this
4871              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
4872              */
4873             eventupdate: true,
4874             /**
4875              * @event eventdelete
4876              * Fires after an event is removed from the underlying store
4877              * @param {Ext.calendar.CalendarPanel} this
4878              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was removed
4879              */
4880             eventdelete: true,
4881             /**
4882              * @event eventcancel
4883              * Fires after an event add/edit operation is canceled by the user and no store update took place
4884              * @param {Ext.calendar.CalendarPanel} this
4885              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
4886              */
4887             eventcancel: true,
4888             /**
4889              * @event viewchange
4890              * Fires after a different calendar view is activated (but not when the event edit form is activated)
4891              * @param {Ext.calendar.CalendarPanel} this
4892              * @param {Ext.CalendarView} view The view being activated (any valid {@link Ext.calendar.CalendarView CalendarView} subclass)
4893              * @param {Object} info Extra information about the newly activated view. This is a plain object 
4894              * with following properties:<div class="mdetail-params"><ul>
4895              * <li><b><code>activeDate</code></b> : <div class="sub-desc">The currently-selected date</div></li>
4896              * <li><b><code>viewStart</code></b> : <div class="sub-desc">The first date in the new view range</div></li>
4897              * <li><b><code>viewEnd</code></b> : <div class="sub-desc">The last date in the new view range</div></li>
4898              * </ul></div>
4899              */
4900             viewchange: true
4901
4902             //
4903             // NOTE: CalendarPanel also relays the following events from contained views as if they originated from this:
4904             //
4905             /**
4906              * @event eventsrendered
4907              * Fires after events are finished rendering in the view
4908              * @param {Ext.calendar.CalendarPanel} this 
4909              */
4910             /**
4911              * @event eventclick
4912              * Fires after the user clicks on an event element
4913              * @param {Ext.calendar.CalendarPanel} this
4914              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on
4915              * @param {HTMLNode} el The DOM node that was clicked on
4916              */
4917             /**
4918              * @event eventover
4919              * Fires anytime the mouse is over an event element
4920              * @param {Ext.calendar.CalendarPanel} this
4921              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over
4922              * @param {HTMLNode} el The DOM node that is being moused over
4923              */
4924             /**
4925              * @event eventout
4926              * Fires anytime the mouse exits an event element
4927              * @param {Ext.calendar.CalendarPanel} this
4928              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited
4929              * @param {HTMLNode} el The DOM node that was exited
4930              */
4931             /**
4932              * @event datechange
4933              * Fires after the start date of the view changes
4934              * @param {Ext.calendar.CalendarPanel} this
4935              * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}
4936              * @param {Date} viewStart The first displayed date in the view
4937              * @param {Date} viewEnd The last displayed date in the view
4938              */
4939             /**
4940              * @event rangeselect
4941              * Fires after the user drags on the calendar to select a range of dates/times in which to create an event
4942              * @param {Ext.calendar.CalendarPanel} this
4943              * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected
4944              * @param {Function} callback A callback function that MUST be called after the event handling is complete so that
4945              * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the
4946              * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard
4947              * function call (e.g., callback()).
4948              */
4949             /**
4950              * @event eventmove
4951              * Fires after an event element is dragged by the user and dropped in a new position
4952              * @param {Ext.calendar.CalendarPanel} this
4953              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with
4954              * updated start and end dates
4955              */
4956             /**
4957              * @event initdrag
4958              * Fires when a drag operation is initiated in the view
4959              * @param {Ext.calendar.CalendarPanel} this
4960              */
4961             /**
4962              * @event eventresize
4963              * Fires after the user drags the resize handle of an event to resize it
4964              * @param {Ext.calendar.CalendarPanel} this
4965              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
4966              * containing the updated start and end dates
4967              */
4968             /**
4969              * @event dayclick
4970              * Fires after the user clicks within a day/week view container and not on an event element
4971              * @param {Ext.calendar.CalendarPanel} this
4972              * @param {Date} dt The date/time that was clicked on
4973              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false.
4974              * @param {Ext.Element} el The Element that was clicked on
4975              */
4976         });
4977
4978         this.layout = 'card';
4979         // do not allow override
4980         if (this.showDayView) {
4981             var day = Ext.apply({
4982                 xtype: 'dayview',
4983                 title: this.dayText,
4984                 showToday: this.showToday,
4985                 showTodayText: this.showTodayText,
4986                 showTime: this.showTime
4987             },
4988             this.dayViewCfg);
4989
4990             day.id = this.id + '-day';
4991             day.store = day.store || this.eventStore;
4992             this.initEventRelay(day);
4993             this.add(day);
4994         }
4995         if (this.showWeekView) {
4996             var wk = Ext.applyIf({
4997                 xtype: 'weekview',
4998                 title: this.weekText,
4999                 showToday: this.showToday,
5000                 showTodayText: this.showTodayText,
5001                 showTime: this.showTime
5002             },
5003             this.weekViewCfg);
5004
5005             wk.id = this.id + '-week';
5006             wk.store = wk.store || this.eventStore;
5007             this.initEventRelay(wk);
5008             this.add(wk);
5009         }
5010         if (this.showMonthView) {
5011             var month = Ext.applyIf({
5012                 xtype: 'monthview',
5013                 title: this.monthText,
5014                 showToday: this.showToday,
5015                 showTodayText: this.showTodayText,
5016                 showTime: this.showTime,
5017                 listeners: {
5018                     'weekclick': {
5019                         fn: function(vw, dt) {
5020                             this.showWeek(dt);
5021                         },
5022                         scope: this
5023                     }
5024                 }
5025             },
5026             this.monthViewCfg);
5027
5028             month.id = this.id + '-month';
5029             month.store = month.store || this.eventStore;
5030             this.initEventRelay(month);
5031             this.add(month);
5032         }
5033
5034         this.add(Ext.applyIf({
5035             xtype: 'eventeditform',
5036             id: this.id + '-edit',
5037             calendarStore: this.calendarStore,
5038             listeners: {
5039                 'eventadd': {
5040                     scope: this,
5041                     fn: this.onEventAdd
5042                 },
5043                 'eventupdate': {
5044                     scope: this,
5045                     fn: this.onEventUpdate
5046                 },
5047                 'eventdelete': {
5048                     scope: this,
5049                     fn: this.onEventDelete
5050                 },
5051                 'eventcancel': {
5052                     scope: this,
5053                     fn: this.onEventCancel
5054                 }
5055             }
5056         },
5057         this.editViewCfg));
5058     },
5059
5060     // private
5061     initEventRelay: function(cfg) {
5062         cfg.listeners = cfg.listeners || {};
5063         cfg.listeners.afterrender = {
5064             fn: function(c) {
5065                 // relay the view events so that app code only has to handle them in one place
5066                 this.relayEvents(c, ['eventsrendered', 'eventclick', 'eventover', 'eventout', 'dayclick',
5067                 'eventmove', 'datechange', 'rangeselect', 'eventdelete', 'eventresize', 'initdrag']);
5068             },
5069             scope: this,
5070             single: true
5071         };
5072     },
5073
5074     // private
5075     afterRender: function() {
5076         Ext.calendar.CalendarPanel.superclass.afterRender.call(this);
5077         this.fireViewChange();
5078     },
5079
5080     // private
5081     onLayout: function() {
5082         Ext.calendar.CalendarPanel.superclass.onLayout.call(this);
5083         if (!this.navInitComplete) {
5084             this.updateNavState();
5085             this.navInitComplete = true;
5086         }
5087     },
5088
5089     // private
5090     onEventAdd: function(form, rec) {
5091         rec.data[Ext.calendar.EventMappings.IsNew.name] = false;
5092         this.eventStore.add(rec);
5093         this.hideEditForm();
5094         this.fireEvent('eventadd', this, rec);
5095     },
5096
5097     // private
5098     onEventUpdate: function(form, rec) {
5099         rec.commit();
5100         this.hideEditForm();
5101         this.fireEvent('eventupdate', this, rec);
5102     },
5103
5104     // private
5105     onEventDelete: function(form, rec) {
5106         this.eventStore.remove(rec);
5107         this.hideEditForm();
5108         this.fireEvent('eventdelete', this, rec);
5109     },
5110
5111     // private
5112     onEventCancel: function(form, rec) {
5113         this.hideEditForm();
5114         this.fireEvent('eventcancel', this, rec);
5115     },
5116
5117     /**
5118      * Shows the built-in event edit form for the passed in event record.  This method automatically
5119      * hides the calendar views and navigation toolbar.  To return to the calendar, call {@link #hideEditForm}.
5120      * @param {Ext.calendar.EventRecord} record The event record to edit
5121      * @return {Ext.calendar.CalendarPanel} this
5122      */
5123     showEditForm: function(rec) {
5124         this.preEditView = this.layout.activeItem.id;
5125         this.setActiveView(this.id + '-edit');
5126         this.layout.activeItem.loadRecord(rec);
5127         return this;
5128     },
5129
5130     /**
5131      * Hides the built-in event edit form and returns to the previous calendar view. If the edit form is
5132      * not currently visible this method has no effect.
5133      * @return {Ext.calendar.CalendarPanel} this
5134      */
5135     hideEditForm: function() {
5136         if (this.preEditView) {
5137             this.setActiveView(this.preEditView);
5138             delete this.preEditView;
5139         }
5140         return this;
5141     },
5142
5143     // private
5144     setActiveView: function(id) {
5145         var l = this.layout;
5146         l.setActiveItem(id);
5147
5148         if (id == this.id + '-edit') {
5149             this.getTopToolbar().hide();
5150             this.doLayout();
5151         }
5152         else {
5153             l.activeItem.refresh();
5154             this.getTopToolbar().show();
5155             this.updateNavState();
5156         }
5157         this.activeView = l.activeItem;
5158         this.fireViewChange();
5159     },
5160
5161     // private
5162     fireViewChange: function() {
5163         var info = null,
5164             view = this.layout.activeItem;
5165
5166         if (view.getViewBounds) {
5167             vb = view.getViewBounds();
5168             info = {
5169                 activeDate: view.getStartDate(),
5170                 viewStart: vb.start,
5171                 viewEnd: vb.end
5172             };
5173         };
5174         this.fireEvent('viewchange', this, view, info);
5175     },
5176
5177     // private
5178     updateNavState: function() {
5179         if (this.showNavBar !== false) {
5180             var item = this.layout.activeItem,
5181             suffix = item.id.split(this.id + '-')[1];
5182
5183             var btn = Ext.getCmp(this.id + '-tb-' + suffix);
5184             btn.toggle(true);
5185         }
5186     },
5187
5188     /**
5189      * Sets the start date for the currently-active calendar view.
5190      * @param {Date} dt
5191      */
5192     setStartDate: function(dt) {
5193         this.layout.activeItem.setStartDate(dt, true);
5194         this.updateNavState();
5195         this.fireViewChange();
5196     },
5197
5198     // private
5199     showWeek: function(dt) {
5200         this.setActiveView(this.id + '-week');
5201         this.setStartDate(dt);
5202     },
5203
5204     // private
5205     onPrevClick: function() {
5206         this.startDate = this.layout.activeItem.movePrev();
5207         this.updateNavState();
5208         this.fireViewChange();
5209     },
5210
5211     // private
5212     onNextClick: function() {
5213         this.startDate = this.layout.activeItem.moveNext();
5214         this.updateNavState();
5215         this.fireViewChange();
5216     },
5217
5218     // private
5219     onDayClick: function() {
5220         this.setActiveView(this.id + '-day');
5221     },
5222
5223     // private
5224     onWeekClick: function() {
5225         this.setActiveView(this.id + '-week');
5226     },
5227
5228     // private
5229     onMonthClick: function() {
5230         this.setActiveView(this.id + '-month');
5231     },
5232
5233     /**
5234      * Return the calendar view that is currently active, which will be a subclass of
5235      * {@link Ext.calendar.CalendarView CalendarView}.
5236      * @return {Ext.calendar.CalendarView} The active view
5237      */
5238     getActiveView: function() {
5239         return this.layout.activeItem;
5240     }
5241 });
5242
5243 Ext.reg('calendarpanel', Ext.calendar.CalendarPanel);