Upgrade to ExtJS 3.3.0 - Released 10/06/2010
[extjs.git] / examples / calendar / src / views / MonthView.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 /**\r
8  * @class Ext.calendar.MonthView\r
9  * @extends Ext.calendar.CalendarView\r
10  * <p>Displays a calendar view by month. This class does not usually need ot be used directly as you can\r
11  * use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including\r
12  * the month view.</p>\r
13  * @constructor\r
14  * @param {Object} config The config object\r
15  */\r
16 Ext.calendar.MonthView = Ext.extend(Ext.calendar.CalendarView, {\r
17     /**\r
18      * @cfg {Boolean} showTime\r
19      * True to display the current time in today's box in the calendar, false to not display it (defautls to true)\r
20      */\r
21     showTime: true,\r
22     /**\r
23      * @cfg {Boolean} showTodayText\r
24      * True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)\r
25      */\r
26     showTodayText: true,\r
27     /**\r
28      * @cfg {String} todayText\r
29      * The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')\r
30      */\r
31     todayText: 'Today',\r
32     /**\r
33      * @cfg {Boolean} showHeader\r
34      * True to display a header beneath the navigation bar containing the week names above each week's column, false not to \r
35      * show it and instead display the week names in the first row of days in the calendar (defaults to false).\r
36      */\r
37     showHeader: false,\r
38     /**\r
39      * @cfg {Boolean} showWeekLinks\r
40      * True to display an extra column before the first day in the calendar that links to the {@link Ext.calendar.WeekView view}\r
41      * for each individual week, false to not show it (defaults to false). If true, the week links can also contain the week \r
42      * number depending on the value of {@link #showWeekNumbers}.\r
43      */\r
44     showWeekLinks: false,\r
45     /**\r
46      * @cfg {Boolean} showWeekNumbers\r
47      * 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
48      * Note that if {@link #showWeekLinks} is false this config will have no affect even if true.\r
49      */\r
50     showWeekNumbers: false,\r
51     /**\r
52      * @cfg {String} weekLinkOverClass\r
53      * The CSS class name applied when the mouse moves over a week link element (only applies when {@link #showWeekLinks} is true,\r
54      * defaults to 'ext-week-link-over').\r
55      */\r
56     weekLinkOverClass: 'ext-week-link-over',\r
57 \r
58     //private properties -- do not override:\r
59     daySelector: '.ext-cal-day',\r
60     moreSelector: '.ext-cal-ev-more',\r
61     weekLinkSelector: '.ext-cal-week-link',\r
62     weekCount: -1,\r
63     // defaults to auto by month\r
64     dayCount: 7,\r
65     moreElIdDelimiter: '-more-',\r
66     weekLinkIdDelimiter: 'ext-cal-week-',\r
67 \r
68     // private\r
69     initComponent: function() {\r
70         Ext.calendar.MonthView.superclass.initComponent.call(this);\r
71         this.addEvents({\r
72             /**\r
73              * @event dayclick\r
74              * Fires after the user clicks within the view container and not on an event element\r
75              * @param {Ext.calendar.MonthView} this\r
76              * @param {Date} dt The date/time that was clicked on\r
77              * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the \r
78              * MonthView always return true for this param.\r
79              * @param {Ext.Element} el The Element that was clicked on\r
80              */\r
81             dayclick: true,\r
82             /**\r
83              * @event weekclick\r
84              * Fires after the user clicks within a week link (when {@link #showWeekLinks is true)\r
85              * @param {Ext.calendar.MonthView} this\r
86              * @param {Date} dt The start date of the week that was clicked on\r
87              */\r
88             weekclick: true,\r
89             // inherited docs\r
90             dayover: true,\r
91             // inherited docs\r
92             dayout: true\r
93         });\r
94     },\r
95 \r
96     // private\r
97     initDD: function() {\r
98         var cfg = {\r
99             view: this,\r
100             createText: this.ddCreateEventText,\r
101             moveText: this.ddMoveEventText,\r
102             ddGroup: 'MonthViewDD'\r
103         };\r
104 \r
105         this.dragZone = new Ext.calendar.DragZone(this.el, cfg);\r
106         this.dropZone = new Ext.calendar.DropZone(this.el, cfg);\r
107     },\r
108 \r
109     // private\r
110     onDestroy: function() {\r
111         Ext.destroy(this.ddSelector);\r
112         Ext.destroy(this.dragZone);\r
113         Ext.destroy(this.dropZone);\r
114         Ext.calendar.MonthView.superclass.onDestroy.call(this);\r
115     },\r
116 \r
117     // private\r
118     afterRender: function() {\r
119         if (!this.tpl) {\r
120             this.tpl = new Ext.calendar.MonthViewTemplate({\r
121                 id: this.id,\r
122                 showTodayText: this.showTodayText,\r
123                 todayText: this.todayText,\r
124                 showTime: this.showTime,\r
125                 showHeader: this.showHeader,\r
126                 showWeekLinks: this.showWeekLinks,\r
127                 showWeekNumbers: this.showWeekNumbers\r
128             });\r
129         }\r
130         this.tpl.compile();\r
131         this.addClass('ext-cal-monthview ext-cal-ct');\r
132 \r
133         Ext.calendar.MonthView.superclass.afterRender.call(this);\r
134     },\r
135 \r
136     // private\r
137     onResize: function() {\r
138         if (this.monitorResize) {\r
139             this.maxEventsPerDay = this.getMaxEventsPerDay();\r
140             this.refresh();\r
141         }\r
142     },\r
143 \r
144     // private\r
145     forceSize: function() {\r
146         // Compensate for the week link gutter width if visible\r
147         if (this.showWeekLinks && this.el && this.el.child) {\r
148             var hd = this.el.select('.ext-cal-hd-days-tbl'),\r
149             bgTbl = this.el.select('.ext-cal-bg-tbl'),\r
150             evTbl = this.el.select('.ext-cal-evt-tbl'),\r
151             wkLinkW = this.el.child('.ext-cal-week-link').getWidth(),\r
152             w = this.el.getWidth() - wkLinkW;\r
153 \r
154             hd.setWidth(w);\r
155             bgTbl.setWidth(w);\r
156             evTbl.setWidth(w);\r
157         }\r
158         Ext.calendar.MonthView.superclass.forceSize.call(this);\r
159     },\r
160 \r
161     //private\r
162     initClock: function() {\r
163         if (Ext.fly(this.id + '-clock') !== null) {\r
164             this.prevClockDay = new Date().getDay();\r
165             if (this.clockTask) {\r
166                 Ext.TaskMgr.stop(this.clockTask);\r
167             }\r
168             this.clockTask = Ext.TaskMgr.start({\r
169                 run: function() {\r
170                     var el = Ext.fly(this.id + '-clock'),\r
171                     t = new Date();\r
172 \r
173                     if (t.getDay() == this.prevClockDay) {\r
174                         if (el) {\r
175                             el.update(t.format('g:i a'));\r
176                         }\r
177                     }\r
178                     else {\r
179                         this.prevClockDay = t.getDay();\r
180                         this.moveTo(t);\r
181                     }\r
182                 },\r
183                 scope: this,\r
184                 interval: 1000\r
185             });\r
186         }\r
187     },\r
188 \r
189     // inherited docs\r
190     getEventBodyMarkup: function() {\r
191         if (!this.eventBodyMarkup) {\r
192             this.eventBodyMarkup = ['{Title}',\r
193             '<tpl if="_isReminder">',\r
194             '<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',\r
195             '</tpl>',\r
196             '<tpl if="_isRecurring">',\r
197             '<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',\r
198             '</tpl>',\r
199             '<tpl if="spanLeft">',\r
200             '<i class="ext-cal-spl">&nbsp;</i>',\r
201             '</tpl>',\r
202             '<tpl if="spanRight">',\r
203             '<i class="ext-cal-spr">&nbsp;</i>',\r
204             '</tpl>'\r
205             ].join('');\r
206         }\r
207         return this.eventBodyMarkup;\r
208     },\r
209 \r
210     // inherited docs\r
211     getEventTemplate: function() {\r
212         if (!this.eventTpl) {\r
213             var tpl,\r
214             body = this.getEventBodyMarkup();\r
215 \r
216             tpl = !(Ext.isIE || Ext.isOpera) ?\r
217             new Ext.XTemplate(\r
218             '<div id="{_elId}" class="{_selectorCls} {_colorCls} {values.spanCls} ext-cal-evt ext-cal-evr">',\r
219             body,\r
220             '</div>'\r
221             )\r
222             : new Ext.XTemplate(\r
223             '<tpl if="_renderAsAllDay">',\r
224             '<div id="{_elId}" class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evt ext-cal-evo">',\r
225             '<div class="ext-cal-evm">',\r
226             '<div class="ext-cal-evi">',\r
227             '</tpl>',\r
228             '<tpl if="!_renderAsAllDay">',\r
229             '<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr">',\r
230             '</tpl>',\r
231             body,\r
232             '<tpl if="_renderAsAllDay">',\r
233             '</div>',\r
234             '</div>',\r
235             '</tpl>',\r
236             '</div>'\r
237             );\r
238             tpl.compile();\r
239             this.eventTpl = tpl;\r
240         }\r
241         return this.eventTpl;\r
242     },\r
243 \r
244     // private\r
245     getTemplateEventData: function(evt) {\r
246         var M = Ext.calendar.EventMappings,\r
247         selector = this.getEventSelectorCls(evt[M.EventId.name]),\r
248         title = evt[M.Title.name];\r
249 \r
250         return Ext.applyIf({\r
251             _selectorCls: selector,\r
252             _colorCls: 'ext-color-' + (evt[M.CalendarId.name] ?\r
253             evt[M.CalendarId.name] : 'default') + (evt._renderAsAllDay ? '-ad': ''),\r
254             _elId: selector + '-' + evt._weekIndex,\r
255             _isRecurring: evt.Recurrence && evt.Recurrence != '',\r
256             _isReminder: evt[M.Reminder.name] && evt[M.Reminder.name] != '',\r
257             Title: (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title)\r
258         },\r
259         evt);\r
260     },\r
261 \r
262     // private\r
263     refresh: function() {\r
264         if (this.detailPanel) {\r
265             this.detailPanel.hide();\r
266         }\r
267         Ext.calendar.MonthView.superclass.refresh.call(this);\r
268 \r
269         if (this.showTime !== false) {\r
270             this.initClock();\r
271         }\r
272     },\r
273 \r
274     // private\r
275     renderItems: function() {\r
276         Ext.calendar.WeekEventRenderer.render({\r
277             eventGrid: this.allDayOnly ? this.allDayGrid: this.eventGrid,\r
278             viewStart: this.viewStart,\r
279             tpl: this.getEventTemplate(),\r
280             maxEventsPerDay: this.maxEventsPerDay,\r
281             id: this.id,\r
282             templateDataFn: this.getTemplateEventData.createDelegate(this),\r
283             evtMaxCount: this.evtMaxCount,\r
284             weekCount: this.weekCount,\r
285             dayCount: this.dayCount\r
286         });\r
287         this.fireEvent('eventsrendered', this);\r
288     },\r
289 \r
290     // private\r
291     getDayEl: function(dt) {\r
292         return Ext.get(this.getDayId(dt));\r
293     },\r
294 \r
295     // private\r
296     getDayId: function(dt) {\r
297         if (Ext.isDate(dt)) {\r
298             dt = dt.format('Ymd');\r
299         }\r
300         return this.id + this.dayElIdDelimiter + dt;\r
301     },\r
302 \r
303     // private\r
304     getWeekIndex: function(dt) {\r
305         var el = this.getDayEl(dt).up('.ext-cal-wk-ct');\r
306         return parseInt(el.id.split('-wk-')[1], 10);\r
307     },\r
308 \r
309     // private\r
310     getDaySize: function(contentOnly) {\r
311         var box = this.el.getBox(),\r
312         w = box.width / this.dayCount,\r
313         h = box.height / this.getWeekCount();\r
314 \r
315         if (contentOnly) {\r
316             var hd = this.el.select('.ext-cal-dtitle').first().parent('tr');\r
317             h = hd ? h - hd.getHeight(true) : h;\r
318         }\r
319         return {\r
320             height: h,\r
321             width: w\r
322         };\r
323     },\r
324 \r
325     // private\r
326     getEventHeight: function() {\r
327         if (!this.eventHeight) {\r
328             var evt = this.el.select('.ext-cal-evt').first();\r
329             this.eventHeight = evt ? evt.parent('tr').getHeight() : 18;\r
330         }\r
331         return this.eventHeight;\r
332     },\r
333 \r
334     // private\r
335     getMaxEventsPerDay: function() {\r
336         var dayHeight = this.getDaySize(true).height,\r
337             h = this.getEventHeight(),\r
338             max = Math.max(Math.floor((dayHeight - h) / h), 0);\r
339 \r
340         return max;\r
341     },\r
342 \r
343     // private\r
344     getDayAt: function(x, y) {\r
345         var box = this.el.getBox(),\r
346             daySize = this.getDaySize(),\r
347             dayL = Math.floor(((x - box.x) / daySize.width)),\r
348             dayT = Math.floor(((y - box.y) / daySize.height)),\r
349             days = (dayT * 7) + dayL,\r
350             dt = this.viewStart.add(Date.DAY, days);\r
351         return {\r
352             date: dt,\r
353             el: this.getDayEl(dt)\r
354         };\r
355     },\r
356 \r
357     // inherited docs\r
358     moveNext: function() {\r
359         return this.moveMonths(1);\r
360     },\r
361 \r
362     // inherited docs\r
363     movePrev: function() {\r
364         return this.moveMonths( - 1);\r
365     },\r
366 \r
367     // private\r
368     onInitDrag: function() {\r
369         Ext.calendar.MonthView.superclass.onInitDrag.call(this);\r
370         Ext.select(this.daySelector).removeClass(this.dayOverClass);\r
371         if (this.detailPanel) {\r
372             this.detailPanel.hide();\r
373         }\r
374     },\r
375 \r
376     // private\r
377     onMoreClick: function(dt) {\r
378         if (!this.detailPanel) {\r
379             this.detailPanel = new Ext.Panel({\r
380                 id: this.id + '-details-panel',\r
381                 title: dt.format('F j'),\r
382                 layout: 'fit',\r
383                 floating: true,\r
384                 renderTo: Ext.getBody(),\r
385                 tools: [{\r
386                     id: 'close',\r
387                     handler: function(e, t, p) {\r
388                         p.hide();\r
389                     }\r
390                 }],\r
391                 items: {\r
392                     xtype: 'monthdaydetailview',\r
393                     id: this.id + '-details-view',\r
394                     date: dt,\r
395                     view: this,\r
396                     store: this.store,\r
397                     listeners: {\r
398                         'eventsrendered': this.onDetailViewUpdated.createDelegate(this)\r
399                     }\r
400                 }\r
401             });\r
402         }\r
403         else {\r
404             this.detailPanel.setTitle(dt.format('F j'));\r
405         }\r
406         this.detailPanel.getComponent(this.id + '-details-view').update(dt);\r
407     },\r
408 \r
409     // private\r
410     onDetailViewUpdated: function(view, dt, numEvents) {\r
411         var p = this.detailPanel,\r
412         frameH = p.getFrameHeight(),\r
413         evtH = this.getEventHeight(),\r
414         bodyH = frameH + (numEvents * evtH) + 3,\r
415         dayEl = this.getDayEl(dt),\r
416         box = dayEl.getBox();\r
417 \r
418         p.updateBox(box);\r
419         p.setHeight(bodyH);\r
420         p.setWidth(Math.max(box.width, 220));\r
421         p.show();\r
422         p.getPositionEl().alignTo(dayEl, 't-t?');\r
423     },\r
424 \r
425     // private\r
426     onHide: function() {\r
427         Ext.calendar.MonthView.superclass.onHide.call(this);\r
428         if (this.detailPanel) {\r
429             this.detailPanel.hide();\r
430         }\r
431     },\r
432 \r
433     // private\r
434     onClick: function(e, t) {\r
435         if (this.detailPanel) {\r
436             this.detailPanel.hide();\r
437         }\r
438         if (Ext.calendar.MonthView.superclass.onClick.apply(this, arguments)) {\r
439             // The superclass handled the click already so exit\r
440             return;\r
441         }\r
442         if (this.dropZone) {\r
443             this.dropZone.clearShims();\r
444         }\r
445         var el = e.getTarget(this.weekLinkSelector, 3),\r
446             dt,\r
447             parts;\r
448         if (el) {\r
449             dt = el.id.split(this.weekLinkIdDelimiter)[1];\r
450             this.fireEvent('weekclick', this, Date.parseDate(dt, 'Ymd'));\r
451             return;\r
452         }\r
453         el = e.getTarget(this.moreSelector, 3);\r
454         if (el) {\r
455             dt = el.id.split(this.moreElIdDelimiter)[1];\r
456             this.onMoreClick(Date.parseDate(dt, 'Ymd'));\r
457             return;\r
458         }\r
459         el = e.getTarget('td', 3);\r
460         if (el) {\r
461             if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {\r
462                 parts = el.id.split(this.dayElIdDelimiter);\r
463                 dt = parts[parts.length - 1];\r
464 \r
465                 this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), false, Ext.get(this.getDayId(dt)));\r
466                 return;\r
467             }\r
468         }\r
469     },\r
470 \r
471     // private\r
472     handleDayMouseEvent: function(e, t, type) {\r
473         var el = e.getTarget(this.weekLinkSelector, 3, true);\r
474         if (el) {\r
475             el[type == 'over' ? 'addClass': 'removeClass'](this.weekLinkOverClass);\r
476             return;\r
477         }\r
478         Ext.calendar.MonthView.superclass.handleDayMouseEvent.apply(this, arguments);\r
479     }\r
480 });\r
481 \r
482 Ext.reg('monthview', Ext.calendar.MonthView);\r