Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / src / widgets / DatePicker.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.DatePicker\r
9  * @extends Ext.Component\r
10  * Simple date picker class.\r
11  * @constructor\r
12  * Create a new DatePicker\r
13  * @param {Object} config The config object\r
14  * @xtype datepicker\r
15  */\r
16 Ext.DatePicker = Ext.extend(Ext.BoxComponent, {\r
17     /**\r
18      * @cfg {String} todayText\r
19      * The text to display on the button that selects the current date (defaults to <tt>'Today'</tt>)\r
20      */\r
21     todayText : 'Today',\r
22     /**\r
23      * @cfg {String} okText\r
24      * The text to display on the ok button (defaults to <tt>'&#160;OK&#160;'</tt> to give the user extra clicking room)\r
25      */\r
26     okText : '&#160;OK&#160;',\r
27     /**\r
28      * @cfg {String} cancelText\r
29      * The text to display on the cancel button (defaults to <tt>'Cancel'</tt>)\r
30      */\r
31     cancelText : 'Cancel',\r
32     /**\r
33      * @cfg {String} todayTip\r
34      * The tooltip to display for the button that selects the current date (defaults to <tt>'{current date} (Spacebar)'</tt>)\r
35      */\r
36     todayTip : '{0} (Spacebar)',\r
37     /**\r
38      * @cfg {String} minText\r
39      * The error text to display if the minDate validation fails (defaults to <tt>'This date is before the minimum date'</tt>)\r
40      */\r
41     minText : 'This date is before the minimum date',\r
42     /**\r
43      * @cfg {String} maxText\r
44      * The error text to display if the maxDate validation fails (defaults to <tt>'This date is after the maximum date'</tt>)\r
45      */\r
46     maxText : 'This date is after the maximum date',\r
47     /**\r
48      * @cfg {String} format\r
49      * The default date format string which can be overriden for localization support.  The format must be\r
50      * valid according to {@link Date#parseDate} (defaults to <tt>'m/d/y'</tt>).\r
51      */\r
52     format : 'm/d/y',\r
53     /**\r
54      * @cfg {String} disabledDaysText\r
55      * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)\r
56      */\r
57     disabledDaysText : 'Disabled',\r
58     /**\r
59      * @cfg {String} disabledDatesText\r
60      * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)\r
61      */\r
62     disabledDatesText : 'Disabled',\r
63     /**\r
64      * @cfg {Array} monthNames\r
65      * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)\r
66      */\r
67     monthNames : Date.monthNames,\r
68     /**\r
69      * @cfg {Array} dayNames\r
70      * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)\r
71      */\r
72     dayNames : Date.dayNames,\r
73     /**\r
74      * @cfg {String} nextText\r
75      * The next month navigation button tooltip (defaults to <tt>'Next Month (Control+Right)'</tt>)\r
76      */\r
77     nextText : 'Next Month (Control+Right)',\r
78     /**\r
79      * @cfg {String} prevText\r
80      * The previous month navigation button tooltip (defaults to <tt>'Previous Month (Control+Left)'</tt>)\r
81      */\r
82     prevText : 'Previous Month (Control+Left)',\r
83     /**\r
84      * @cfg {String} monthYearText\r
85      * The header month selector tooltip (defaults to <tt>'Choose a month (Control+Up/Down to move years)'</tt>)\r
86      */\r
87     monthYearText : 'Choose a month (Control+Up/Down to move years)',\r
88     /**\r
89      * @cfg {Number} startDay\r
90      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)\r
91      */\r
92     startDay : 0,\r
93     /**\r
94      * @cfg {Boolean} showToday\r
95      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar\r
96      * that selects the current date (defaults to <tt>true</tt>).\r
97      */\r
98     showToday : true,\r
99     /**\r
100      * @cfg {Date} minDate\r
101      * Minimum allowable date (JavaScript date object, defaults to null)\r
102      */\r
103     /**\r
104      * @cfg {Date} maxDate\r
105      * Maximum allowable date (JavaScript date object, defaults to null)\r
106      */\r
107     /**\r
108      * @cfg {Array} disabledDays\r
109      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).\r
110      */\r
111     /**\r
112      * @cfg {RegExp} disabledDatesRE\r
113      * JavaScript regular expression used to disable a pattern of dates (defaults to null).  The {@link #disabledDates}\r
114      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the\r
115      * disabledDates value.\r
116      */\r
117     /**\r
118      * @cfg {Array} disabledDates\r
119      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular\r
120      * expression so they are very powerful. Some examples:\r
121      * <ul>\r
122      * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>\r
123      * <li>['03/08', '09/16'] would disable those days for every year</li>\r
124      * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>\r
125      * <li>['03/../2006'] would disable every day in March 2006</li>\r
126      * <li>['^03'] would disable every day in every March</li>\r
127      * </ul>\r
128      * Note that the format of the dates included in the array should exactly match the {@link #format} config.\r
129      * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to\r
130      * escape the dot when restricting dates. For example: ['03\\.08\\.03'].\r
131      */\r
132 \r
133     // private\r
134     initComponent : function(){\r
135         Ext.DatePicker.superclass.initComponent.call(this);\r
136 \r
137         this.value = this.value ?\r
138                  this.value.clearTime() : new Date().clearTime();\r
139 \r
140         this.addEvents(\r
141             /**\r
142              * @event select\r
143              * Fires when a date is selected\r
144              * @param {DatePicker} this\r
145              * @param {Date} date The selected date\r
146              */\r
147             'select'\r
148         );\r
149 \r
150         if(this.handler){\r
151             this.on('select', this.handler,  this.scope || this);\r
152         }\r
153 \r
154         this.initDisabledDays();\r
155     },\r
156 \r
157     // private\r
158     initDisabledDays : function(){\r
159         if(!this.disabledDatesRE && this.disabledDates){\r
160             var dd = this.disabledDates,\r
161                 len = dd.length - 1,\r
162                 re = '(?:';\r
163                 \r
164             Ext.each(dd, function(d, i){\r
165                 re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];\r
166                 if(i != len){\r
167                     re += '|';\r
168                 }\r
169             }, this);\r
170             this.disabledDatesRE = new RegExp(re + ')');\r
171         }\r
172     },\r
173 \r
174     /**\r
175      * Replaces any existing disabled dates with new values and refreshes the DatePicker.\r
176      * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config\r
177      * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.\r
178      */\r
179     setDisabledDates : function(dd){\r
180         if(Ext.isArray(dd)){\r
181             this.disabledDates = dd;\r
182             this.disabledDatesRE = null;\r
183         }else{\r
184             this.disabledDatesRE = dd;\r
185         }\r
186         this.initDisabledDays();\r
187         this.update(this.value, true);\r
188     },\r
189 \r
190     /**\r
191      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.\r
192      * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config\r
193      * for details on supported values.\r
194      */\r
195     setDisabledDays : function(dd){\r
196         this.disabledDays = dd;\r
197         this.update(this.value, true);\r
198     },\r
199 \r
200     /**\r
201      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.\r
202      * @param {Date} value The minimum date that can be selected\r
203      */\r
204     setMinDate : function(dt){\r
205         this.minDate = dt;\r
206         this.update(this.value, true);\r
207     },\r
208 \r
209     /**\r
210      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.\r
211      * @param {Date} value The maximum date that can be selected\r
212      */\r
213     setMaxDate : function(dt){\r
214         this.maxDate = dt;\r
215         this.update(this.value, true);\r
216     },\r
217 \r
218     /**\r
219      * Sets the value of the date field\r
220      * @param {Date} value The date to set\r
221      */\r
222     setValue : function(value){\r
223         var old = this.value;\r
224         this.value = value.clearTime(true);\r
225         if(this.el){\r
226             this.update(this.value);\r
227         }\r
228     },\r
229 \r
230     /**\r
231      * Gets the current selected value of the date field\r
232      * @return {Date} The selected date\r
233      */\r
234     getValue : function(){\r
235         return this.value;\r
236     },\r
237 \r
238     // private\r
239     focus : function(){\r
240         if(this.el){\r
241             this.update(this.activeDate);\r
242         }\r
243     },\r
244     \r
245     // private\r
246     onEnable: function(initial){\r
247         Ext.DatePicker.superclass.onEnable.call(this);    \r
248         this.doDisabled(false);\r
249         this.update(initial ? this.value : this.activeDate);\r
250         if(Ext.isIE){\r
251             this.el.repaint();\r
252         }\r
253         \r
254     },\r
255     \r
256     // private\r
257     onDisable: function(){\r
258         Ext.DatePicker.superclass.onDisable.call(this);   \r
259         this.doDisabled(true);\r
260         if(Ext.isIE && !Ext.isIE8){\r
261             /* Really strange problem in IE6/7, when disabled, have to explicitly\r
262              * repaint each of the nodes to get them to display correctly, simply\r
263              * calling repaint on the main element doesn't appear to be enough.\r
264              */\r
265              Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){\r
266                  Ext.fly(el).repaint();\r
267              });\r
268         }\r
269     },\r
270     \r
271     // private\r
272     doDisabled: function(disabled){\r
273         this.keyNav.setDisabled(disabled);\r
274         this.prevRepeater.setDisabled(disabled);\r
275         this.nextRepeater.setDisabled(disabled);\r
276         if(this.showToday){\r
277             this.todayKeyListener.setDisabled(disabled);\r
278             this.todayBtn.setDisabled(disabled);\r
279         }\r
280     },\r
281 \r
282     // private\r
283     onRender : function(container, position){\r
284         var m = [\r
285              '<table cellspacing="0">',\r
286                 '<tr><td class="x-date-left"><a href="#" title="', this.prevText ,'">&#160;</a></td><td class="x-date-middle" align="center"></td><td class="x-date-right"><a href="#" title="', this.nextText ,'">&#160;</a></td></tr>',\r
287                 '<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'],\r
288                 dn = this.dayNames,\r
289                 i;\r
290         for(i = 0; i < 7; i++){\r
291             var d = this.startDay+i;\r
292             if(d > 6){\r
293                 d = d-7;\r
294             }\r
295             m.push('<th><span>', dn[d].substr(0,1), '</span></th>');\r
296         }\r
297         m[m.length] = '</tr></thead><tbody><tr>';\r
298         for(i = 0; i < 42; i++) {\r
299             if(i % 7 === 0 && i !== 0){\r
300                 m[m.length] = '</tr><tr>';\r
301             }\r
302             m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';\r
303         }\r
304         m.push('</tr></tbody></table></td></tr>',\r
305                 this.showToday ? '<tr><td colspan="3" class="x-date-bottom" align="center"></td></tr>' : '',\r
306                 '</table><div class="x-date-mp"></div>');\r
307 \r
308         var el = document.createElement('div');\r
309         el.className = 'x-date-picker';\r
310         el.innerHTML = m.join('');\r
311 \r
312         container.dom.insertBefore(el, position);\r
313 \r
314         this.el = Ext.get(el);\r
315         this.eventEl = Ext.get(el.firstChild);\r
316 \r
317         this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), {\r
318             handler: this.showPrevMonth,\r
319             scope: this,\r
320             preventDefault:true,\r
321             stopDefault:true\r
322         });\r
323 \r
324         this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), {\r
325             handler: this.showNextMonth,\r
326             scope: this,\r
327             preventDefault:true,\r
328             stopDefault:true\r
329         });\r
330 \r
331         this.monthPicker = this.el.down('div.x-date-mp');\r
332         this.monthPicker.enableDisplayMode('block');\r
333 \r
334         this.keyNav = new Ext.KeyNav(this.eventEl, {\r
335             'left' : function(e){\r
336                 if(e.ctrlKey){\r
337                     this.showPrevMonth();\r
338                 }else{\r
339                     this.update(this.activeDate.add('d', -1));    \r
340                 }\r
341             },\r
342 \r
343             'right' : function(e){\r
344                 if(e.ctrlKey){\r
345                     this.showNextMonth();\r
346                 }else{\r
347                     this.update(this.activeDate.add('d', 1));    \r
348                 }\r
349             },\r
350 \r
351             'up' : function(e){\r
352                 if(e.ctrlKey){\r
353                     this.showNextYear();\r
354                 }else{\r
355                     this.update(this.activeDate.add('d', -7));\r
356                 }\r
357             },\r
358 \r
359             'down' : function(e){\r
360                 if(e.ctrlKey){\r
361                     this.showPrevYear();\r
362                 }else{\r
363                     this.update(this.activeDate.add('d', 7));\r
364                 }\r
365             },\r
366 \r
367             'pageUp' : function(e){\r
368                 this.showNextMonth();\r
369             },\r
370 \r
371             'pageDown' : function(e){\r
372                 this.showPrevMonth();\r
373             },\r
374 \r
375             'enter' : function(e){\r
376                 e.stopPropagation();\r
377                 return true;\r
378             },\r
379 \r
380             scope : this\r
381         });\r
382 \r
383         this.el.unselectable();\r
384 \r
385         this.cells = this.el.select('table.x-date-inner tbody td');\r
386         this.textNodes = this.el.query('table.x-date-inner tbody span');\r
387 \r
388         this.mbtn = new Ext.Button({\r
389             text: '&#160;',\r
390             tooltip: this.monthYearText,\r
391             renderTo: this.el.child('td.x-date-middle', true)\r
392         });\r
393         this.mbtn.el.child('em').addClass('x-btn-arrow');\r
394 \r
395         if(this.showToday){\r
396             this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday,  this);\r
397             var today = (new Date()).dateFormat(this.format);\r
398             this.todayBtn = new Ext.Button({\r
399                 renderTo: this.el.child('td.x-date-bottom', true),\r
400                 text: String.format(this.todayText, today),\r
401                 tooltip: String.format(this.todayTip, today),\r
402                 handler: this.selectToday,\r
403                 scope: this\r
404             });\r
405         }\r
406         this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this);\r
407         this.mon(this.eventEl, 'click', this.handleDateClick,  this, {delegate: 'a.x-date-date'});\r
408         this.mon(this.mbtn, 'click', this.showMonthPicker, this);\r
409         this.onEnable(true);\r
410     },\r
411 \r
412     // private\r
413     createMonthPicker : function(){\r
414         if(!this.monthPicker.dom.firstChild){\r
415             var buf = ['<table border="0" cellspacing="0">'];\r
416             for(var i = 0; i < 6; i++){\r
417                 buf.push(\r
418                     '<tr><td class="x-date-mp-month"><a href="#">', Date.getShortMonthName(i), '</a></td>',\r
419                     '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', Date.getShortMonthName(i + 6), '</a></td>',\r
420                     i === 0 ?\r
421                     '<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>' :\r
422                     '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'\r
423                 );\r
424             }\r
425             buf.push(\r
426                 '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',\r
427                     this.okText,\r
428                     '</button><button type="button" class="x-date-mp-cancel">',\r
429                     this.cancelText,\r
430                     '</button></td></tr>',\r
431                 '</table>'\r
432             );\r
433             this.monthPicker.update(buf.join(''));\r
434 \r
435             this.mon(this.monthPicker, 'click', this.onMonthClick, this);\r
436             this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this);\r
437 \r
438             this.mpMonths = this.monthPicker.select('td.x-date-mp-month');\r
439             this.mpYears = this.monthPicker.select('td.x-date-mp-year');\r
440 \r
441             this.mpMonths.each(function(m, a, i){\r
442                 i += 1;\r
443                 if((i%2) === 0){\r
444                     m.dom.xmonth = 5 + Math.round(i * 0.5);\r
445                 }else{\r
446                     m.dom.xmonth = Math.round((i-1) * 0.5);\r
447                 }\r
448             });\r
449         }\r
450     },\r
451 \r
452     // private\r
453     showMonthPicker : function(){\r
454         if(!this.disabled){\r
455             this.createMonthPicker();\r
456             var size = this.el.getSize();\r
457             this.monthPicker.setSize(size);\r
458             this.monthPicker.child('table').setSize(size);\r
459 \r
460             this.mpSelMonth = (this.activeDate || this.value).getMonth();\r
461             this.updateMPMonth(this.mpSelMonth);\r
462             this.mpSelYear = (this.activeDate || this.value).getFullYear();\r
463             this.updateMPYear(this.mpSelYear);\r
464 \r
465             this.monthPicker.slideIn('t', {duration:0.2});\r
466         }\r
467     },\r
468 \r
469     // private\r
470     updateMPYear : function(y){\r
471         this.mpyear = y;\r
472         var ys = this.mpYears.elements;\r
473         for(var i = 1; i <= 10; i++){\r
474             var td = ys[i-1], y2;\r
475             if((i%2) === 0){\r
476                 y2 = y + Math.round(i * 0.5);\r
477                 td.firstChild.innerHTML = y2;\r
478                 td.xyear = y2;\r
479             }else{\r
480                 y2 = y - (5-Math.round(i * 0.5));\r
481                 td.firstChild.innerHTML = y2;\r
482                 td.xyear = y2;\r
483             }\r
484             this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');\r
485         }\r
486     },\r
487 \r
488     // private\r
489     updateMPMonth : function(sm){\r
490         this.mpMonths.each(function(m, a, i){\r
491             m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');\r
492         });\r
493     },\r
494 \r
495     // private\r
496     selectMPMonth : function(m){\r
497 \r
498     },\r
499 \r
500     // private\r
501     onMonthClick : function(e, t){\r
502         e.stopEvent();\r
503         var el = new Ext.Element(t), pn;\r
504         if(el.is('button.x-date-mp-cancel')){\r
505             this.hideMonthPicker();\r
506         }\r
507         else if(el.is('button.x-date-mp-ok')){\r
508             var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate());\r
509             if(d.getMonth() != this.mpSelMonth){\r
510                 // 'fix' the JS rolling date conversion if needed\r
511                 d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth();\r
512             }\r
513             this.update(d);\r
514             this.hideMonthPicker();\r
515         }\r
516         else if((pn = el.up('td.x-date-mp-month', 2))){\r
517             this.mpMonths.removeClass('x-date-mp-sel');\r
518             pn.addClass('x-date-mp-sel');\r
519             this.mpSelMonth = pn.dom.xmonth;\r
520         }\r
521         else if((pn = el.up('td.x-date-mp-year', 2))){\r
522             this.mpYears.removeClass('x-date-mp-sel');\r
523             pn.addClass('x-date-mp-sel');\r
524             this.mpSelYear = pn.dom.xyear;\r
525         }\r
526         else if(el.is('a.x-date-mp-prev')){\r
527             this.updateMPYear(this.mpyear-10);\r
528         }\r
529         else if(el.is('a.x-date-mp-next')){\r
530             this.updateMPYear(this.mpyear+10);\r
531         }\r
532     },\r
533 \r
534     // private\r
535     onMonthDblClick : function(e, t){\r
536         e.stopEvent();\r
537         var el = new Ext.Element(t), pn;\r
538         if((pn = el.up('td.x-date-mp-month', 2))){\r
539             this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));\r
540             this.hideMonthPicker();\r
541         }\r
542         else if((pn = el.up('td.x-date-mp-year', 2))){\r
543             this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));\r
544             this.hideMonthPicker();\r
545         }\r
546     },\r
547 \r
548     // private\r
549     hideMonthPicker : function(disableAnim){\r
550         if(this.monthPicker){\r
551             if(disableAnim === true){\r
552                 this.monthPicker.hide();\r
553             }else{\r
554                 this.monthPicker.slideOut('t', {duration:0.2});\r
555             }\r
556         }\r
557     },\r
558 \r
559     // private\r
560     showPrevMonth : function(e){\r
561         this.update(this.activeDate.add('mo', -1));\r
562     },\r
563 \r
564     // private\r
565     showNextMonth : function(e){\r
566         this.update(this.activeDate.add('mo', 1));\r
567     },\r
568 \r
569     // private\r
570     showPrevYear : function(){\r
571         this.update(this.activeDate.add('y', -1));\r
572     },\r
573 \r
574     // private\r
575     showNextYear : function(){\r
576         this.update(this.activeDate.add('y', 1));\r
577     },\r
578 \r
579     // private\r
580     handleMouseWheel : function(e){\r
581         e.stopEvent();\r
582         if(!this.disabled){\r
583             var delta = e.getWheelDelta();\r
584             if(delta > 0){\r
585                 this.showPrevMonth();\r
586             } else if(delta < 0){\r
587                 this.showNextMonth();\r
588             }\r
589         }\r
590     },\r
591 \r
592     // private\r
593     handleDateClick : function(e, t){\r
594         e.stopEvent();\r
595         if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){\r
596             this.setValue(new Date(t.dateValue));\r
597             this.fireEvent('select', this, this.value);\r
598         }\r
599     },\r
600 \r
601     // private\r
602     selectToday : function(){\r
603         if(this.todayBtn && !this.todayBtn.disabled){\r
604             this.setValue(new Date().clearTime());\r
605             this.fireEvent('select', this, this.value);\r
606         }\r
607     },\r
608 \r
609     // private\r
610     update : function(date, forceRefresh){\r
611         var vd = this.activeDate, vis = this.isVisible();\r
612         this.activeDate = date;\r
613         if(!forceRefresh && vd && this.el){\r
614             var t = date.getTime();\r
615             if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){\r
616                 this.cells.removeClass('x-date-selected');\r
617                 this.cells.each(function(c){\r
618                    if(c.dom.firstChild.dateValue == t){\r
619                        c.addClass('x-date-selected');\r
620                        if(vis){\r
621                            Ext.fly(c.dom.firstChild).focus(50);\r
622                        }\r
623                        return false;\r
624                    }\r
625                 });\r
626                 return;\r
627             }\r
628         }\r
629         var days = date.getDaysInMonth();\r
630         var firstOfMonth = date.getFirstDateOfMonth();\r
631         var startingPos = firstOfMonth.getDay()-this.startDay;\r
632 \r
633         if(startingPos <= this.startDay){\r
634             startingPos += 7;\r
635         }\r
636 \r
637         var pm = date.add('mo', -1);\r
638         var prevStart = pm.getDaysInMonth()-startingPos;\r
639 \r
640         var cells = this.cells.elements;\r
641         var textEls = this.textNodes;\r
642         days += startingPos;\r
643 \r
644         // convert everything to numbers so it's fast\r
645         var day = 86400000;\r
646         var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();\r
647         var today = new Date().clearTime().getTime();\r
648         var sel = date.clearTime().getTime();\r
649         var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;\r
650         var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;\r
651         var ddMatch = this.disabledDatesRE;\r
652         var ddText = this.disabledDatesText;\r
653         var ddays = this.disabledDays ? this.disabledDays.join('') : false;\r
654         var ddaysText = this.disabledDaysText;\r
655         var format = this.format;\r
656 \r
657         if(this.showToday){\r
658             var td = new Date().clearTime();\r
659             var disable = (td < min || td > max ||\r
660                 (ddMatch && format && ddMatch.test(td.dateFormat(format))) ||\r
661                 (ddays && ddays.indexOf(td.getDay()) != -1));\r
662 \r
663             if(!this.disabled){\r
664                 this.todayBtn.setDisabled(disable);\r
665                 this.todayKeyListener[disable ? 'disable' : 'enable']();\r
666             }\r
667         }\r
668 \r
669         var setCellClass = function(cal, cell){\r
670             cell.title = '';\r
671             var t = d.getTime();\r
672             cell.firstChild.dateValue = t;\r
673             if(t == today){\r
674                 cell.className += ' x-date-today';\r
675                 cell.title = cal.todayText;\r
676             }\r
677             if(t == sel){\r
678                 cell.className += ' x-date-selected';\r
679                 if(vis){\r
680                     Ext.fly(cell.firstChild).focus(50);\r
681                 }\r
682             }\r
683             // disabling\r
684             if(t < min) {\r
685                 cell.className = ' x-date-disabled';\r
686                 cell.title = cal.minText;\r
687                 return;\r
688             }\r
689             if(t > max) {\r
690                 cell.className = ' x-date-disabled';\r
691                 cell.title = cal.maxText;\r
692                 return;\r
693             }\r
694             if(ddays){\r
695                 if(ddays.indexOf(d.getDay()) != -1){\r
696                     cell.title = ddaysText;\r
697                     cell.className = ' x-date-disabled';\r
698                 }\r
699             }\r
700             if(ddMatch && format){\r
701                 var fvalue = d.dateFormat(format);\r
702                 if(ddMatch.test(fvalue)){\r
703                     cell.title = ddText.replace('%0', fvalue);\r
704                     cell.className = ' x-date-disabled';\r
705                 }\r
706             }\r
707         };\r
708 \r
709         var i = 0;\r
710         for(; i < startingPos; i++) {\r
711             textEls[i].innerHTML = (++prevStart);\r
712             d.setDate(d.getDate()+1);\r
713             cells[i].className = 'x-date-prevday';\r
714             setCellClass(this, cells[i]);\r
715         }\r
716         for(; i < days; i++){\r
717             var intDay = i - startingPos + 1;\r
718             textEls[i].innerHTML = (intDay);\r
719             d.setDate(d.getDate()+1);\r
720             cells[i].className = 'x-date-active';\r
721             setCellClass(this, cells[i]);\r
722         }\r
723         var extraDays = 0;\r
724         for(; i < 42; i++) {\r
725              textEls[i].innerHTML = (++extraDays);\r
726              d.setDate(d.getDate()+1);\r
727              cells[i].className = 'x-date-nextday';\r
728              setCellClass(this, cells[i]);\r
729         }\r
730 \r
731         this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear());\r
732 \r
733         if(!this.internalRender){\r
734             var main = this.el.dom.firstChild;\r
735             var w = main.offsetWidth;\r
736             this.el.setWidth(w + this.el.getBorderWidth('lr'));\r
737             Ext.fly(main).setWidth(w);\r
738             this.internalRender = true;\r
739             // opera does not respect the auto grow header center column\r
740             // then, after it gets a width opera refuses to recalculate\r
741             // without a second pass\r
742             if(Ext.isOpera && !this.secondPass){\r
743                 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px';\r
744                 this.secondPass = true;\r
745                 this.update.defer(10, this, [date]);\r
746             }\r
747         }\r
748     },\r
749 \r
750     // private\r
751     beforeDestroy : function() {\r
752         if(this.rendered){\r
753             this.keyNav.disable();\r
754             this.keyNav = null;\r
755             Ext.destroy(\r
756                 this.leftClickRpt,\r
757                 this.rightClickRpt,\r
758                 this.monthPicker,\r
759                 this.eventEl,\r
760                 this.mbtn,\r
761                 this.todayBtn\r
762             );\r
763         }\r
764     }\r
765 \r
766     /**\r
767      * @cfg {String} autoEl @hide\r
768      */\r
769 });\r
770 \r
771 Ext.reg('datepicker', Ext.DatePicker);\r