Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / docs / source / CalendarView.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 <div id="cls-Ext.calendar.CalendarView"></div>/**\r
16  * @class Ext.calendar.CalendarView\r
17  * @extends Ext.BoxComponent\r
18  * <p>This is an abstract class that serves as the base for other calendar views. This class is not\r
19  * intended to be directly instantiated.</p>\r
20  * <p>When extending this class to create a custom calendar view, you must provide an implementation\r
21  * for the <code>renderItems</code> method, as there is no default implementation for rendering events\r
22  * The rendering logic is totally dependent on how the UI structures its data, which\r
23  * is determined by the underlying UI template (this base class does not have a template).</p>\r
24  * @constructor\r
25  * @param {Object} config The config object\r
26  */\r
27 Ext.calendar.CalendarView = Ext.extend(Ext.BoxComponent, {\r
28     <div id="cfg-Ext.calendar.CalendarView-startDay"></div>/**\r
29      * @cfg {Number} startDay\r
30      * The 0-based index for the day on which the calendar week begins (0=Sunday, which is the default)\r
31      */\r
32     startDay: 0,\r
33     <div id="cfg-Ext.calendar.CalendarView-spansHavePriority"></div>/**\r
34      * @cfg {Boolean} spansHavePriority\r
35      * Allows switching between two different modes of rendering events that span multiple days. When true,\r
36      * span events are always sorted first, possibly at the expense of start dates being out of order (e.g., \r
37      * a span event that starts at 11am one day and spans into the next day would display before a non-spanning \r
38      * event that starts at 10am, even though they would not be in date order). This can lead to more compact\r
39      * layouts when there are many overlapping events. If false (the default), events will always sort by start date\r
40      * first which can result in a less compact, but chronologically consistent layout.\r
41      */\r
42     spansHavePriority: false,\r
43     <div id="cfg-Ext.calendar.CalendarView-trackMouseOver"></div>/**\r
44      * @cfg {Boolean} trackMouseOver\r
45      * Whether or not the view tracks and responds to the browser mouseover event on contained elements (defaults to\r
46      * true). If you don't need mouseover event highlighting you can disable this.\r
47      */\r
48     trackMouseOver: true,\r
49     <div id="cfg-Ext.calendar.CalendarView-enableFx"></div>/**\r
50      * @cfg {Boolean} enableFx\r
51      * Determines whether or not visual effects for CRUD actions are enabled (defaults to true). If this is false\r
52      * it will override any values for {@link #enableAddFx}, {@link #enableUpdateFx} or {@link enableRemoveFx} and\r
53      * all animations will be disabled.\r
54      */\r
55     enableFx: true,\r
56     <div id="cfg-Ext.calendar.CalendarView-enableAddFx"></div>/**\r
57      * @cfg {Boolean} enableAddFx\r
58      * True to enable a visual effect on adding a new event (the default), false to disable it. Note that if \r
59      * {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the\r
60      * {@link #doAddFx} method.\r
61      */\r
62     enableAddFx: true,\r
63     <div id="cfg-Ext.calendar.CalendarView-enableUpdateFx"></div>/**\r
64      * @cfg {Boolean} enableUpdateFx\r
65      * True to enable a visual effect on updating an event, false to disable it (the default). Note that if \r
66      * {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the\r
67      * {@link #doUpdateFx} method.\r
68      */\r
69     enableUpdateFx: false,\r
70     <div id="cfg-Ext.calendar.CalendarView-enableRemoveFx"></div>/**\r
71      * @cfg {Boolean} enableRemoveFx\r
72      * True to enable a visual effect on removing an event (the default), false to disable it. Note that if \r
73      * {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the\r
74      * {@link #doRemoveFx} method.\r
75      */\r
76     enableRemoveFx: true,\r
77     <div id="cfg-Ext.calendar.CalendarView-enableDD"></div>/**\r
78      * @cfg {Boolean} enableDD\r
79      * True to enable drag and drop in the calendar view (the default), false to disable it\r
80      */\r
81     enableDD: true,\r
82     <div id="cfg-Ext.calendar.CalendarView-monitorResize"></div>/**\r
83      * @cfg {Boolean} monitorResize\r
84      * True to monitor the browser's resize event (the default), false to ignore it. If the calendar view is rendered\r
85      * into a fixed-size container this can be set to false. However, if the view can change dimensions (e.g., it's in \r
86      * fit layout in a viewport or some other resizable container) it is very important that this config is true so that\r
87      * any resize event propagates properly to all subcomponents and layouts get recalculated properly.\r
88      */\r
89     monitorResize: true,\r
90     <div id="cfg-Ext.calendar.CalendarView-ddCreateEventText"></div>/**\r
91      * @cfg {String} ddCreateEventText\r
92      * The text to display inside the drag proxy while dragging over the calendar to create a new event (defaults to \r
93      * 'Create event for {0}' where {0} is a date range supplied by the view)\r
94      */\r
95     ddCreateEventText: 'Create event for {0}',\r
96     <div id="cfg-Ext.calendar.CalendarView-ddMoveEventText"></div>/**\r
97      * @cfg {String} ddMoveEventText\r
98      * The text to display inside the drag proxy while dragging an event to reposition it (defaults to \r
99      * 'Move event to {0}' where {0} is the updated event start date/time supplied by the view)\r
100      */\r
101     ddMoveEventText: 'Move event to {0}',\r
102     <div id="cfg-Ext.calendar.CalendarView-ddResizeEventText"></div>/**\r
103      * @cfg {String} ddResizeEventText\r
104      * The string displayed to the user in the drag proxy while dragging the resize handle of an event (defaults to \r
105      * 'Update event to {0}' where {0} is the updated event start-end range supplied by the view). Note that \r
106      * this text is only used in views\r
107      * that allow resizing of events.\r
108      */\r
109     ddResizeEventText: 'Update event to {0}',\r
110 \r
111     //private properties -- do not override:\r
112     weekCount: 1,\r
113     dayCount: 1,\r
114     eventSelector: '.ext-cal-evt',\r
115     eventOverClass: 'ext-evt-over',\r
116     eventElIdDelimiter: '-evt-',\r
117     dayElIdDelimiter: '-day-',\r
118 \r
119     <div id="method-Ext.calendar.CalendarView-getEventBodyMarkup"></div>/**\r
120      * Returns a string of HTML template markup to be used as the body portion of the event template created\r
121      * by {@link #getEventTemplate}. This provdes the flexibility to customize what's in the body without\r
122      * having to override the entire XTemplate. This string can include any valid {@link Ext.Template} code, and\r
123      * any data tokens accessible to the containing event template can be referenced in this string.\r
124      * @return {String} The body template string\r
125      */\r
126     getEventBodyMarkup: Ext.emptyFn,\r
127     // must be implemented by a subclass\r
128     <div id="method-Ext.calendar.CalendarView-getEventTemplate"></div>/**\r
129      * <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type\r
130      * {@link Ext.calendar.EventRecord}) to populate the calendar views with events. Internally this method\r
131      * by default generates different markup for browsers that support CSS border radius and those that don't.\r
132      * This method can be overridden as needed to customize the markup generated.</p>\r
133      * <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately\r
134      * from the surrounding container markup.  This provdes the flexibility to customize what's in the body without\r
135      * having to override the entire XTemplate. If you do override this method, you should make sure that your \r
136      * overridden version also does the same.</p>\r
137      * @return {Ext.XTemplate} The event XTemplate\r
138      */\r
139     getEventTemplate: Ext.emptyFn,\r
140     // must be implemented by a subclass\r
141     // private\r
142     initComponent: function() {\r
143         this.setStartDate(this.startDate || new Date());\r
144 \r
145         Ext.calendar.CalendarView.superclass.initComponent.call(this);\r
146 \r
147         this.addEvents({\r
148             <div id="event-Ext.calendar.CalendarView-eventsrendered"></div>/**\r
149              * @event eventsrendered\r
150              * Fires after events are finished rendering in the view\r
151              * @param {Ext.calendar.CalendarView} this \r
152              */\r
153             eventsrendered: true,\r
154             <div id="event-Ext.calendar.CalendarView-eventclick"></div>/**\r
155              * @event eventclick\r
156              * Fires after the user clicks on an event element\r
157              * @param {Ext.calendar.CalendarView} this\r
158              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on\r
159              * @param {HTMLNode} el The DOM node that was clicked on\r
160              */\r
161             eventclick: true,\r
162             <div id="event-Ext.calendar.CalendarView-eventover"></div>/**\r
163              * @event eventover\r
164              * Fires anytime the mouse is over an event element\r
165              * @param {Ext.calendar.CalendarView} this\r
166              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over\r
167              * @param {HTMLNode} el The DOM node that is being moused over\r
168              */\r
169             eventover: true,\r
170             <div id="event-Ext.calendar.CalendarView-eventout"></div>/**\r
171              * @event eventout\r
172              * Fires anytime the mouse exits an event element\r
173              * @param {Ext.calendar.CalendarView} this\r
174              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited\r
175              * @param {HTMLNode} el The DOM node that was exited\r
176              */\r
177             eventout: true,\r
178             <div id="event-Ext.calendar.CalendarView-datechange"></div>/**\r
179              * @event datechange\r
180              * Fires after the start date of the view changes\r
181              * @param {Ext.calendar.CalendarView} this\r
182              * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}\r
183              * @param {Date} viewStart The first displayed date in the view\r
184              * @param {Date} viewEnd The last displayed date in the view\r
185              */\r
186             datechange: true,\r
187             <div id="event-Ext.calendar.CalendarView-rangeselect"></div>/**\r
188              * @event rangeselect\r
189              * Fires after the user drags on the calendar to select a range of dates/times in which to create an event\r
190              * @param {Ext.calendar.CalendarView} this\r
191              * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected\r
192              * @param {Function} callback A callback function that MUST be called after the event handling is complete so that\r
193              * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the\r
194              * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard\r
195              * function call (e.g., callback()).\r
196              */\r
197             rangeselect: true,\r
198             <div id="event-Ext.calendar.CalendarView-eventmove"></div>/**\r
199              * @event eventmove\r
200              * Fires after an event element is dragged by the user and dropped in a new position\r
201              * @param {Ext.calendar.CalendarView} this\r
202              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with\r
203              * updated start and end dates\r
204              */\r
205             eventmove: true,\r
206             <div id="event-Ext.calendar.CalendarView-initdrag"></div>/**\r
207              * @event initdrag\r
208              * Fires when a drag operation is initiated in the view\r
209              * @param {Ext.calendar.CalendarView} this\r
210              */\r
211             initdrag: true,\r
212             <div id="event-Ext.calendar.CalendarView-dayover"></div>/**\r
213              * @event dayover\r
214              * Fires while the mouse is over a day element \r
215              * @param {Ext.calendar.CalendarView} this\r
216              * @param {Date} dt The date that is being moused over\r
217              * @param {Ext.Element} el The day Element that is being moused over\r
218              */\r
219             dayover: true,\r
220             <div id="event-Ext.calendar.CalendarView-dayout"></div>/**\r
221              * @event dayout\r
222              * Fires when the mouse exits a day element \r
223              * @param {Ext.calendar.CalendarView} this\r
224              * @param {Date} dt The date that is exited\r
225              * @param {Ext.Element} el The day Element that is exited\r
226              */\r
227             dayout: true\r
228             /*\r
229              * @event eventdelete\r
230              * Fires after an event element is deleted by the user. Not currently implemented directly at the view level -- currently \r
231              * deletes only happen from one of the forms.\r
232              * @param {Ext.calendar.CalendarView} this\r
233              * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was deleted\r
234              */\r
235             //eventdelete: true\r
236         });\r
237     },\r
238 \r
239     // private\r
240     afterRender: function() {\r
241         Ext.calendar.CalendarView.superclass.afterRender.call(this);\r
242 \r
243         this.renderTemplate();\r
244 \r
245         if (this.store) {\r
246             this.setStore(this.store, true);\r
247         }\r
248 \r
249         this.el.on({\r
250             'mouseover': this.onMouseOver,\r
251             'mouseout': this.onMouseOut,\r
252             'click': this.onClick,\r
253             'resize': this.onResize,\r
254             scope: this\r
255         });\r
256 \r
257         this.el.unselectable();\r
258 \r
259         if (this.enableDD && this.initDD) {\r
260             this.initDD();\r
261         }\r
262 \r
263         this.on('eventsrendered', this.forceSize);\r
264         this.forceSize.defer(100, this);\r
265 \r
266     },\r
267 \r
268     // private\r
269     forceSize: function() {\r
270         if (this.el && this.el.child) {\r
271             var hd = this.el.child('.ext-cal-hd-ct'),\r
272             bd = this.el.child('.ext-cal-body-ct');\r
273 \r
274             if (bd == null || hd == null) return;\r
275 \r
276             var headerHeight = hd.getHeight(),\r
277             sz = this.el.parent().getSize();\r
278 \r
279             bd.setHeight(sz.height - headerHeight);\r
280         }\r
281     },\r
282 \r
283     refresh: function() {\r
284         this.prepareData();\r
285         this.renderTemplate();\r
286         this.renderItems();\r
287     },\r
288 \r
289     getWeekCount: function() {\r
290         var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd);\r
291         return Math.ceil(days / this.dayCount);\r
292     },\r
293 \r
294     // private\r
295     prepareData: function() {\r
296         var lastInMonth = this.startDate.getLastDateOfMonth(),\r
297         w = 0,\r
298         row = 0,\r
299         dt = this.viewStart.clone(),\r
300         weeks = this.weekCount < 1 ? 6: this.weekCount;\r
301 \r
302         this.eventGrid = [[]];\r
303         this.allDayGrid = [[]];\r
304         this.evtMaxCount = [];\r
305 \r
306         var evtsInView = this.store.queryBy(function(rec) {\r
307             return this.isEventVisible(rec.data);\r
308         },\r
309         this);\r
310 \r
311         for (; w < weeks; w++) {\r
312             this.evtMaxCount[w] = 0;\r
313             if (this.weekCount == -1 && dt > lastInMonth) {\r
314                 //current week is fully in next month so skip\r
315                 break;\r
316             }\r
317             this.eventGrid[w] = this.eventGrid[w] || [];\r
318             this.allDayGrid[w] = this.allDayGrid[w] || [];\r
319 \r
320             for (d = 0; d < this.dayCount; d++) {\r
321                 if (evtsInView.getCount() > 0) {\r
322                     var evts = evtsInView.filterBy(function(rec) {\r
323                         var startsOnDate = (dt.getTime() == rec.data[Ext.calendar.EventMappings.StartDate.name].clearTime(true).getTime());\r
324                         var spansFromPrevView = (w == 0 && d == 0 && (dt > rec.data[Ext.calendar.EventMappings.StartDate.name]));\r
325                         return startsOnDate || spansFromPrevView;\r
326                     },\r
327                     this);\r
328 \r
329                     this.sortEventRecordsForDay(evts);\r
330                     this.prepareEventGrid(evts, w, d);\r
331                 }\r
332                 dt = dt.add(Date.DAY, 1);\r
333             }\r
334         }\r
335         this.currentWeekCount = w;\r
336     },\r
337 \r
338     // private\r
339     prepareEventGrid: function(evts, w, d) {\r
340         var row = 0,\r
341         dt = this.viewStart.clone(),\r
342         max = this.maxEventsPerDay ? this.maxEventsPerDay: 999;\r
343 \r
344         evts.each(function(evt) {\r
345             var M = Ext.calendar.EventMappings,\r
346             days = Ext.calendar.Date.diffDays(\r
347             Ext.calendar.Date.max(this.viewStart, evt.data[M.StartDate.name]),\r
348             Ext.calendar.Date.min(this.viewEnd, evt.data[M.EndDate.name])) + 1;\r
349 \r
350             if (days > 1 || Ext.calendar.Date.diffDays(evt.data[M.StartDate.name], evt.data[M.EndDate.name]) > 1) {\r
351                 this.prepareEventGridSpans(evt, this.eventGrid, w, d, days);\r
352                 this.prepareEventGridSpans(evt, this.allDayGrid, w, d, days, true);\r
353             } else {\r
354                 row = this.findEmptyRowIndex(w, d);\r
355                 this.eventGrid[w][d] = this.eventGrid[w][d] || [];\r
356                 this.eventGrid[w][d][row] = evt;\r
357 \r
358                 if (evt.data[M.IsAllDay.name]) {\r
359                     row = this.findEmptyRowIndex(w, d, true);\r
360                     this.allDayGrid[w][d] = this.allDayGrid[w][d] || [];\r
361                     this.allDayGrid[w][d][row] = evt;\r
362                 }\r
363             }\r
364 \r
365             if (this.evtMaxCount[w] < this.eventGrid[w][d].length) {\r
366                 this.evtMaxCount[w] = Math.min(max + 1, this.eventGrid[w][d].length);\r
367             }\r
368             return true;\r
369         },\r
370         this);\r
371     },\r
372 \r
373     // private\r
374     prepareEventGridSpans: function(evt, grid, w, d, days, allday) {\r
375         // this event spans multiple days/weeks, so we have to preprocess\r
376         // the events and store special span events as placeholders so that\r
377         // the render routine can build the necessary TD spans correctly.\r
378         var w1 = w,\r
379         d1 = d,\r
380         row = this.findEmptyRowIndex(w, d, allday),\r
381         dt = this.viewStart.clone();\r
382 \r
383         var start = {\r
384             event: evt,\r
385             isSpan: true,\r
386             isSpanStart: true,\r
387             spanLeft: false,\r
388             spanRight: (d == 6)\r
389         };\r
390         grid[w][d] = grid[w][d] || [];\r
391         grid[w][d][row] = start;\r
392 \r
393         while (--days) {\r
394             dt = dt.add(Date.DAY, 1);\r
395             if (dt > this.viewEnd) {\r
396                 break;\r
397             }\r
398             if (++d1 > 6) {\r
399                 // reset counters to the next week\r
400                 d1 = 0;\r
401                 w1++;\r
402                 row = this.findEmptyRowIndex(w1, 0);\r
403             }\r
404             grid[w1] = grid[w1] || [];\r
405             grid[w1][d1] = grid[w1][d1] || [];\r
406 \r
407             grid[w1][d1][row] = {\r
408                 event: evt,\r
409                 isSpan: true,\r
410                 isSpanStart: (d1 == 0),\r
411                 spanLeft: (w1 > w) && (d1 % 7 == 0),\r
412                 spanRight: (d1 == 6) && (days > 1)\r
413             };\r
414         }\r
415     },\r
416 \r
417     // private\r
418     findEmptyRowIndex: function(w, d, allday) {\r
419         var grid = allday ? this.allDayGrid: this.eventGrid,\r
420         day = grid[w] ? grid[w][d] || [] : [],\r
421         i = 0,\r
422         ln = day.length;\r
423 \r
424         for (; i < ln; i++) {\r
425             if (day[i] == null) {\r
426                 return i;\r
427             }\r
428         }\r
429         return ln;\r
430     },\r
431 \r
432     // private\r
433     renderTemplate: function() {\r
434         if (this.tpl) {\r
435             this.tpl.overwrite(this.el, this.getParams());\r
436             this.lastRenderStart = this.viewStart.clone();\r
437             this.lastRenderEnd = this.viewEnd.clone();\r
438         }\r
439     },\r
440 \r
441     disableStoreEvents: function() {\r
442         this.monitorStoreEvents = false;\r
443     },\r
444 \r
445     enableStoreEvents: function(refresh) {\r
446         this.monitorStoreEvents = true;\r
447         if (refresh === true) {\r
448             this.refresh();\r
449         }\r
450     },\r
451 \r
452     // private\r
453     onResize: function() {\r
454         this.refresh();\r
455     },\r
456 \r
457     // private\r
458     onInitDrag: function() {\r
459         this.fireEvent('initdrag', this);\r
460     },\r
461 \r
462     // private\r
463     onEventDrop: function(rec, dt) {\r
464         if (Ext.calendar.Date.compare(rec.data[Ext.calendar.EventMappings.StartDate.name], dt) === 0) {\r
465             // no changes\r
466             return;\r
467         }\r
468         var diff = dt.getTime() - rec.data[Ext.calendar.EventMappings.StartDate.name].getTime();\r
469         rec.set(Ext.calendar.EventMappings.StartDate.name, dt);\r
470         rec.set(Ext.calendar.EventMappings.EndDate.name, rec.data[Ext.calendar.EventMappings.EndDate.name].add(Date.MILLI, diff));\r
471 \r
472         this.fireEvent('eventmove', this, rec);\r
473     },\r
474 \r
475     // private\r
476     onCalendarEndDrag: function(start, end, onComplete) {\r
477         // set this flag for other event handlers that might conflict while we're waiting\r
478         this.dragPending = true;\r
479 \r
480         // have to wait for the user to save or cancel before finalizing the dd interation\r
481         var o = {};\r
482         o[Ext.calendar.EventMappings.StartDate.name] = start;\r
483         o[Ext.calendar.EventMappings.EndDate.name] = end;\r
484 \r
485         this.fireEvent('rangeselect', this, o, this.onCalendarEndDragComplete.createDelegate(this, [onComplete]));\r
486     },\r
487 \r
488     // private\r
489     onCalendarEndDragComplete: function(onComplete) {\r
490         // callback for the drop zone to clean up\r
491         onComplete();\r
492         // clear flag for other events to resume normally\r
493         this.dragPending = false;\r
494     },\r
495 \r
496     // private\r
497     onUpdate: function(ds, rec, operation) {\r
498         if (this.monitorStoreEvents === false) {\r
499             return;\r
500         }\r
501         if (operation == Ext.data.Record.COMMIT) {\r
502             this.refresh();\r
503             if (this.enableFx && this.enableUpdateFx) {\r
504                 this.doUpdateFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {\r
505                     scope: this\r
506                 });\r
507             }\r
508         }\r
509     },\r
510 \r
511 \r
512     doUpdateFx: function(els, o) {\r
513         this.highlightEvent(els, null, o);\r
514     },\r
515 \r
516     // private\r
517     onAdd: function(ds, records, index) {\r
518         if (this.monitorStoreEvents === false) {\r
519             return;\r
520         }\r
521         var rec = records[0];\r
522         this.tempEventId = rec.id;\r
523         this.refresh();\r
524 \r
525         if (this.enableFx && this.enableAddFx) {\r
526             this.doAddFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {\r
527                 scope: this\r
528             });\r
529         };\r
530     },\r
531 \r
532     doAddFx: function(els, o) {\r
533         els.fadeIn(Ext.apply(o, {\r
534             duration: 2\r
535         }));\r
536     },\r
537 \r
538     // private\r
539     onRemove: function(ds, rec) {\r
540         if (this.monitorStoreEvents === false) {\r
541             return;\r
542         }\r
543         if (this.enableFx && this.enableRemoveFx) {\r
544             this.doRemoveFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {\r
545                 remove: true,\r
546                 scope: this,\r
547                 callback: this.refresh\r
548             });\r
549         }\r
550         else {\r
551             this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]).remove();\r
552             this.refresh();\r
553         }\r
554     },\r
555 \r
556     doRemoveFx: function(els, o) {\r
557         els.fadeOut(o);\r
558     },\r
559 \r
560     <div id="method-Ext.calendar.CalendarView-highlightEvent"></div>/**\r
561      * Visually highlights an event using {@link Ext.Fx#highlight} config options.\r
562      * If {@link #highlightEventActions} is false this method will have no effect.\r
563      * @param {Ext.CompositeElement} els The element(s) to highlight\r
564      * @param {Object} color (optional) The highlight color. Should be a 6 char hex \r
565      * color without the leading # (defaults to yellow: 'ffff9c')\r
566      * @param {Object} o (optional) Object literal with any of the {@link Ext.Fx} config \r
567      * options. See {@link Ext.Fx#highlight} for usage examples.\r
568      */\r
569     highlightEvent: function(els, color, o) {\r
570         if (this.enableFx) {\r
571             var c;\r
572             ! (Ext.isIE || Ext.isOpera) ?\r
573             els.highlight(color, o) :\r
574             // Fun IE/Opera handling:\r
575             els.each(function(el) {\r
576                 el.highlight(color, Ext.applyIf({\r
577                     attr: 'color'\r
578                 },\r
579                 o));\r
580                 c = el.child('.ext-cal-evm');\r
581                 if (c) {\r
582                     c.highlight(color, o);\r
583                 }\r
584             },\r
585             this);\r
586         }\r
587     },\r
588 \r
589     <div id="method-Ext.calendar.CalendarView-getEventIdFromEl"></div>/**\r
590      * Retrieve an Event object's id from its corresponding node in the DOM.\r
591      * @param {String/Element/HTMLElement} el An {@link Ext.Element}, DOM node or id\r
592      */\r
593     getEventIdFromEl: function(el) {\r
594         el = Ext.get(el);\r
595         var id = el.id.split(this.eventElIdDelimiter)[1];\r
596         if (id.indexOf('-') > -1) {\r
597             //This id has the index of the week it is rendered in as the suffix.\r
598             //This allows events that span across weeks to still have reproducibly-unique DOM ids.\r
599             id = id.split('-')[0];\r
600         }\r
601         return id;\r
602     },\r
603 \r
604     // private\r
605     getEventId: function(eventId) {\r
606         if (eventId === undefined && this.tempEventId) {\r
607             eventId = this.tempEventId;\r
608         }\r
609         return eventId;\r
610     },\r
611 \r
612     <div id="method-Ext.calendar.CalendarView-getEventSelectorCls"></div>/**\r
613      * \r
614      * @param {String} eventId\r
615      * @param {Boolean} forSelect\r
616      * @return {String} The selector class\r
617      */\r
618     getEventSelectorCls: function(eventId, forSelect) {\r
619         var prefix = forSelect ? '.': '';\r
620         return prefix + this.id + this.eventElIdDelimiter + this.getEventId(eventId);\r
621     },\r
622 \r
623     <div id="method-Ext.calendar.CalendarView-getEventEls"></div>/**\r
624      * \r
625      * @param {String} eventId\r
626      * @return {Ext.CompositeElement} The matching CompositeElement of nodes\r
627      * that comprise the rendered event.  Any event that spans across a view \r
628      * boundary will contain more than one internal Element.\r
629      */\r
630     getEventEls: function(eventId) {\r
631         var els = Ext.select(this.getEventSelectorCls(this.getEventId(eventId), true), false, this.el.id);\r
632         return new Ext.CompositeElement(els);\r
633     },\r
634 \r
635     <div id="method-Ext.calendar.CalendarView-isToday"></div>/**\r
636      * Returns true if the view is currently displaying today's date, else false.\r
637      * @return {Boolean} True or false\r
638      */\r
639     isToday: function() {\r
640         var today = new Date().clearTime().getTime();\r
641         return this.viewStart.getTime() <= today && this.viewEnd.getTime() >= today;\r
642     },\r
643 \r
644     // private\r
645     onDataChanged: function(store) {\r
646         this.refresh();\r
647     },\r
648 \r
649     // private\r
650     isEventVisible: function(evt) {\r
651         var start = this.viewStart.getTime(),\r
652         end = this.viewEnd.getTime(),\r
653         M = Ext.calendar.EventMappings,\r
654         evStart = (evt.data ? evt.data[M.StartDate.name] : evt[M.StartDate.name]).getTime(),\r
655         evEnd = (evt.data ? evt.data[M.EndDate.name] : evt[M.EndDate.name]).add(Date.SECOND, -1).getTime(),\r
656 \r
657         startsInRange = (evStart >= start && evStart <= end),\r
658         endsInRange = (evEnd >= start && evEnd <= end),\r
659         spansRange = (evStart < start && evEnd > end);\r
660 \r
661         return (startsInRange || endsInRange || spansRange);\r
662     },\r
663 \r
664     // private\r
665     isOverlapping: function(evt1, evt2) {\r
666         var ev1 = evt1.data ? evt1.data: evt1,\r
667         ev2 = evt2.data ? evt2.data: evt2,\r
668         M = Ext.calendar.EventMappings,\r
669         start1 = ev1[M.StartDate.name].getTime(),\r
670         end1 = ev1[M.EndDate.name].add(Date.SECOND, -1).getTime(),\r
671         start2 = ev2[M.StartDate.name].getTime(),\r
672         end2 = ev2[M.EndDate.name].add(Date.SECOND, -1).getTime();\r
673 \r
674         if (end1 < start1) {\r
675             end1 = start1;\r
676         }\r
677         if (end2 < start2) {\r
678             end2 = start2;\r
679         }\r
680 \r
681         var ev1startsInEv2 = (start1 >= start2 && start1 <= end2),\r
682         ev1EndsInEv2 = (end1 >= start2 && end1 <= end2),\r
683         ev1SpansEv2 = (start1 < start2 && end1 > end2);\r
684 \r
685         return (ev1startsInEv2 || ev1EndsInEv2 || ev1SpansEv2);\r
686     },\r
687 \r
688     getDayEl: function(dt) {\r
689         return Ext.get(this.getDayId(dt));\r
690     },\r
691 \r
692     getDayId: function(dt) {\r
693         if (Ext.isDate(dt)) {\r
694             dt = dt.format('Ymd');\r
695         }\r
696         return this.id + this.dayElIdDelimiter + dt;\r
697     },\r
698 \r
699     <div id="method-Ext.calendar.CalendarView-getStartDate"></div>/**\r
700      * Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not \r
701      * be the first date displayed in the rendered calendar -- to get the start and end dates displayed\r
702      * to the user use {@link #getViewBounds}.\r
703      * @return {Date} The start date\r
704      */\r
705     getStartDate: function() {\r
706         return this.startDate;\r
707     },\r
708 \r
709     <div id="method-Ext.calendar.CalendarView-setStartDate"></div>/**\r
710      * Sets the start date used to calculate the view boundaries to display. The displayed view will be the \r
711      * earliest and latest dates that match the view requirements and contain the date passed to this function.\r
712      * @param {Date} dt The date used to calculate the new view boundaries\r
713      */\r
714     setStartDate: function(start, refresh) {\r
715         this.startDate = start.clearTime();\r
716         this.setViewBounds(start);\r
717         this.store.load({\r
718             params: {\r
719                 start: this.viewStart.format('m-d-Y'),\r
720                 end: this.viewEnd.format('m-d-Y')\r
721             }\r
722         });\r
723         if (refresh === true) {\r
724             this.refresh();\r
725         }\r
726         this.fireEvent('datechange', this, this.startDate, this.viewStart, this.viewEnd);\r
727     },\r
728 \r
729     // private\r
730     setViewBounds: function(startDate) {\r
731         var start = startDate || this.startDate,\r
732         offset = start.getDay() - this.startDay;\r
733 \r
734         switch (this.weekCount) {\r
735         case 0:\r
736         case 1:\r
737             this.viewStart = this.dayCount < 7 ? start: start.add(Date.DAY, -offset).clearTime(true);\r
738             this.viewEnd = this.viewStart.add(Date.DAY, this.dayCount || 7).add(Date.SECOND, -1);\r
739             return;\r
740 \r
741         case - 1:\r
742             // auto by month\r
743             start = start.getFirstDateOfMonth();\r
744             offset = start.getDay() - this.startDay;\r
745             if (offset < 0) {\r
746                 offset += 7;\r
747             }\r
748             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
749 \r
750             // start from current month start, not view start:\r
751             var end = start.add(Date.MONTH, 1).add(Date.SECOND, -1);\r
752             // fill out to the end of the week:\r
753             this.viewEnd = end.add(Date.DAY, 6 - end.getDay());\r
754             return;\r
755 \r
756         default:\r
757             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
758             this.viewEnd = this.viewStart.add(Date.DAY, this.weekCount * 7).add(Date.SECOND, -1);\r
759         }\r
760     },\r
761 \r
762     // private\r
763     getViewBounds: function() {\r
764         return {\r
765             start: this.viewStart,\r
766             end: this.viewEnd\r
767         };\r
768     },\r
769 \r
770     /* private\r
771      * Sort events for a single day for display in the calendar.  This sorts allday\r
772      * events first, then non-allday events are sorted either based on event start\r
773      * priority or span priority based on the value of {@link #spansHavePriority} \r
774      * (defaults to event start priority).\r
775      * @param {MixedCollection} evts A {@link Ext.util.MixedCollection MixedCollection}  \r
776      * of {@link #Ext.calendar.EventRecord EventRecord} objects\r
777      */\r
778     sortEventRecordsForDay: function(evts) {\r
779         if (evts.length < 2) {\r
780             return;\r
781         }\r
782         evts.sort('ASC',\r
783         function(evtA, evtB) {\r
784             var a = evtA.data,\r
785             b = evtB.data,\r
786             M = Ext.calendar.EventMappings;\r
787 \r
788             // Always sort all day events before anything else\r
789             if (a[M.IsAllDay.name]) {\r
790                 return - 1;\r
791             }\r
792             else if (b[M.IsAllDay.name]) {\r
793                 return 1;\r
794             }\r
795             if (this.spansHavePriority) {\r
796                 // This logic always weights span events higher than non-span events\r
797                 // (at the possible expense of start time order). This seems to\r
798                 // be the approach used by Google calendar and can lead to a more\r
799                 // visually appealing layout in complex cases, but event order is\r
800                 // not guaranteed to be consistent.\r
801                 var diff = Ext.calendar.Date.diffDays;\r
802                 if (diff(a[M.StartDate.name], a[M.EndDate.name]) > 0) {\r
803                     if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
804                         // Both events are multi-day\r
805                         if (a[M.StartDate.name].getTime() == b[M.StartDate.name].getTime()) {\r
806                             // If both events start at the same time, sort the one\r
807                             // that ends later (potentially longer span bar) first\r
808                             return b[M.EndDate.name].getTime() - a[M.EndDate.name].getTime();\r
809                         }\r
810                         return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
811                     }\r
812                     return - 1;\r
813                 }\r
814                 else if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
815                     return 1;\r
816                 }\r
817                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
818             }\r
819             else {\r
820                 // Doing this allows span and non-span events to intermingle but\r
821                 // remain sorted sequentially by start time. This seems more proper\r
822                 // but can make for a less visually-compact layout when there are\r
823                 // many such events mixed together closely on the calendar.\r
824                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
825             }\r
826         }.createDelegate(this));\r
827     },\r
828 \r
829     <div id="method-Ext.calendar.CalendarView-moveTo"></div>/**\r
830      * Updates the view to contain the passed date\r
831      * @param {Date} dt The date to display\r
832      */\r
833     moveTo: function(dt, noRefresh) {\r
834         if (Ext.isDate(dt)) {\r
835             this.setStartDate(dt);\r
836             if (noRefresh !== false) {\r
837                 this.refresh();\r
838             }\r
839             return this.startDate;\r
840         }\r
841         return dt;\r
842     },\r
843 \r
844     <div id="method-Ext.calendar.CalendarView-moveNext"></div>/**\r
845      * Updates the view to the next consecutive date(s)\r
846      */\r
847     moveNext: function(noRefresh) {\r
848         return this.moveTo(this.viewEnd.add(Date.DAY, 1));\r
849     },\r
850 \r
851     <div id="method-Ext.calendar.CalendarView-movePrev"></div>/**\r
852      * Updates the view to the previous consecutive date(s)\r
853      */\r
854     movePrev: function(noRefresh) {\r
855         var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd) + 1;\r
856         return this.moveDays( - days, noRefresh);\r
857     },\r
858 \r
859     <div id="method-Ext.calendar.CalendarView-moveMonths"></div>/**\r
860      * Shifts the view by the passed number of months relative to the currently set date\r
861      * @param {Number} value The number of months (positive or negative) by which to shift the view\r
862      */\r
863     moveMonths: function(value, noRefresh) {\r
864         return this.moveTo(this.startDate.add(Date.MONTH, value), noRefresh);\r
865     },\r
866 \r
867     <div id="method-Ext.calendar.CalendarView-moveWeeks"></div>/**\r
868      * Shifts the view by the passed number of weeks relative to the currently set date\r
869      * @param {Number} value The number of weeks (positive or negative) by which to shift the view\r
870      */\r
871     moveWeeks: function(value, noRefresh) {\r
872         return this.moveTo(this.startDate.add(Date.DAY, value * 7), noRefresh);\r
873     },\r
874 \r
875     <div id="method-Ext.calendar.CalendarView-moveDays"></div>/**\r
876      * Shifts the view by the passed number of days relative to the currently set date\r
877      * @param {Number} value The number of days (positive or negative) by which to shift the view\r
878      */\r
879     moveDays: function(value, noRefresh) {\r
880         return this.moveTo(this.startDate.add(Date.DAY, value), noRefresh);\r
881     },\r
882 \r
883     <div id="method-Ext.calendar.CalendarView-moveToday"></div>/**\r
884      * Updates the view to show today\r
885      */\r
886     moveToday: function(noRefresh) {\r
887         return this.moveTo(new Date(), noRefresh);\r
888     },\r
889 \r
890     <div id="method-Ext.calendar.CalendarView-setStore"></div>/**\r
891      * Sets the event store used by the calendar to display {@link Ext.calendar.EventRecord events}.\r
892      * @param {Ext.data.Store} store\r
893      */\r
894     setStore: function(store, initial) {\r
895         if (!initial && this.store) {\r
896             this.store.un("datachanged", this.onDataChanged, this);\r
897             this.store.un("add", this.onAdd, this);\r
898             this.store.un("remove", this.onRemove, this);\r
899             this.store.un("update", this.onUpdate, this);\r
900             this.store.un("clear", this.refresh, this);\r
901         }\r
902         if (store) {\r
903             store.on("datachanged", this.onDataChanged, this);\r
904             store.on("add", this.onAdd, this);\r
905             store.on("remove", this.onRemove, this);\r
906             store.on("update", this.onUpdate, this);\r
907             store.on("clear", this.refresh, this);\r
908         }\r
909         this.store = store;\r
910         if (store && store.getCount() > 0) {\r
911             this.refresh();\r
912         }\r
913     },\r
914 \r
915     getEventRecord: function(id) {\r
916         var idx = this.store.find(Ext.calendar.EventMappings.EventId.name, id);\r
917         return this.store.getAt(idx);\r
918     },\r
919 \r
920     getEventRecordFromEl: function(el) {\r
921         return this.getEventRecord(this.getEventIdFromEl(el));\r
922     },\r
923 \r
924     // private\r
925     getParams: function() {\r
926         return {\r
927             viewStart: this.viewStart,\r
928             viewEnd: this.viewEnd,\r
929             startDate: this.startDate,\r
930             dayCount: this.dayCount,\r
931             weekCount: this.weekCount,\r
932             title: this.getTitle()\r
933         };\r
934     },\r
935 \r
936     getTitle: function() {\r
937         return this.startDate.format('F Y');\r
938     },\r
939 \r
940     /*\r
941      * Shared click handling.  Each specific view also provides view-specific\r
942      * click handling that calls this first.  This method returns true if it\r
943      * can handle the click (and so the subclass should ignore it) else false.\r
944      */\r
945     onClick: function(e, t) {\r
946         var el = e.getTarget(this.eventSelector, 5);\r
947         if (el) {\r
948             var id = this.getEventIdFromEl(el);\r
949             this.fireEvent('eventclick', this, this.getEventRecord(id), el);\r
950             return true;\r
951         }\r
952     },\r
953 \r
954     // private\r
955     onMouseOver: function(e, t) {\r
956         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
957             if (!this.handleEventMouseEvent(e, t, 'over')) {\r
958                 this.handleDayMouseEvent(e, t, 'over');\r
959             }\r
960         }\r
961     },\r
962 \r
963     // private\r
964     onMouseOut: function(e, t) {\r
965         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
966             if (!this.handleEventMouseEvent(e, t, 'out')) {\r
967                 this.handleDayMouseEvent(e, t, 'out');\r
968             }\r
969         }\r
970     },\r
971 \r
972     // private\r
973     handleEventMouseEvent: function(e, t, type) {\r
974         var el = e.getTarget(this.eventSelector, 5, true),\r
975             rel,\r
976             els,\r
977             evtId;\r
978         if (el) {\r
979             rel = Ext.get(e.getRelatedTarget());\r
980             if (el == rel || el.contains(rel)) {\r
981                 return true;\r
982             }\r
983 \r
984             evtId = this.getEventIdFromEl(el);\r
985 \r
986             if (this.eventOverClass != '') {\r
987                 els = this.getEventEls(evtId);\r
988                 els[type == 'over' ? 'addClass': 'removeClass'](this.eventOverClass);\r
989             }\r
990             this.fireEvent('event' + type, this, this.getEventRecord(evtId), el);\r
991             return true;\r
992         }\r
993         return false;\r
994     },\r
995 \r
996     // private\r
997     getDateFromId: function(id, delim) {\r
998         var parts = id.split(delim);\r
999         return parts[parts.length - 1];\r
1000     },\r
1001 \r
1002     // private\r
1003     handleDayMouseEvent: function(e, t, type) {\r
1004         t = e.getTarget('td', 3);\r
1005         if (t) {\r
1006             if (t.id && t.id.indexOf(this.dayElIdDelimiter) > -1) {\r
1007                 var dt = this.getDateFromId(t.id, this.dayElIdDelimiter),\r
1008                 rel = Ext.get(e.getRelatedTarget()),\r
1009                 relTD,\r
1010                 relDate;\r
1011 \r
1012                 if (rel) {\r
1013                     relTD = rel.is('td') ? rel: rel.up('td', 3);\r
1014                     relDate = relTD && relTD.id ? this.getDateFromId(relTD.id, this.dayElIdDelimiter) : '';\r
1015                 }\r
1016                 if (!rel || dt != relDate) {\r
1017                     var el = this.getDayEl(dt);\r
1018                     if (el && this.dayOverClass != '') {\r
1019                         el[type == 'over' ? 'addClass': 'removeClass'](this.dayOverClass);\r
1020                     }\r
1021                     this.fireEvent('day' + type, this, Date.parseDate(dt, "Ymd"), el);\r
1022                 }\r
1023             }\r
1024         }\r
1025     },\r
1026 \r
1027     // private\r
1028     renderItems: function() {\r
1029         throw 'This method must be implemented by a subclass';\r
1030     }\r
1031 });\r
1032 </pre>    
1033 </body>
1034 </html>