Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / picker / Date.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.picker.Date
17  * @extends Ext.Component
18  * <p>A date picker. This class is used by the {@link Ext.form.field.Date} field to allow browsing and
19  * selection of valid dates in a popup next to the field, but may also be used with other components.</p>
20  * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
21  * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
22  * method.</p>
23  * <p>By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
24  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.</p>
25  * <p>All the string values documented below may be overridden by including an Ext locale file in your page.</p>
26  * <p>Example usage:</p>
27  * <pre><code>new Ext.panel.Panel({
28     title: 'Choose a future date:',
29     width: 200,
30     bodyPadding: 10,
31     renderTo: Ext.getBody(),
32     items: [{
33         xtype: 'datepicker',
34         minDate: new Date(),
35         handler: function(picker, date) {
36             // do something with the selected date
37         }
38     }]
39 });</code></pre>
40  * {@img Ext.picker.Date/Ext.picker.Date.png Ext.picker.Date component}
41  *
42  */
43 Ext.define('Ext.picker.Date', {
44     extend: 'Ext.Component',
45     requires: [
46         'Ext.XTemplate',
47         'Ext.button.Button',
48         'Ext.button.Split',
49         'Ext.util.ClickRepeater',
50         'Ext.util.KeyNav',
51         'Ext.EventObject',
52         'Ext.fx.Manager',
53         'Ext.picker.Month'
54     ],
55     alias: 'widget.datepicker',
56     alternateClassName: 'Ext.DatePicker',
57
58     renderTpl: [
59         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
60             '<div role="presentation" class="{baseCls}-header">',
61                 '<div class="{baseCls}-prev"><a href="#" role="button" title="{prevText}"></a></div>',
62                 '<div class="{baseCls}-month"></div>',
63                 '<div class="{baseCls}-next"><a href="#" role="button" title="{nextText}"></a></div>',
64             '</div>',
65             '<table class="{baseCls}-inner" cellspacing="0" role="presentation">',
66                 '<thead role="presentation"><tr role="presentation">',
67                     '<tpl for="dayNames">',
68                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
69                     '</tpl>',
70                 '</tr></thead>',
71                 '<tbody role="presentation"><tr role="presentation">',
72                     '<tpl for="days">',
73                         '{#:this.isEndOfWeek}',
74                         '<td role="gridcell" id="{[Ext.id()]}">',
75                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
76                                 '<em role="presentation"><span role="presentation"></span></em>',
77                             '</a>',
78                         '</td>',
79                     '</tpl>',
80                 '</tr></tbody>',
81             '</table>',
82             '<tpl if="showToday">',
83                 '<div role="presentation" class="{baseCls}-footer"></div>',
84             '</tpl>',
85         '</div>',
86         {
87             firstInitial: function(value) {
88                 return value.substr(0,1);
89             },
90             isEndOfWeek: function(value) {
91                 // convert from 1 based index to 0 based
92                 // by decrementing value once.
93                 value--;
94                 var end = value % 7 === 0 && value !== 0;
95                 return end ? '</tr><tr role="row">' : '';
96             },
97             longDay: function(value){
98                 return Ext.Date.format(value, this.longDayFormat);
99             }
100         }
101     ],
102
103     ariaTitle: 'Date Picker',
104     /**
105      * @cfg {String} todayText
106      * The text to display on the button that selects the current date (defaults to <code>'Today'</code>)
107      */
108     todayText : 'Today',
109     /**
110      * @cfg {Function} handler
111      * Optional. A function that will handle the select event of this picker.
112      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
113      * <li><code>picker</code> : Ext.picker.Date <div class="sub-desc">This Date picker.</div></li>
114      * <li><code>date</code> : Date <div class="sub-desc">The selected date.</div></li>
115      * </ul></div>
116      */
117     /**
118      * @cfg {Object} scope
119      * The scope (<code><b>this</b></code> reference) in which the <code>{@link #handler}</code>
120      * function will be called.  Defaults to this DatePicker instance.
121      */
122     /**
123      * @cfg {String} todayTip
124      * A string used to format the message for displaying in a tooltip over the button that
125      * selects the current date. Defaults to <code>'{0} (Spacebar)'</code> where
126      * the <code>{0}</code> token is replaced by today's date.
127      */
128     todayTip : '{0} (Spacebar)',
129     /**
130      * @cfg {String} minText
131      * The error text to display if the minDate validation fails (defaults to <code>'This date is before the minimum date'</code>)
132      */
133     minText : 'This date is before the minimum date',
134     /**
135      * @cfg {String} maxText
136      * The error text to display if the maxDate validation fails (defaults to <code>'This date is after the maximum date'</code>)
137      */
138     maxText : 'This date is after the maximum date',
139     /**
140      * @cfg {String} format
141      * The default date format string which can be overriden for localization support.  The format must be
142      * valid according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
143      */
144     /**
145      * @cfg {String} disabledDaysText
146      * The tooltip to display when the date falls on a disabled day (defaults to <code>'Disabled'</code>)
147      */
148     disabledDaysText : 'Disabled',
149     /**
150      * @cfg {String} disabledDatesText
151      * The tooltip text to display when the date falls on a disabled date (defaults to <code>'Disabled'</code>)
152      */
153     disabledDatesText : 'Disabled',
154     /**
155      * @cfg {Array} monthNames
156      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
157      */
158     /**
159      * @cfg {Array} dayNames
160      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
161      */
162     /**
163      * @cfg {String} nextText
164      * The next month navigation button tooltip (defaults to <code>'Next Month (Control+Right)'</code>)
165      */
166     nextText : 'Next Month (Control+Right)',
167     /**
168      * @cfg {String} prevText
169      * The previous month navigation button tooltip (defaults to <code>'Previous Month (Control+Left)'</code>)
170      */
171     prevText : 'Previous Month (Control+Left)',
172     /**
173      * @cfg {String} monthYearText
174      * The header month selector tooltip (defaults to <code>'Choose a month (Control+Up/Down to move years)'</code>)
175      */
176     monthYearText : 'Choose a month (Control+Up/Down to move years)',
177     /**
178      * @cfg {Number} startDay
179      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
180      */
181     startDay : 0,
182     /**
183      * @cfg {Boolean} showToday
184      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
185      * that selects the current date (defaults to <code>true</code>).
186      */
187     showToday : true,
188     /**
189      * @cfg {Date} minDate
190      * Minimum allowable date (JavaScript date object, defaults to null)
191      */
192     /**
193      * @cfg {Date} maxDate
194      * Maximum allowable date (JavaScript date object, defaults to null)
195      */
196     /**
197      * @cfg {Array} disabledDays
198      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
199      */
200     /**
201      * @cfg {RegExp} disabledDatesRE
202      * JavaScript regular expression used to disable a pattern of dates (defaults to null).  The {@link #disabledDates}
203      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
204      * disabledDates value.
205      */
206     /**
207      * @cfg {Array} disabledDates
208      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
209      * expression so they are very powerful. Some examples:
210      * <ul>
211      * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
212      * <li>['03/08', '09/16'] would disable those days for every year</li>
213      * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
214      * <li>['03/../2006'] would disable every day in March 2006</li>
215      * <li>['^03'] would disable every day in every March</li>
216      * </ul>
217      * Note that the format of the dates included in the array should exactly match the {@link #format} config.
218      * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
219      * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
220      */
221
222     /**
223      * @cfg {Boolean} disableAnim True to disable animations when showing the month picker. Defaults to <tt>false</tt>.
224      */
225     disableAnim: true,
226
227     /**
228      * @cfg {String} baseCls
229      * The base CSS class to apply to this components element (defaults to <tt>'x-datepicker'</tt>).
230      */
231     baseCls: Ext.baseCSSPrefix + 'datepicker',
232
233     /**
234      * @cfg {String} selectedCls
235      * The class to apply to the selected cell. Defaults to <tt>'x-datepicker-selected'</tt>
236      */
237
238     /**
239      * @cfg {String} disabledCellCls
240      * The class to apply to disabled cells. Defaults to <tt>'x-datepicker-disabled'</tt>
241      */
242
243     /**
244      * @cfg {String} longDayFormat
245      * The format for displaying a date in a longer format. Defaults to <tt>'F d, Y'</tt>
246      */
247     longDayFormat: 'F d, Y',
248
249     /**
250      * @cfg {Object} keyNavConfig Specifies optional custom key event handlers for the {@link Ext.util.KeyNav}
251      * attached to this date picker. Must conform to the config format recognized by the {@link Ext.util.KeyNav}
252      * constructor. Handlers specified in this object will replace default handlers of the same name.
253      */
254
255     /**
256      * @cfg {Boolean} focusOnShow
257      * True to automatically focus the picker on show. Defaults to <tt>false</tt>.
258      */
259     focusOnShow: false,
260
261     // private
262     // Set by other components to stop the picker focus being updated when the value changes.
263     focusOnSelect: true,
264
265     width: 178,
266
267     // default value used to initialise each date in the DatePicker
268     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
269     initHour: 12, // 24-hour format
270
271     numDays: 42,
272
273     // private, inherit docs
274     initComponent : function() {
275         var me = this,
276             clearTime = Ext.Date.clearTime;
277
278         me.selectedCls = me.baseCls + '-selected';
279         me.disabledCellCls = me.baseCls + '-disabled';
280         me.prevCls = me.baseCls + '-prevday';
281         me.activeCls = me.baseCls + '-active';
282         me.nextCls = me.baseCls + '-prevday';
283         me.todayCls = me.baseCls + '-today';
284         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
285         this.callParent();
286
287         me.value = me.value ?
288                  clearTime(me.value, true) : clearTime(new Date());
289
290         me.addEvents(
291             /**
292              * @event select
293              * Fires when a date is selected
294              * @param {DatePicker} this DatePicker
295              * @param {Date} date The selected date
296              */
297             'select'
298         );
299
300         me.initDisabledDays();
301     },
302
303     // private, inherit docs
304     onRender : function(container, position){
305         /*
306          * days array for looping through 6 full weeks (6 weeks * 7 days)
307          * Note that we explicitly force the size here so the template creates
308          * all the appropriate cells.
309          */
310
311         var me = this,
312             days = new Array(me.numDays),
313             today = Ext.Date.format(new Date(), me.format);
314
315         Ext.applyIf(me, {
316             renderData: {},
317             renderSelectors: {}
318         });
319
320         Ext.apply(me.renderData, {
321             dayNames: me.dayNames,
322             ariaTitle: me.ariaTitle,
323             value: me.value,
324             showToday: me.showToday,
325             prevText: me.prevText,
326             nextText: me.nextText,
327             days: days
328         });
329         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
330
331         Ext.apply(me.renderSelectors, {
332             eventEl: 'table.' + me.baseCls + '-inner',
333             prevEl: '.' + me.baseCls + '-prev a',
334             nextEl: '.' + me.baseCls + '-next a',
335             middleBtnEl: '.' + me.baseCls + '-month',
336             footerEl: '.' + me.baseCls + '-footer'
337         });
338
339         this.callParent(arguments);
340         me.el.unselectable();
341
342         me.cells = me.eventEl.select('tbody td');
343         me.textNodes = me.eventEl.query('tbody td span');
344
345         me.monthBtn = Ext.create('Ext.button.Split', {
346             text: '',
347             tooltip: me.monthYearText,
348             renderTo: me.middleBtnEl
349         });
350         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
351
352
353         me.todayBtn = Ext.create('Ext.button.Button', {
354             renderTo: me.footerEl,
355             text: Ext.String.format(me.todayText, today),
356             tooltip: Ext.String.format(me.todayTip, today),
357             handler: me.selectToday,
358             scope: me
359         });
360     },
361
362     // private, inherit docs
363     initEvents: function(){
364         var me = this,
365             eDate = Ext.Date,
366             day = eDate.DAY;
367
368         this.callParent();
369
370         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
371             handler: me.showPrevMonth,
372             scope: me,
373             preventDefault: true,
374             stopDefault: true
375         });
376
377         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
378             handler: me.showNextMonth,
379             scope: me,
380             preventDefault:true,
381             stopDefault:true
382         });
383
384         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
385             scope: me,
386             'left' : function(e){
387                 if(e.ctrlKey){
388                     me.showPrevMonth();
389                 }else{
390                     me.update(eDate.add(me.activeDate, day, -1));
391                 }
392             },
393
394             'right' : function(e){
395                 if(e.ctrlKey){
396                     me.showNextMonth();
397                 }else{
398                     me.update(eDate.add(me.activeDate, day, 1));
399                 }
400             },
401
402             'up' : function(e){
403                 if(e.ctrlKey){
404                     me.showNextYear();
405                 }else{
406                     me.update(eDate.add(me.activeDate, day, -7));
407                 }
408             },
409
410             'down' : function(e){
411                 if(e.ctrlKey){
412                     me.showPrevYear();
413                 }else{
414                     me.update(eDate.add(me.activeDate, day, 7));
415                 }
416             },
417             'pageUp' : me.showNextMonth,
418             'pageDown' : me.showPrevMonth,
419             'enter' : function(e){
420                 e.stopPropagation();
421                 return true;
422             }
423         }, me.keyNavConfig));
424
425         if(me.showToday){
426             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
427         }
428         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
429         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
430         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
431         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
432         me.update(me.value);
433     },
434
435     /**
436      * Setup the disabled dates regex based on config options
437      * @private
438      */
439     initDisabledDays : function(){
440         var me = this,
441             dd = me.disabledDates,
442             re = '(?:',
443             len;
444
445         if(!me.disabledDatesRE && dd){
446                 len = dd.length - 1;
447
448             Ext.each(dd, function(d, i){
449                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
450                 if(i != len){
451                     re += '|';
452                 }
453             }, me);
454             me.disabledDatesRE = new RegExp(re + ')');
455         }
456     },
457
458     /**
459      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
460      * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
461      * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
462      * @return {Ext.picker.Date} this
463      */
464     setDisabledDates : function(dd){
465         var me = this;
466
467         if(Ext.isArray(dd)){
468             me.disabledDates = dd;
469             me.disabledDatesRE = null;
470         }else{
471             me.disabledDatesRE = dd;
472         }
473         me.initDisabledDays();
474         me.update(me.value, true);
475         return me;
476     },
477
478     /**
479      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
480      * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
481      * for details on supported values.
482      * @return {Ext.picker.Date} this
483      */
484     setDisabledDays : function(dd){
485         this.disabledDays = dd;
486         return this.update(this.value, true);
487     },
488
489     /**
490      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
491      * @param {Date} value The minimum date that can be selected
492      * @return {Ext.picker.Date} this
493      */
494     setMinDate : function(dt){
495         this.minDate = dt;
496         return this.update(this.value, true);
497     },
498
499     /**
500      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
501      * @param {Date} value The maximum date that can be selected
502      * @return {Ext.picker.Date} this
503      */
504     setMaxDate : function(dt){
505         this.maxDate = dt;
506         return this.update(this.value, true);
507     },
508
509     /**
510      * Sets the value of the date field
511      * @param {Date} value The date to set
512      * @return {Ext.picker.Date} this
513      */
514     setValue : function(value){
515         this.value = Ext.Date.clearTime(value, true);
516         return this.update(this.value);
517     },
518
519     /**
520      * Gets the current selected value of the date field
521      * @return {Date} The selected date
522      */
523     getValue : function(){
524         return this.value;
525     },
526
527     // private
528     focus : function(){
529         this.update(this.activeDate);
530     },
531
532     // private, inherit docs
533     onEnable: function(){
534         this.callParent();
535         this.setDisabledStatus(false);
536         this.update(this.activeDate);
537
538     },
539
540     // private, inherit docs
541     onDisable : function(){
542         this.callParent();
543         this.setDisabledStatus(true);
544     },
545
546     /**
547      * Set the disabled state of various internal components
548      * @private
549      * @param {Boolean} disabled
550      */
551     setDisabledStatus : function(disabled){
552         var me = this;
553
554         me.keyNav.setDisabled(disabled);
555         me.prevRepeater.setDisabled(disabled);
556         me.nextRepeater.setDisabled(disabled);
557         if (me.showToday) {
558             me.todayKeyListener.setDisabled(disabled);
559             me.todayBtn.setDisabled(disabled);
560         }
561     },
562
563     /**
564      * Get the current active date.
565      * @private
566      * @return {Date} The active date
567      */
568     getActive: function(){
569         return this.activeDate || me.value;
570     },
571
572     /**
573      * Run any animation required to hide/show the month picker.
574      * @private
575      * @param {Boolean} isHide True if it's a hide operation
576      */
577     runAnimation: function(isHide){
578         var options = {
579                 target: this.monthPicker,
580                 duration: 200
581             };
582
583         Ext.fx.Manager.run();
584         if (isHide) {
585             //TODO: slideout
586         } else {
587             //TODO: slidein
588         }
589         Ext.create('Ext.fx.Anim', options);
590     },
591
592     /**
593      * Hides the month picker, if it's visible.
594      * @return {Ext.picker.Date} this
595      */
596     hideMonthPicker : function(){
597         var me = this,
598             picker = me.monthPicker;
599
600         if (picker) {
601             if (me.disableAnim) {
602                 picker.hide();
603             } else {
604                 this.runAnimation(true);
605             }
606         }
607         return me;
608     },
609
610     /**
611      * Show the month picker
612      * @return {Ext.picker.Date} this
613      */
614     showMonthPicker : function(){
615
616         var me = this,
617             picker,
618             size,
619             top,
620             left;
621
622
623         if (me.rendered && !me.disabled) {
624             size = me.getSize();
625             picker = me.createMonthPicker();
626             picker.show();
627             picker.setSize(size);
628             picker.setValue(me.getActive());
629
630             if (me.disableAnim) {
631                 picker.setPosition(-1, -1);
632             } else {
633                 me.runAnimation(false);
634             }
635         }
636         return me;
637     },
638
639     /**
640      * Create the month picker instance
641      * @private
642      * @return {Ext.picker.Month} picker
643      */
644     createMonthPicker: function(){
645         var me = this,
646             picker = me.monthPicker;
647
648         if (!picker) {
649             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
650                 renderTo: me.el,
651                 floating: true,
652                 shadow: false,
653                 small: me.showToday === false,
654                 listeners: {
655                     scope: me,
656                     cancelclick: me.onCancelClick,
657                     okclick: me.onOkClick,
658                     yeardblclick: me.onOkClick,
659                     monthdblclick: me.onOkClick
660                 }
661             });
662
663             me.on('beforehide', me.hideMonthPicker, me);
664         }
665         return picker;
666     },
667
668     /**
669      * Respond to an ok click on the month picker
670      * @private
671      */
672     onOkClick: function(picker, value){
673         var me = this,
674             month = value[0],
675             year = value[1],
676             date = new Date(year, month, me.getActive().getDate());
677
678         if (date.getMonth() !== month) {
679             // 'fix' the JS rolling date conversion if needed
680             date = new Date(year, month, 1).getLastDateOfMonth();
681         }
682         me.update(date);
683         me.hideMonthPicker();
684     },
685
686     /**
687      * Respond to a cancel click on the month picker
688      * @private
689      */
690     onCancelClick: function(){
691         this.hideMonthPicker();
692     },
693
694     /**
695      * Show the previous month.
696      * @return {Ext.picker.Date} this
697      */
698     showPrevMonth : function(e){
699         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
700     },
701
702     /**
703      * Show the next month.
704      * @return {Ext.picker.Date} this
705      */
706     showNextMonth : function(e){
707         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
708     },
709
710     /**
711      * Show the previous year.
712      * @return {Ext.picker.Date} this
713      */
714     showPrevYear : function(){
715         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
716     },
717
718     /**
719      * Show the next year.
720      * @return {Ext.picker.Date} this
721      */
722     showNextYear : function(){
723         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
724     },
725
726     /**
727      * Respond to the mouse wheel event
728      * @private
729      * @param {Ext.EventObject} e
730      */
731     handleMouseWheel : function(e){
732         e.stopEvent();
733         if(!this.disabled){
734             var delta = e.getWheelDelta();
735             if(delta > 0){
736                 this.showPrevMonth();
737             } else if(delta < 0){
738                 this.showNextMonth();
739             }
740         }
741     },
742
743     /**
744      * Respond to a date being clicked in the picker
745      * @private
746      * @param {Ext.EventObject} e
747      * @param {HTMLElement} t
748      */
749     handleDateClick : function(e, t){
750         var me = this,
751             handler = me.handler;
752
753         e.stopEvent();
754         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
755             me.cancelFocus = me.focusOnSelect === false;
756             me.setValue(new Date(t.dateValue));
757             delete me.cancelFocus;
758             me.fireEvent('select', me, me.value);
759             if (handler) {
760                 handler.call(me.scope || me, me, me.value);
761             }
762             // event handling is turned off on hide
763             // when we are using the picker in a field
764             // therefore onSelect comes AFTER the select
765             // event.
766             me.onSelect();
767         }
768     },
769
770     /**
771      * Perform any post-select actions
772      * @private
773      */
774     onSelect: function() {
775         if (this.hideOnSelect) {
776              this.hide();
777          }
778     },
779
780     /**
781      * Sets the current value to today.
782      * @return {Ext.picker.Date} this
783      */
784     selectToday : function(){
785         var me = this,
786             btn = me.todayBtn,
787             handler = me.handler;
788
789         if(btn && !btn.disabled){
790             me.setValue(Ext.Date.clearTime(new Date()));
791             me.fireEvent('select', me, me.value);
792             if (handler) {
793                 handler.call(me.scope || me, me, me.value);
794             }
795             me.onSelect();
796         }
797         return me;
798     },
799
800     /**
801      * Update the selected cell
802      * @private
803      * @param {Date} date The new date
804      * @param {Date} active The active date
805      */
806     selectedUpdate: function(date, active){
807         var me = this,
808             t = date.getTime(),
809             cells = me.cells,
810             cls = me.selectedCls;
811
812         cells.removeCls(cls);
813         cells.each(function(c){
814             if (c.dom.firstChild.dateValue == t) {
815                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
816                 c.addCls(cls);
817                 if(me.isVisible() && !me.cancelFocus){
818                     Ext.fly(c.dom.firstChild).focus(50);
819                 }
820                 return false;
821             }
822         }, this);
823     },
824
825     /**
826      * Update the contents of the picker for a new month
827      * @private
828      * @param {Date} date The new date
829      * @param {Date} active The active date
830      */
831     fullUpdate: function(date, active){
832         var me = this,
833             cells = me.cells.elements,
834             textNodes = me.textNodes,
835             disabledCls = me.disabledCellCls,
836             eDate = Ext.Date,
837             i = 0,
838             extraDays = 0,
839             visible = me.isVisible(),
840             sel = +eDate.clearTime(date, true),
841             today = +eDate.clearTime(new Date()),
842             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
843             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
844             ddMatch = me.disabledDatesRE,
845             ddText = me.disabledDatesText,
846             ddays = me.disabledDays ? me.disabledDays.join('') : false,
847             ddaysText = me.disabledDaysText,
848             format = me.format,
849             days = eDate.getDaysInMonth(date),
850             firstOfMonth = eDate.getFirstDateOfMonth(date),
851             startingPos = firstOfMonth.getDay() - me.startDay,
852             previousMonth = eDate.add(date, eDate.MONTH, -1),
853             longDayFormat = me.longDayFormat,
854             prevStart,
855             current,
856             disableToday,
857             tempDate,
858             setCellClass,
859             html,
860             cls,
861             formatValue,
862             value;
863
864         if (startingPos < 0) {
865             startingPos += 7;
866         }
867
868         days += startingPos;
869         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
870         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
871
872         if (me.showToday) {
873             tempDate = eDate.clearTime(new Date());
874             disableToday = (tempDate < min || tempDate > max ||
875                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
876                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
877
878             if (!me.disabled) {
879                 me.todayBtn.setDisabled(disableToday);
880                 me.todayKeyListener.setDisabled(disableToday);
881             }
882         }
883
884         setCellClass = function(cell){
885             value = +eDate.clearTime(current, true);
886             cell.title = eDate.format(current, longDayFormat);
887             // store dateValue number as an expando
888             cell.firstChild.dateValue = value;
889             if(value == today){
890                 cell.className += ' ' + me.todayCls;
891                 cell.title = me.todayText;
892             }
893             if(value == sel){
894                 cell.className += ' ' + me.selectedCls;
895                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
896                 if (visible && me.floating) {
897                     Ext.fly(cell.firstChild).focus(50);
898                 }
899             }
900             // disabling
901             if(value < min) {
902                 cell.className = disabledCls;
903                 cell.title = me.minText;
904                 return;
905             }
906             if(value > max) {
907                 cell.className = disabledCls;
908                 cell.title = me.maxText;
909                 return;
910             }
911             if(ddays){
912                 if(ddays.indexOf(current.getDay()) != -1){
913                     cell.title = ddaysText;
914                     cell.className = disabledCls;
915                 }
916             }
917             if(ddMatch && format){
918                 formatValue = eDate.dateFormat(current, format);
919                 if(ddMatch.test(formatValue)){
920                     cell.title = ddText.replace('%0', formatValue);
921                     cell.className = disabledCls;
922                 }
923             }
924         };
925
926         for(; i < me.numDays; ++i) {
927             if (i < startingPos) {
928                 html = (++prevStart);
929                 cls = me.prevCls;
930             } else if (i >= days) {
931                 html = (++extraDays);
932                 cls = me.nextCls;
933             } else {
934                 html = i - startingPos + 1;
935                 cls = me.activeCls;
936             }
937             textNodes[i].innerHTML = html;
938             cells[i].className = cls;
939             current.setDate(current.getDate() + 1);
940             setCellClass(cells[i]);
941         }
942
943         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
944     },
945
946     /**
947      * Update the contents of the picker
948      * @private
949      * @param {Date} date The new date
950      * @param {Boolean} forceRefresh True to force a full refresh
951      */
952     update : function(date, forceRefresh){
953         var me = this,
954             active = me.activeDate;
955
956         if (me.rendered) {
957             me.activeDate = date;
958             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
959                 me.selectedUpdate(date, active);
960             } else {
961                 me.fullUpdate(date, active);
962             }
963         }
964         return me;
965     },
966
967     // private, inherit docs
968     beforeDestroy : function() {
969         var me = this;
970
971         if (me.rendered) {
972             Ext.destroy(
973                 me.todayKeyListener,
974                 me.keyNav,
975                 me.monthPicker,
976                 me.monthBtn,
977                 me.nextRepeater,
978                 me.prevRepeater,
979                 me.todayBtn
980             );
981             delete me.textNodes;
982             delete me.cells.elements;
983         }
984     },
985
986     // private, inherit docs
987     onShow: function() {
988         this.callParent(arguments);
989         if (this.focusOnShow) {
990             this.focus();
991         }
992     }
993 },
994
995 // After dependencies have loaded:
996 function() {
997     var proto = this.prototype;
998
999     proto.monthNames = Ext.Date.monthNames;
1000
1001     proto.dayNames = Ext.Date.dayNames;
1002
1003     proto.format = Ext.Date.defaultFormat;
1004 });
1005