Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / calendar / src / CalendarPanel.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /**
8  * @class Ext.calendar.CalendarPanel
9  * @extends Ext.Panel
10  * <p>This is the default container for Ext calendar views. It supports day, week and month views as well
11  * as a built-in event edit form. The only requirement for displaying a calendar is passing in a valid
12  * {@link #calendarStore} config containing records of type {@link Ext.calendar.EventRecord EventRecord}. In order
13  * to make the calendar interactive (enable editing, drag/drop, etc.) you can handle any of the various
14  * events fired by the underlying views and exposed through the CalendarPanel.</p>
15  * {@link #layoutConfig} option if needed.</p>
16  * @constructor
17  * @param {Object} config The config object
18  * @xtype calendarpanel
19  */
20 Ext.calendar.CalendarPanel = Ext.extend(Ext.Panel, {
21     /**
22      * @cfg {Boolean} showDayView
23      * True to include the day view (and toolbar button), false to hide them (defaults to true).
24      */
25     showDayView: true,
26     /**
27      * @cfg {Boolean} showWeekView
28      * True to include the week view (and toolbar button), false to hide them (defaults to true).
29      */
30     showWeekView: true,
31     /**
32      * @cfg {Boolean} showMonthView
33      * True to include the month view (and toolbar button), false to hide them (defaults to true).
34      * If the day and week views are both hidden, the month view will show by default even if
35      * this config is false.
36      */
37     showMonthView: true,
38     /**
39      * @cfg {Boolean} showNavBar
40      * True to display the calendar navigation toolbar, false to hide it (defaults to true). Note that
41      * if you hide the default navigation toolbar you'll have to provide an alternate means of navigating the calendar.
42      */
43     showNavBar: true,
44     /**
45      * @cfg {String} todayText
46      * Alternate text to use for the 'Today' nav bar button.
47      */
48     todayText: 'Today',
49     /**
50      * @cfg {Boolean} showTodayText
51      * True to show the value of {@link #todayText} instead of today's date in the calendar's current day box,
52      * false to display the day number(defaults to true).
53      */
54     showTodayText: true,
55     /**
56      * @cfg {Boolean} showTime
57      * True to display the current time next to the date in the calendar's current day box, false to not show it 
58      * (defaults to true).
59      */
60     showTime: true,
61     /**
62      * @cfg {String} dayText
63      * Alternate text to use for the 'Day' nav bar button.
64      */
65     dayText: 'Day',
66     /**
67      * @cfg {String} weekText
68      * Alternate text to use for the 'Week' nav bar button.
69      */
70     weekText: 'Week',
71     /**
72      * @cfg {String} monthText
73      * Alternate text to use for the 'Month' nav bar button.
74      */
75     monthText: 'Month',
76
77     // private
78     layoutConfig: {
79         layoutOnCardChange: true,
80         deferredRender: true
81     },
82
83     // private property
84     startDate: new Date(),
85
86     // private
87     initComponent: function() {
88         this.tbar = {
89             cls: 'ext-cal-toolbar',
90             border: true,
91             buttonAlign: 'center',
92             items: [{
93                 id: this.id + '-tb-prev',
94                 handler: this.onPrevClick,
95                 scope: this,
96                 iconCls: 'x-tbar-page-prev'
97             }]
98         };
99
100         this.viewCount = 0;
101
102         if (this.showDayView) {
103             this.tbar.items.push({
104                 id: this.id + '-tb-day',
105                 text: this.dayText,
106                 handler: this.onDayClick,
107                 scope: this,
108                 toggleGroup: 'tb-views'
109             });
110             this.viewCount++;
111         }
112         if (this.showWeekView) {
113             this.tbar.items.push({
114                 id: this.id + '-tb-week',
115                 text: this.weekText,
116                 handler: this.onWeekClick,
117                 scope: this,
118                 toggleGroup: 'tb-views'
119             });
120             this.viewCount++;
121         }
122         if (this.showMonthView || this.viewCount == 0) {
123             this.tbar.items.push({
124                 id: this.id + '-tb-month',
125                 text: this.monthText,
126                 handler: this.onMonthClick,
127                 scope: this,
128                 toggleGroup: 'tb-views'
129             });
130             this.viewCount++;
131             this.showMonthView = true;
132         }
133         this.tbar.items.push({
134             id: this.id + '-tb-next',
135             handler: this.onNextClick,
136             scope: this,
137             iconCls: 'x-tbar-page-next'
138         });
139         this.tbar.items.push('->');
140
141         var idx = this.viewCount - 1;
142         this.activeItem = this.activeItem === undefined ? idx: (this.activeItem > idx ? idx: this.activeItem);
143
144         if (this.showNavBar === false) {
145             delete this.tbar;
146             this.addClass('x-calendar-nonav');
147         }
148
149         Ext.calendar.CalendarPanel.superclass.initComponent.call(this);
150
151         this.addEvents({
152             /**
153              * @event eventadd
154              * Fires after a new event is added to the underlying store
155              * @param {Ext.calendar.CalendarPanel} this
156              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
157              */
158             eventadd: true,
159             /**
160              * @event eventupdate
161              * Fires after an existing event is updated
162              * @param {Ext.calendar.CalendarPanel} this
163              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
164              */
165             eventupdate: true,
166             /**
167              * @event eventdelete
168              * Fires after an event is removed from the underlying store
169              * @param {Ext.calendar.CalendarPanel} this
170              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was removed
171              */
172             eventdelete: true,
173             /**
174              * @event eventcancel
175              * Fires after an event add/edit operation is canceled by the user and no store update took place
176              * @param {Ext.calendar.CalendarPanel} this
177              * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
178              */
179             eventcancel: true,
180             /**
181              * @event viewchange
182              * Fires after a different calendar view is activated (but not when the event edit form is activated)
183              * @param {Ext.calendar.CalendarPanel} this
184              * @param {Ext.CalendarView} view The view being activated (any valid {@link Ext.calendar.CalendarView CalendarView} subclass)
185              * @param {Object} info Extra information about the newly activated view. This is a plain object 
186              * with following properties:<div class="mdetail-params"><ul>
187              * <li><b><code>activeDate</code></b> : <div class="sub-desc">The currently-selected date</div></li>
188              * <li><b><code>viewStart</code></b> : <div class="sub-desc">The first date in the new view range</div></li>
189              * <li><b><code>viewEnd</code></b> : <div class="sub-desc">The last date in the new view range</div></li>
190              * </ul></div>
191              */
192             viewchange: true
193
194             //
195             // NOTE: CalendarPanel also relays the following events from contained views as if they originated from this:
196             //
197             /**
198              * @event eventsrendered
199              * Fires after events are finished rendering in the view
200              * @param {Ext.calendar.CalendarPanel} this 
201              */
202             /**
203              * @event eventclick
204              * Fires after the user clicks on an event element
205              * @param {Ext.calendar.CalendarPanel} this
206              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on
207              * @param {HTMLNode} el The DOM node that was clicked on
208              */
209             /**
210              * @event eventover
211              * Fires anytime the mouse is over an event element
212              * @param {Ext.calendar.CalendarPanel} this
213              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over
214              * @param {HTMLNode} el The DOM node that is being moused over
215              */
216             /**
217              * @event eventout
218              * Fires anytime the mouse exits an event element
219              * @param {Ext.calendar.CalendarPanel} this
220              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited
221              * @param {HTMLNode} el The DOM node that was exited
222              */
223             /**
224              * @event datechange
225              * Fires after the start date of the view changes
226              * @param {Ext.calendar.CalendarPanel} this
227              * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}
228              * @param {Date} viewStart The first displayed date in the view
229              * @param {Date} viewEnd The last displayed date in the view
230              */
231             /**
232              * @event rangeselect
233              * Fires after the user drags on the calendar to select a range of dates/times in which to create an event
234              * @param {Ext.calendar.CalendarPanel} this
235              * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected
236              * @param {Function} callback A callback function that MUST be called after the event handling is complete so that
237              * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the
238              * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard
239              * function call (e.g., callback()).
240              */
241             /**
242              * @event eventmove
243              * Fires after an event element is dragged by the user and dropped in a new position
244              * @param {Ext.calendar.CalendarPanel} this
245              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with
246              * updated start and end dates
247              */
248             /**
249              * @event initdrag
250              * Fires when a drag operation is initiated in the view
251              * @param {Ext.calendar.CalendarPanel} this
252              */
253             /**
254              * @event eventresize
255              * Fires after the user drags the resize handle of an event to resize it
256              * @param {Ext.calendar.CalendarPanel} this
257              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
258              * containing the updated start and end dates
259              */
260             /**
261              * @event dayclick
262              * Fires after the user clicks within a day/week view container and not on an event element
263              * @param {Ext.calendar.CalendarPanel} this
264              * @param {Date} dt The date/time that was clicked on
265              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false.
266              * @param {Ext.Element} el The Element that was clicked on
267              */
268         });
269
270         this.layout = 'card';
271         // do not allow override
272         if (this.showDayView) {
273             var day = Ext.apply({
274                 xtype: 'dayview',
275                 title: this.dayText,
276                 showToday: this.showToday,
277                 showTodayText: this.showTodayText,
278                 showTime: this.showTime
279             },
280             this.dayViewCfg);
281
282             day.id = this.id + '-day';
283             day.store = day.store || this.eventStore;
284             this.initEventRelay(day);
285             this.add(day);
286         }
287         if (this.showWeekView) {
288             var wk = Ext.applyIf({
289                 xtype: 'weekview',
290                 title: this.weekText,
291                 showToday: this.showToday,
292                 showTodayText: this.showTodayText,
293                 showTime: this.showTime
294             },
295             this.weekViewCfg);
296
297             wk.id = this.id + '-week';
298             wk.store = wk.store || this.eventStore;
299             this.initEventRelay(wk);
300             this.add(wk);
301         }
302         if (this.showMonthView) {
303             var month = Ext.applyIf({
304                 xtype: 'monthview',
305                 title: this.monthText,
306                 showToday: this.showToday,
307                 showTodayText: this.showTodayText,
308                 showTime: this.showTime,
309                 listeners: {
310                     'weekclick': {
311                         fn: function(vw, dt) {
312                             this.showWeek(dt);
313                         },
314                         scope: this
315                     }
316                 }
317             },
318             this.monthViewCfg);
319
320             month.id = this.id + '-month';
321             month.store = month.store || this.eventStore;
322             this.initEventRelay(month);
323             this.add(month);
324         }
325
326         this.add(Ext.applyIf({
327             xtype: 'eventeditform',
328             id: this.id + '-edit',
329             calendarStore: this.calendarStore,
330             listeners: {
331                 'eventadd': {
332                     scope: this,
333                     fn: this.onEventAdd
334                 },
335                 'eventupdate': {
336                     scope: this,
337                     fn: this.onEventUpdate
338                 },
339                 'eventdelete': {
340                     scope: this,
341                     fn: this.onEventDelete
342                 },
343                 'eventcancel': {
344                     scope: this,
345                     fn: this.onEventCancel
346                 }
347             }
348         },
349         this.editViewCfg));
350     },
351
352     // private
353     initEventRelay: function(cfg) {
354         cfg.listeners = cfg.listeners || {};
355         cfg.listeners.afterrender = {
356             fn: function(c) {
357                 // relay the view events so that app code only has to handle them in one place
358                 this.relayEvents(c, ['eventsrendered', 'eventclick', 'eventover', 'eventout', 'dayclick',
359                 'eventmove', 'datechange', 'rangeselect', 'eventdelete', 'eventresize', 'initdrag']);
360             },
361             scope: this,
362             single: true
363         };
364     },
365
366     // private
367     afterRender: function() {
368         Ext.calendar.CalendarPanel.superclass.afterRender.call(this);
369         this.fireViewChange();
370     },
371
372     // private
373     onLayout: function() {
374         Ext.calendar.CalendarPanel.superclass.onLayout.call(this);
375         if (!this.navInitComplete) {
376             this.updateNavState();
377             this.navInitComplete = true;
378         }
379     },
380
381     // private
382     onEventAdd: function(form, rec) {
383         rec.data[Ext.calendar.EventMappings.IsNew.name] = false;
384         this.eventStore.add(rec);
385         this.hideEditForm();
386         this.fireEvent('eventadd', this, rec);
387     },
388
389     // private
390     onEventUpdate: function(form, rec) {
391         rec.commit();
392         this.hideEditForm();
393         this.fireEvent('eventupdate', this, rec);
394     },
395
396     // private
397     onEventDelete: function(form, rec) {
398         this.eventStore.remove(rec);
399         this.hideEditForm();
400         this.fireEvent('eventdelete', this, rec);
401     },
402
403     // private
404     onEventCancel: function(form, rec) {
405         this.hideEditForm();
406         this.fireEvent('eventcancel', this, rec);
407     },
408
409     /**
410      * Shows the built-in event edit form for the passed in event record.  This method automatically
411      * hides the calendar views and navigation toolbar.  To return to the calendar, call {@link #hideEditForm}.
412      * @param {Ext.calendar.EventRecord} record The event record to edit
413      * @return {Ext.calendar.CalendarPanel} this
414      */
415     showEditForm: function(rec) {
416         this.preEditView = this.layout.activeItem.id;
417         this.setActiveView(this.id + '-edit');
418         this.layout.activeItem.loadRecord(rec);
419         return this;
420     },
421
422     /**
423      * Hides the built-in event edit form and returns to the previous calendar view. If the edit form is
424      * not currently visible this method has no effect.
425      * @return {Ext.calendar.CalendarPanel} this
426      */
427     hideEditForm: function() {
428         if (this.preEditView) {
429             this.setActiveView(this.preEditView);
430             delete this.preEditView;
431         }
432         return this;
433     },
434
435     // private
436     setActiveView: function(id) {
437         var l = this.layout;
438         l.setActiveItem(id);
439
440         if (id == this.id + '-edit') {
441             this.getTopToolbar().hide();
442             this.doLayout();
443         }
444         else {
445             l.activeItem.refresh();
446             this.getTopToolbar().show();
447             this.updateNavState();
448         }
449         this.activeView = l.activeItem;
450         this.fireViewChange();
451     },
452
453     // private
454     fireViewChange: function() {
455         var info = null,
456             view = this.layout.activeItem;
457
458         if (view.getViewBounds) {
459             vb = view.getViewBounds();
460             info = {
461                 activeDate: view.getStartDate(),
462                 viewStart: vb.start,
463                 viewEnd: vb.end
464             };
465         };
466         this.fireEvent('viewchange', this, view, info);
467     },
468
469     // private
470     updateNavState: function() {
471         if (this.showNavBar !== false) {
472             var item = this.layout.activeItem,
473             suffix = item.id.split(this.id + '-')[1];
474
475             var btn = Ext.getCmp(this.id + '-tb-' + suffix);
476             btn.toggle(true);
477         }
478     },
479
480     /**
481      * Sets the start date for the currently-active calendar view.
482      * @param {Date} dt
483      */
484     setStartDate: function(dt) {
485         this.layout.activeItem.setStartDate(dt, true);
486         this.updateNavState();
487         this.fireViewChange();
488     },
489
490     // private
491     showWeek: function(dt) {
492         this.setActiveView(this.id + '-week');
493         this.setStartDate(dt);
494     },
495
496     // private
497     onPrevClick: function() {
498         this.startDate = this.layout.activeItem.movePrev();
499         this.updateNavState();
500         this.fireViewChange();
501     },
502
503     // private
504     onNextClick: function() {
505         this.startDate = this.layout.activeItem.moveNext();
506         this.updateNavState();
507         this.fireViewChange();
508     },
509
510     // private
511     onDayClick: function() {
512         this.setActiveView(this.id + '-day');
513     },
514
515     // private
516     onWeekClick: function() {
517         this.setActiveView(this.id + '-week');
518     },
519
520     // private
521     onMonthClick: function() {
522         this.setActiveView(this.id + '-month');
523     },
524
525     /**
526      * Return the calendar view that is currently active, which will be a subclass of
527      * {@link Ext.calendar.CalendarView CalendarView}.
528      * @return {Ext.calendar.CalendarView} The active view
529      */
530     getActiveView: function() {
531         return this.layout.activeItem;
532     }
533 });
534
535 Ext.reg('calendarpanel', Ext.calendar.CalendarPanel);