Upgrade to ExtJS 3.3.0 - Released 10/06/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.0
11  * Copyright(c) 2006-2010 Ext JS, Inc.
12  * licensing@extjs.com
13  * http://www.extjs.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 \r
746             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
747 \r
748             // start from current month start, not view start:\r
749             var end = start.add(Date.MONTH, 1).add(Date.SECOND, -1);\r
750             // fill out to the end of the week:\r
751             this.viewEnd = end.add(Date.DAY, 6 - end.getDay());\r
752             return;\r
753 \r
754         default:\r
755             this.viewStart = start.add(Date.DAY, -offset).clearTime(true);\r
756             this.viewEnd = this.viewStart.add(Date.DAY, this.weekCount * 7).add(Date.SECOND, -1);\r
757         }\r
758     },\r
759 \r
760     // private\r
761     getViewBounds: function() {\r
762         return {\r
763             start: this.viewStart,\r
764             end: this.viewEnd\r
765         };\r
766     },\r
767 \r
768     /* private\r
769      * Sort events for a single day for display in the calendar.  This sorts allday\r
770      * events first, then non-allday events are sorted either based on event start\r
771      * priority or span priority based on the value of {@link #spansHavePriority} \r
772      * (defaults to event start priority).\r
773      * @param {MixedCollection} evts A {@link Ext.util.MixedCollection MixedCollection}  \r
774      * of {@link #Ext.calendar.EventRecord EventRecord} objects\r
775      */\r
776     sortEventRecordsForDay: function(evts) {\r
777         if (evts.length < 2) {\r
778             return;\r
779         }\r
780         evts.sort('ASC',\r
781         function(evtA, evtB) {\r
782             var a = evtA.data,\r
783             b = evtB.data,\r
784             M = Ext.calendar.EventMappings;\r
785 \r
786             // Always sort all day events before anything else\r
787             if (a[M.IsAllDay.name]) {\r
788                 return - 1;\r
789             }\r
790             else if (b[M.IsAllDay.name]) {\r
791                 return 1;\r
792             }\r
793             if (this.spansHavePriority) {\r
794                 // This logic always weights span events higher than non-span events\r
795                 // (at the possible expense of start time order). This seems to\r
796                 // be the approach used by Google calendar and can lead to a more\r
797                 // visually appealing layout in complex cases, but event order is\r
798                 // not guaranteed to be consistent.\r
799                 var diff = Ext.calendar.Date.diffDays;\r
800                 if (diff(a[M.StartDate.name], a[M.EndDate.name]) > 0) {\r
801                     if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
802                         // Both events are multi-day\r
803                         if (a[M.StartDate.name].getTime() == b[M.StartDate.name].getTime()) {\r
804                             // If both events start at the same time, sort the one\r
805                             // that ends later (potentially longer span bar) first\r
806                             return b[M.EndDate.name].getTime() - a[M.EndDate.name].getTime();\r
807                         }\r
808                         return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
809                     }\r
810                     return - 1;\r
811                 }\r
812                 else if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {\r
813                     return 1;\r
814                 }\r
815                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
816             }\r
817             else {\r
818                 // Doing this allows span and non-span events to intermingle but\r
819                 // remain sorted sequentially by start time. This seems more proper\r
820                 // but can make for a less visually-compact layout when there are\r
821                 // many such events mixed together closely on the calendar.\r
822                 return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();\r
823             }\r
824         }.createDelegate(this));\r
825     },\r
826 \r
827     <div id="method-Ext.calendar.CalendarView-moveTo"></div>/**\r
828      * Updates the view to contain the passed date\r
829      * @param {Date} dt The date to display\r
830      */\r
831     moveTo: function(dt, noRefresh) {\r
832         if (Ext.isDate(dt)) {\r
833             this.setStartDate(dt);\r
834             if (noRefresh !== false) {\r
835                 this.refresh();\r
836             }\r
837             return this.startDate;\r
838         }\r
839         return dt;\r
840     },\r
841 \r
842     <div id="method-Ext.calendar.CalendarView-moveNext"></div>/**\r
843      * Updates the view to the next consecutive date(s)\r
844      */\r
845     moveNext: function(noRefresh) {\r
846         return this.moveTo(this.viewEnd.add(Date.DAY, 1));\r
847     },\r
848 \r
849     <div id="method-Ext.calendar.CalendarView-movePrev"></div>/**\r
850      * Updates the view to the previous consecutive date(s)\r
851      */\r
852     movePrev: function(noRefresh) {\r
853         var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd) + 1;\r
854         return this.moveDays( - days, noRefresh);\r
855     },\r
856 \r
857     <div id="method-Ext.calendar.CalendarView-moveMonths"></div>/**\r
858      * Shifts the view by the passed number of months relative to the currently set date\r
859      * @param {Number} value The number of months (positive or negative) by which to shift the view\r
860      */\r
861     moveMonths: function(value, noRefresh) {\r
862         return this.moveTo(this.startDate.add(Date.MONTH, value), noRefresh);\r
863     },\r
864 \r
865     <div id="method-Ext.calendar.CalendarView-moveWeeks"></div>/**\r
866      * Shifts the view by the passed number of weeks relative to the currently set date\r
867      * @param {Number} value The number of weeks (positive or negative) by which to shift the view\r
868      */\r
869     moveWeeks: function(value, noRefresh) {\r
870         return this.moveTo(this.startDate.add(Date.DAY, value * 7), noRefresh);\r
871     },\r
872 \r
873     <div id="method-Ext.calendar.CalendarView-moveDays"></div>/**\r
874      * Shifts the view by the passed number of days relative to the currently set date\r
875      * @param {Number} value The number of days (positive or negative) by which to shift the view\r
876      */\r
877     moveDays: function(value, noRefresh) {\r
878         return this.moveTo(this.startDate.add(Date.DAY, value), noRefresh);\r
879     },\r
880 \r
881     <div id="method-Ext.calendar.CalendarView-moveToday"></div>/**\r
882      * Updates the view to show today\r
883      */\r
884     moveToday: function(noRefresh) {\r
885         return this.moveTo(new Date(), noRefresh);\r
886     },\r
887 \r
888     <div id="method-Ext.calendar.CalendarView-setStore"></div>/**\r
889      * Sets the event store used by the calendar to display {@link Ext.calendar.EventRecord events}.\r
890      * @param {Ext.data.Store} store\r
891      */\r
892     setStore: function(store, initial) {\r
893         if (!initial && this.store) {\r
894             this.store.un("datachanged", this.onDataChanged, this);\r
895             this.store.un("add", this.onAdd, this);\r
896             this.store.un("remove", this.onRemove, this);\r
897             this.store.un("update", this.onUpdate, this);\r
898             this.store.un("clear", this.refresh, this);\r
899         }\r
900         if (store) {\r
901             store.on("datachanged", this.onDataChanged, this);\r
902             store.on("add", this.onAdd, this);\r
903             store.on("remove", this.onRemove, this);\r
904             store.on("update", this.onUpdate, this);\r
905             store.on("clear", this.refresh, this);\r
906         }\r
907         this.store = store;\r
908         if (store && store.getCount() > 0) {\r
909             this.refresh();\r
910         }\r
911     },\r
912 \r
913     getEventRecord: function(id) {\r
914         var idx = this.store.find(Ext.calendar.EventMappings.EventId.name, id);\r
915         return this.store.getAt(idx);\r
916     },\r
917 \r
918     getEventRecordFromEl: function(el) {\r
919         return this.getEventRecord(this.getEventIdFromEl(el));\r
920     },\r
921 \r
922     // private\r
923     getParams: function() {\r
924         return {\r
925             viewStart: this.viewStart,\r
926             viewEnd: this.viewEnd,\r
927             startDate: this.startDate,\r
928             dayCount: this.dayCount,\r
929             weekCount: this.weekCount,\r
930             title: this.getTitle()\r
931         };\r
932     },\r
933 \r
934     getTitle: function() {\r
935         return this.startDate.format('F Y');\r
936     },\r
937 \r
938     /*\r
939      * Shared click handling.  Each specific view also provides view-specific\r
940      * click handling that calls this first.  This method returns true if it\r
941      * can handle the click (and so the subclass should ignore it) else false.\r
942      */\r
943     onClick: function(e, t) {\r
944         var el = e.getTarget(this.eventSelector, 5);\r
945         if (el) {\r
946             var id = this.getEventIdFromEl(el);\r
947             this.fireEvent('eventclick', this, this.getEventRecord(id), el);\r
948             return true;\r
949         }\r
950     },\r
951 \r
952     // private\r
953     onMouseOver: function(e, t) {\r
954         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
955             if (!this.handleEventMouseEvent(e, t, 'over')) {\r
956                 this.handleDayMouseEvent(e, t, 'over');\r
957             }\r
958         }\r
959     },\r
960 \r
961     // private\r
962     onMouseOut: function(e, t) {\r
963         if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {\r
964             if (!this.handleEventMouseEvent(e, t, 'out')) {\r
965                 this.handleDayMouseEvent(e, t, 'out');\r
966             }\r
967         }\r
968     },\r
969 \r
970     // private\r
971     handleEventMouseEvent: function(e, t, type) {\r
972         var el = e.getTarget(this.eventSelector, 5, true),\r
973             rel,\r
974             els,\r
975             evtId;\r
976         if (el) {\r
977             rel = Ext.get(e.getRelatedTarget());\r
978             if (el == rel || el.contains(rel)) {\r
979                 return true;\r
980             }\r
981 \r
982             evtId = this.getEventIdFromEl(el);\r
983 \r
984             if (this.eventOverClass != '') {\r
985                 els = this.getEventEls(evtId);\r
986                 els[type == 'over' ? 'addClass': 'removeClass'](this.eventOverClass);\r
987             }\r
988             this.fireEvent('event' + type, this, this.getEventRecord(evtId), el);\r
989             return true;\r
990         }\r
991         return false;\r
992     },\r
993 \r
994     // private\r
995     getDateFromId: function(id, delim) {\r
996         var parts = id.split(delim);\r
997         return parts[parts.length - 1];\r
998     },\r
999 \r
1000     // private\r
1001     handleDayMouseEvent: function(e, t, type) {\r
1002         t = e.getTarget('td', 3);\r
1003         if (t) {\r
1004             if (t.id && t.id.indexOf(this.dayElIdDelimiter) > -1) {\r
1005                 var dt = this.getDateFromId(t.id, this.dayElIdDelimiter),\r
1006                 rel = Ext.get(e.getRelatedTarget()),\r
1007                 relTD,\r
1008                 relDate;\r
1009 \r
1010                 if (rel) {\r
1011                     relTD = rel.is('td') ? rel: rel.up('td', 3);\r
1012                     relDate = relTD && relTD.id ? this.getDateFromId(relTD.id, this.dayElIdDelimiter) : '';\r
1013                 }\r
1014                 if (!rel || dt != relDate) {\r
1015                     var el = this.getDayEl(dt);\r
1016                     if (el && this.dayOverClass != '') {\r
1017                         el[type == 'over' ? 'addClass': 'removeClass'](this.dayOverClass);\r
1018                     }\r
1019                     this.fireEvent('day' + type, this, Date.parseDate(dt, "Ymd"), el);\r
1020                 }\r
1021             }\r
1022         }\r
1023     },\r
1024 \r
1025     // private\r
1026     renderItems: function() {\r
1027         throw 'This method must be implemented by a subclass';\r
1028     }\r
1029 });</pre>    
1030 </body>
1031 </html>