fix #7905 - time picker onblur issue
[roojs1] / Roo / bootstrap / form / DateField.js
1 /*
2  * - LGPL
3  *
4  * DateField
5  * 
6  */
7
8 /**
9  * @class Roo.bootstrap.form.DateField
10  * @extends Roo.bootstrap.form.Input
11  * Bootstrap DateField class
12  * @cfg {Number} weekStart default 0
13  * @cfg {String} viewMode default empty, (months|years)
14  * @cfg {String} minViewMode default empty, (months|years)
15  * @cfg {Number} startDate default -Infinity
16  * @cfg {Number} endDate default Infinity
17  * @cfg {Boolean} todayHighlight default false
18  * @cfg {Boolean} todayBtn default false
19  * @cfg {Boolean} calendarWeeks default false
20  * @cfg {Object} daysOfWeekDisabled default empty
21  * @cfg {Boolean} singleMode default false (true | false)
22  * 
23  * @cfg {Boolean} keyboardNavigation default true
24  * @cfg {String} language default en
25  * 
26  * @constructor
27  * Create a new DateField
28  * @param {Object} config The config object
29  */
30  
31 Roo.bootstrap.form.DateField = function(config){
32     Roo.bootstrap.form.DateField.superclass.constructor.call(this, config);
33      this.addEvents({
34             /**
35              * @event show
36              * Fires when this field show.
37              * @param {Roo.bootstrap.form.DateField} this
38              * @param {Mixed} date The date value
39              */
40             show : true,
41             /**
42              * @event show
43              * Fires when this field hide.
44              * @param {Roo.bootstrap.form.DateField} this
45              * @param {Mixed} date The date value
46              */
47             hide : true,
48             /**
49              * @event select
50              * Fires when select a date.
51              * @param {Roo.bootstrap.form.DateField} this
52              * @param {Mixed} date The date value
53              */
54             select : true,
55             /**
56              * @event beforeselect
57              * Fires when before select a date.
58              * @param {Roo.bootstrap.form.DateField} this
59              * @param {Mixed} date The date value
60              */
61             beforeselect : true
62         });
63 };
64
65 Roo.extend(Roo.bootstrap.form.DateField, Roo.bootstrap.form.Input,  {
66     
67     /**
68      * @cfg {String} format
69      * The default date format string which can be overriden for localization support.  The format must be
70      * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
71      */
72     format : "m/d/y",
73     
74     weekStart : 0,
75     
76     viewMode : '',
77     
78     minViewMode : '',
79     
80     todayHighlight : false,
81     
82     todayBtn: false,
83     
84     language: 'en',
85     
86     keyboardNavigation: true,
87     
88     calendarWeeks: false,
89     
90     startDate: -Infinity,
91     
92     endDate: Infinity,
93     
94     daysOfWeekDisabled: [],
95     
96     _events: [],
97     
98     singleMode : false,
99
100     hiddenField : false,
101     
102     UTCDate: function()
103     {
104         return new Date(Date.UTC.apply(Date, arguments));
105     },
106     
107     UTCToday: function()
108     {
109         var today = new Date();
110         return this.UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
111     },
112     
113     getDate: function() {
114             var d = this.getUTCDate();
115             return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
116     },
117     
118     getUTCDate: function() {
119             return this.date;
120     },
121     
122     setDate: function(d) {
123             this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
124     },
125     
126     setUTCDate: function(d) {
127             this.date = d;
128             this.setValue(this.date);
129     },
130
131     translateDates: function(lang) 
132     {
133         var translation = Roo.bootstrap.form.DateField.dates[lang] = {
134             days: [],
135             daysShort: [],
136             daysMin: [],
137             months: [],
138             monthsShort: []
139         };
140
141         var locale = lang.replace('_', '-');
142
143         var is_latin = [ 'zh-hk', 'zh-cn', 'jp', 'ko' ].indexOf(locale.toLowerCase()) < 0; 
144                  
145
146         // fill days
147         for(var i = 0; i < 7; i++) {
148             var date = new Date(2020, 0, 5 + i);
149
150             var day = new Intl.DateTimeFormat(locale, {
151                 weekday : 'long'
152             }).format(date);
153
154             var dayShort = new Intl.DateTimeFormat(locale, {
155                 weekday : 'short'
156             }).format(date);
157
158             var dayMin = new Intl.DateTimeFormat(locale, {
159                 weekday : 'narrow'
160             }).format(date);
161
162             if(is_latin) {
163                 dayShort = day.substring(0, 3);
164                 dayMin = day.substring(0, 2);
165             }
166             
167             translation.days.push(day);
168             translation.daysShort.push(dayShort);
169             translation.daysMin.push(dayMin);
170         }
171
172         // fill months
173         for(var i = 0; i < 12; i++) {
174             var date = new Date(2020, i);
175
176             var month = new Intl.DateTimeFormat(locale, {
177                 month : 'long'
178             }).format(date);
179
180             var monthShort = new Intl.DateTimeFormat(locale, {
181                 month : 'short'
182             }).format(date);
183
184             if(is_latin) {
185                 monthShort = month.substring(0, 3);
186             }
187
188             translation.months.push(month);
189             translation.monthsShort.push(monthShort);
190         }
191     },
192         
193     onRender: function(ct, position)
194     {
195         
196         Roo.bootstrap.form.DateField.superclass.onRender.call(this, ct, position);
197
198         this.translateDates(this.language);
199         
200         this.isRTL = Roo.bootstrap.form.DateField.dates[this.language].rtl || false;
201         this.format = this.format || 'm/d/y';
202         this.isInline = false;
203         this.isInput = true;
204         this.component = this.el.select('.add-on', true).first() || false;
205         this.component = (this.component && this.component.length === 0) ? false : this.component;
206         this.hasInput = this.component && this.inputEl().length;
207         
208         if (typeof(this.minViewMode === 'string')) {
209             switch (this.minViewMode) {
210                 case 'months':
211                     this.minViewMode = 1;
212                     break;
213                 case 'years':
214                     this.minViewMode = 2;
215                     break;
216                 default:
217                     this.minViewMode = 0;
218                     break;
219             }
220         }
221         
222         if (typeof(this.viewMode === 'string')) {
223             switch (this.viewMode) {
224                 case 'months':
225                     this.viewMode = 1;
226                     break;
227                 case 'years':
228                     this.viewMode = 2;
229                     break;
230                 default:
231                     this.viewMode = 0;
232                     break;
233             }
234         }
235                 
236         this.pickerEl = Roo.get(document.body).createChild(Roo.bootstrap.form.DateField.template);
237         
238 //        this.el.select('>.input-group', true).first().createChild(Roo.bootstrap.form.DateField.template);
239         
240         this.picker().setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
241         
242         this.picker().on('mousedown', this.onMousedown, this);
243         this.picker().on('click', this.onClick, this);
244         
245         this.picker().addClass('datepicker-dropdown');
246         
247         this.startViewMode = this.viewMode;
248         
249         if(this.singleMode){
250             Roo.each(this.picker().select('thead > tr > th', true).elements, function(v){
251                 v.setVisibilityMode(Roo.Element.DISPLAY);
252                 v.hide();
253             });
254             
255             Roo.each(this.picker().select('tbody > tr > td', true).elements, function(v){
256                 v.setStyle('width', '189px');
257             });
258         }
259         
260         Roo.each(this.picker().select('tfoot th.today', true).elements, function(v){
261             v.dom.innerHTML = Roo.bootstrap.form.DateField.todayText;
262         });
263                         
264         
265         this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
266         
267         this.setStartDate(this.startDate);
268         this.setEndDate(this.endDate);
269         
270         this.setDaysOfWeekDisabled(this.daysOfWeekDisabled);
271         
272         this.fillDow();
273         this.fillMonths();
274         this.update();
275         this.showMode();
276         
277         if(this.isInline) {
278             this.showPopup();
279         }
280
281         this.hiddenField = this.inputEl().insertSibling(
282             {tag : 'input', type : 'hidden', name : this.name},
283             'before',
284             true
285         );
286         this.inputEl().dom.setAttribute('name', this.name + '____hidden___');
287
288     },
289     
290     picker : function()
291     {
292         return this.pickerEl;
293 //        return this.el.select('.datepicker', true).first();
294     },
295     
296     fillDow: function()
297     {
298         var dowCnt = this.weekStart;
299         
300         var dow = {
301             tag: 'tr',
302             cn: [
303                 
304             ]
305         };
306         
307         while (dowCnt < this.weekStart + 7) {
308             dow.cn.push({
309                 tag: 'th',
310                 cls: 'dow',
311                 html: Roo.bootstrap.form.DateField.dates[this.language].daysMin[(dowCnt++)%7]
312             });
313         }
314         
315         this.picker().select('>.datepicker-days thead', true).first().createChild(dow);
316     },
317     
318     fillMonths: function()
319     {    
320         var i = 0;
321         var months = this.picker().select('>.datepicker-months td', true).first();
322         
323         months.dom.innerHTML = '';
324         
325         while (i < 12) {
326             var month = {
327                 tag: 'span',
328                 cls: 'month',
329                 html: Roo.bootstrap.form.DateField.dates[this.language].monthsShort[i++]
330             };
331             
332             months.createChild(month);
333         }
334         
335     },
336     
337     update: function()
338     {
339         this.date = (typeof(this.date) === 'undefined' || ((typeof(this.date) === 'string') && !this.date.length)) ? this.UTCToday() : (typeof(this.date) === 'string') ? this.parseDate(this.date) : this.date;
340         
341         if (this.date < this.startDate) {
342             this.viewDate = new Date(this.startDate);
343         } else if (this.date > this.endDate) {
344             this.viewDate = new Date(this.endDate);
345         } else {
346             this.viewDate = new Date(this.date);
347         }
348         
349         this.fill();
350     },
351     
352     fill: function() 
353     {
354         var d = new Date(this.viewDate),
355                 year = d.getUTCFullYear(),
356                 month = d.getUTCMonth(),
357                 startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
358                 startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
359                 endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
360                 endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
361                 currentDate = this.date && this.date.valueOf(),
362                 today = this.UTCToday();
363         
364         this.picker().select('>.datepicker-days thead th.switch', true).first().dom.innerHTML = Roo.bootstrap.form.DateField.dates[this.language].months[month]+' '+year;
365     
366         this.updateNavArrows();
367         this.fillMonths();
368                                                 
369         var prevMonth = this.UTCDate(year, month-1, 28,0,0,0,0),
370         
371         day = prevMonth.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
372          
373         prevMonth.setUTCDate(day);
374         
375         prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
376         
377         var nextMonth = new Date(prevMonth);
378         
379         nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
380         
381         nextMonth = nextMonth.valueOf();
382         
383         var fillMonths = false;
384         
385         this.picker().select('>.datepicker-days tbody',true).first().dom.innerHTML = '';
386         
387         while(prevMonth.valueOf() <= nextMonth) {
388             var clsName = '';
389             
390             if (prevMonth.getUTCDay() === this.weekStart) {
391                 if(fillMonths){
392                     this.picker().select('>.datepicker-days tbody',true).first().createChild(fillMonths);
393                 }
394                     
395                 fillMonths = {
396                     tag: 'tr',
397                     cn: []
398                 };
399             }
400             
401             if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
402                 clsName += ' old';
403             } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
404                 clsName += ' new';
405             }
406             if (this.todayHighlight &&
407                 prevMonth.getUTCFullYear() == today.getFullYear() &&
408                 prevMonth.getUTCMonth() == today.getMonth() &&
409                 prevMonth.getUTCDate() == today.getDate()) {
410                 clsName += ' today';
411             }
412             
413             if (currentDate && prevMonth.valueOf() === currentDate) {
414                 clsName += ' active';
415             }
416             
417             if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
418                     this.daysOfWeekDisabled.indexOf(prevMonth.getUTCDay()) !== -1) {
419                     clsName += ' disabled';
420             }
421             
422             fillMonths.cn.push({
423                 tag: 'td',
424                 cls: 'day ' + clsName,
425                 html: prevMonth.getDate()
426             });
427             
428             prevMonth.setDate(prevMonth.getDate()+1);
429         }
430           
431         var currentYear = this.date && this.date.getUTCFullYear();
432         var currentMonth = this.date && this.date.getUTCMonth();
433         
434         this.picker().select('>.datepicker-months th.switch',true).first().dom.innerHTML = year;
435         
436         Roo.each(this.picker().select('>.datepicker-months tbody span',true).elements, function(v,k){
437             v.removeClass('active');
438             
439             if(currentYear === year && k === currentMonth){
440                 v.addClass('active');
441             }
442             
443             if (year < startYear || year > endYear || (year == startYear && k < startMonth) || (year == endYear && k > endMonth)) {
444                 v.addClass('disabled');
445             }
446             
447         });
448         
449         
450         year = parseInt(year/10, 10) * 10;
451         
452         this.picker().select('>.datepicker-years th.switch', true).first().dom.innerHTML = year + '-' + (year + 9);
453         
454         this.picker().select('>.datepicker-years tbody td',true).first().dom.innerHTML = '';
455         
456         year -= 1;
457         for (var i = -1; i < 11; i++) {
458             this.picker().select('>.datepicker-years tbody td',true).first().createChild({
459                 tag: 'span',
460                 cls: 'year' + (i === -1 || i === 10 ? ' old' : '') + (currentYear === year ? ' active' : '') + (year < startYear || year > endYear ? ' disabled' : ''),
461                 html: year
462             });
463             
464             year += 1;
465         }
466     },
467     
468     showMode: function(dir) 
469     {
470         if (dir) {
471             this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
472         }
473         
474         Roo.each(this.picker().select('>div',true).elements, function(v){
475             v.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
476             v.hide();
477         });
478         this.picker().select('>.datepicker-'+Roo.bootstrap.form.DateField.modes[this.viewMode].clsName, true).first().show();
479     },
480     
481     place: function()
482     {
483         if(this.isInline) {
484             return;
485         }
486         
487         this.picker().removeClass(['bottom', 'top']);
488         
489         if((Roo.lib.Dom.getViewHeight() + Roo.get(document.body).getScroll().top) - (this.inputEl().getBottom() + this.picker().getHeight()) < 0){
490             /*
491              * place to the top of element!
492              *
493              */
494             
495             this.picker().addClass('top');
496             this.picker().setTop(this.inputEl().getTop() - this.picker().getHeight()).setLeft(this.inputEl().getLeft());
497             
498             return;
499         }
500         
501         this.picker().addClass('bottom');
502         
503         this.picker().setTop(this.inputEl().getBottom()).setLeft(this.inputEl().getLeft());
504     },
505     
506     // return false when it fails
507     parseDate : function(value)
508     {
509         if(!value) {
510             return false;
511         }
512         if(value instanceof Date){
513             return value;
514         }
515         var v = Date.parseDate(value, 'Y-m-d');
516
517         return (typeof(v) == 'undefined') ? false : v;
518     },
519     
520     formatDate : function(date, fmt)
521     {   
522         return (!date || !(date instanceof Date)) ?
523         date : date.dateFormat(fmt || this.format);
524     },
525
526     translateDate : function(date)
527     {
528         switch(this.language) {
529             case 'zh_CN':
530                 return new Intl.DateTimeFormat('zh-CN', {
531                     year : 'numeric',
532                     month : 'long',
533                     day : 'numeric'
534                 }).format(date);
535             default :
536                 return this.formatDate(date);
537         }
538     },
539     
540     onFocus : function()
541     {
542         Roo.bootstrap.form.DateField.superclass.onFocus.call(this);
543         this.showPopup();
544     },
545     
546     onBlur : function()
547     {
548         Roo.bootstrap.form.DateField.superclass.onBlur.call(this);
549
550         if(!this.readOnly) {
551             var d = this.inputEl().getValue();
552             var date = this.parseDate(d);
553             if(date) {
554                 this.setValue(date);
555             }
556             else {
557                 this.setValue(this.getValue());
558             }
559         }
560                 
561         this.hidePopup();
562     },
563     
564     showPopup : function()
565     {
566         this.picker().show();
567         this.update();
568         this.place();
569         
570         this.fireEvent('showpopup', this, this.date);
571     },
572     
573     hidePopup : function()
574     {
575         if(this.isInline) {
576             return;
577         }
578         this.picker().hide();
579         this.viewMode = this.startViewMode;
580         this.showMode();
581
582         this.inputEl().blur();
583         
584         this.fireEvent('hidepopup', this, this.date);
585         
586     },
587     
588     onMousedown: function(e)
589     {
590         e.stopPropagation();
591         e.preventDefault();
592     },
593     
594     keyup: function(e)
595     {
596         Roo.bootstrap.form.DateField.superclass.keyup.call(this);
597         this.update();
598     },
599
600     setValue: function(v)
601     {
602         if(this.fireEvent('beforeselect', this, v) !== false){
603             var d = this.parseDate(v);
604
605             if(!d) {
606                 this.date = this.viewDate = this.value = this.hiddenField.value =  '';
607                 if(this.rendered){
608                     this.inputEl().dom.value = '';
609                     this.validate();
610                 }
611                 return;
612             }
613
614             d = new Date(d).clearTime();
615
616             this.value = this.hiddenField.value = d.dateFormat('Y-m-d');
617
618             v = this.translateDate(d);
619             if(this.rendered){
620                 this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
621                 this.validate();
622             }
623
624             this.date = new Date(d.getTime() - d.getTimezoneOffset()*60000);
625
626             this.update();
627
628             this.fireEvent('select', this, this.date);
629         }
630     },
631
632     // bypass validation
633     setRawValue : function(v){
634         if(this.fireEvent('beforeselect', this, v) !== false){
635             var d = this.parseDate(v);
636
637             if(!d) {
638                 this.date = this.viewDate = this.value = this.hiddenField.value =  '';
639                 if(this.rendered){
640                     this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
641                 }
642                 return;
643             }
644
645             d = new Date(d).clearTime();
646
647             this.value = this.hiddenField.value = d.dateFormat('Y-m-d');
648
649             v = this.translateDate(d);
650             if(this.rendered){
651                 this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
652             }
653
654             this.date = new Date(d.getTime() - d.getTimezoneOffset()*60000);
655
656             this.update();
657
658             this.fireEvent('select', this, this.date);
659         }
660     },
661     
662     getValue: function()
663     {
664         return this.value;
665     },
666
667     getRawValue : function(){
668         return this.getValue();
669     },
670     
671     fireKey: function(e)
672     {
673         if (!this.picker().isVisible()){
674             if (e.keyCode == 27) { // allow escape to hide and re-show picker
675                 this.showPopup();
676             }
677             return;
678         }
679         
680         var dateChanged = false,
681         dir, day, month,
682         newDate, newViewDate;
683         
684         switch(e.keyCode){
685             case 27: // escape
686                 this.hidePopup();
687                 e.preventDefault();
688                 break;
689             case 37: // left
690             case 39: // right
691                 if (!this.keyboardNavigation) {
692                     break;
693                 }
694                 dir = e.keyCode == 37 ? -1 : 1;
695                 
696                 if (e.ctrlKey){
697                     newDate = this.moveYear(this.date, dir);
698                     newViewDate = this.moveYear(this.viewDate, dir);
699                 } else if (e.shiftKey){
700                     newDate = this.moveMonth(this.date, dir);
701                     newViewDate = this.moveMonth(this.viewDate, dir);
702                 } else {
703                     newDate = new Date(this.date);
704                     newDate.setUTCDate(this.date.getUTCDate() + dir);
705                     newViewDate = new Date(this.viewDate);
706                     newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
707                 }
708                 if (this.dateWithinRange(newDate)){
709                     this.date = newDate;
710                     this.viewDate = newViewDate;
711                     this.setValue(this.date);
712 //                    this.update();
713                     e.preventDefault();
714                     dateChanged = true;
715                 }
716                 break;
717             case 38: // up
718             case 40: // down
719                 if (!this.keyboardNavigation) {
720                     break;
721                 }
722                 dir = e.keyCode == 38 ? -1 : 1;
723                 if (e.ctrlKey){
724                     newDate = this.moveYear(this.date, dir);
725                     newViewDate = this.moveYear(this.viewDate, dir);
726                 } else if (e.shiftKey){
727                     newDate = this.moveMonth(this.date, dir);
728                     newViewDate = this.moveMonth(this.viewDate, dir);
729                 } else {
730                     newDate = new Date(this.date);
731                     newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
732                     newViewDate = new Date(this.viewDate);
733                     newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
734                 }
735                 if (this.dateWithinRange(newDate)){
736                     this.date = newDate;
737                     this.viewDate = newViewDate;
738                     this.setValue(this.date);
739 //                    this.update();
740                     e.preventDefault();
741                     dateChanged = true;
742                 }
743                 break;
744             case 13: // enter
745                 this.setValue(this.date);
746                 this.hidePopup();
747                 e.preventDefault();
748                 break;
749             case 9: // tab
750                 this.setValue(this.date);
751                 this.hidePopup();
752                 break;
753             case 16: // shift
754             case 17: // ctrl
755             case 18: // alt
756                 break;
757             default :
758                 this.hidePopup();
759                 
760         }
761     },
762     
763     
764     onClick: function(e) 
765     {
766         e.stopPropagation();
767         e.preventDefault();
768         
769         var target = e.getTarget();
770         
771         if(target.nodeName.toLowerCase() === 'i'){
772             target = Roo.get(target).dom.parentNode;
773         }
774         
775         var nodeName = target.nodeName;
776         var className = target.className;
777         var html = target.innerHTML;
778         //Roo.log(nodeName);
779         
780         switch(nodeName.toLowerCase()) {
781             case 'th':
782                 switch(className) {
783                     case 'switch':
784                         this.showMode(1);
785                         break;
786                     case 'prev':
787                     case 'next':
788                         var dir = Roo.bootstrap.form.DateField.modes[this.viewMode].navStep * (className == 'prev' ? -1 : 1);
789                         switch(this.viewMode){
790                                 case 0:
791                                         this.viewDate = this.moveMonth(this.viewDate, dir);
792                                         break;
793                                 case 1:
794                                 case 2:
795                                         this.viewDate = this.moveYear(this.viewDate, dir);
796                                         break;
797                         }
798                         this.fill();
799                         break;
800                     case 'today':
801                         var date = new Date();
802                         this.date = this.UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
803 //                        this.fill()
804                         this.setValue(this.date);
805                         
806                         this.hidePopup();
807                         break;
808                 }
809                 break;
810             case 'span':
811                 if (className.indexOf('disabled') < 0) {
812                 if (!this.viewDate) {
813                     this.viewDate = new Date();
814                 }
815                 this.viewDate.setUTCDate(1);
816                     if (className.indexOf('month') > -1) {
817                         this.viewDate.setUTCMonth(Roo.bootstrap.form.DateField.dates[this.language].monthsShort.indexOf(html));
818                     } else {
819                         var year = parseInt(html, 10) || 0;
820                         this.viewDate.setUTCFullYear(year);
821                         
822                     }
823                     
824                     if(this.singleMode){
825                         this.setValue(this.viewDate);
826                         this.hidePopup();
827                         return;
828                     }
829                     
830                     this.showMode(-1);
831                     this.fill();
832                 }
833                 break;
834                 
835             case 'td':
836                 //Roo.log(className);
837                 if (className.indexOf('day') > -1 && className.indexOf('disabled') < 0 ){
838                     var day = parseInt(html, 10) || 1;
839                     var year =  (this.viewDate || new Date()).getUTCFullYear(),
840                         month = (this.viewDate || new Date()).getUTCMonth();
841
842                     if (className.indexOf('old') > -1) {
843                         if(month === 0 ){
844                             month = 11;
845                             year -= 1;
846                         }else{
847                             month -= 1;
848                         }
849                     } else if (className.indexOf('new') > -1) {
850                         if (month == 11) {
851                             month = 0;
852                             year += 1;
853                         } else {
854                             month += 1;
855                         }
856                     }
857                     //Roo.log([year,month,day]);
858                     this.date = this.UTCDate(year, month, day,0,0,0,0);
859                     this.viewDate = this.UTCDate(year, month, Math.min(28, day),0,0,0,0);
860 //                    this.fill();
861                     this.setValue(this.date);
862                     this.hidePopup();
863                 }
864                 break;
865         }
866     },
867     
868     setStartDate: function(startDate)
869     {
870         this.startDate = startDate || -Infinity;
871         if (this.startDate !== -Infinity) {
872             var date = this.parseDate(this.startDate);
873             this.startDate = date ? date : -Infinity;
874         }
875         this.update();
876         this.updateNavArrows();
877     },
878
879     setEndDate: function(endDate)
880     {
881         this.endDate = endDate || Infinity;
882         if (this.endDate !== Infinity) {
883             var date = this.parseDate(this.endDate);
884             this.endDate = date ? date : Infinity;
885         }
886         this.update();
887         this.updateNavArrows();
888     },
889     
890     setDaysOfWeekDisabled: function(daysOfWeekDisabled)
891     {
892         this.daysOfWeekDisabled = daysOfWeekDisabled || [];
893         if (typeof(this.daysOfWeekDisabled) !== 'object') {
894             this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
895         }
896         this.daysOfWeekDisabled = this.daysOfWeekDisabled.map(function (d) {
897             return parseInt(d, 10);
898         });
899         this.update();
900         this.updateNavArrows();
901     },
902     
903     updateNavArrows: function() 
904     {
905         if(this.singleMode){
906             return;
907         }
908         
909         var d = new Date(this.viewDate),
910         year = d.getUTCFullYear(),
911         month = d.getUTCMonth();
912         
913         Roo.each(this.picker().select('.prev', true).elements, function(v){
914             v.show();
915             switch (this.viewMode) {
916                 case 0:
917
918                     if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
919                         v.hide();
920                     }
921                     break;
922                 case 1:
923                 case 2:
924                     if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
925                         v.hide();
926                     }
927                     break;
928             }
929         });
930         
931         Roo.each(this.picker().select('.next', true).elements, function(v){
932             v.show();
933             switch (this.viewMode) {
934                 case 0:
935
936                     if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
937                         v.hide();
938                     }
939                     break;
940                 case 1:
941                 case 2:
942                     if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
943                         v.hide();
944                     }
945                     break;
946             }
947         })
948     },
949     
950     moveMonth: function(date, dir)
951     {
952         if (!dir) {
953             return date;
954         }
955         var new_date = new Date(date.valueOf()),
956         day = new_date.getUTCDate(),
957         month = new_date.getUTCMonth(),
958         mag = Math.abs(dir),
959         new_month, test;
960         dir = dir > 0 ? 1 : -1;
961         if (mag == 1){
962             test = dir == -1
963             // If going back one month, make sure month is not current month
964             // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
965             ? function(){
966                 return new_date.getUTCMonth() == month;
967             }
968             // If going forward one month, make sure month is as expected
969             // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
970             : function(){
971                 return new_date.getUTCMonth() != new_month;
972             };
973             new_month = month + dir;
974             new_date.setUTCMonth(new_month);
975             // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
976             if (new_month < 0 || new_month > 11) {
977                 new_month = (new_month + 12) % 12;
978             }
979         } else {
980             // For magnitudes >1, move one month at a time...
981             for (var i=0; i<mag; i++) {
982                 // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
983                 new_date = this.moveMonth(new_date, dir);
984             }
985             // ...then reset the day, keeping it in the new month
986             new_month = new_date.getUTCMonth();
987             new_date.setUTCDate(day);
988             test = function(){
989                 return new_month != new_date.getUTCMonth();
990             };
991         }
992         // Common date-resetting loop -- if date is beyond end of month, make it
993         // end of month
994         while (test()){
995             new_date.setUTCDate(--day);
996             new_date.setUTCMonth(new_month);
997         }
998         return new_date;
999     },
1000
1001     moveYear: function(date, dir)
1002     {
1003         return this.moveMonth(date, dir*12);
1004     },
1005
1006     dateWithinRange: function(date)
1007     {
1008         return date >= this.startDate && date <= this.endDate;
1009     },
1010
1011     
1012     remove: function() 
1013     {
1014         this.picker().remove();
1015     },
1016     
1017     validateValue : function(value)
1018     {
1019         if(this.getVisibilityEl().hasClass('hidden')){
1020             return true;
1021         }
1022         
1023         if(value.length < 1)  {
1024             if(this.allowBlank){
1025                 return true;
1026             }
1027             return false;
1028         }
1029         
1030         if(value.length < this.minLength){
1031             return false;
1032         }
1033         if(value.length > this.maxLength){
1034             return false;
1035         }
1036         if(this.vtype){
1037             var vt = Roo.form.VTypes;
1038             if(!vt[this.vtype](value, this)){
1039                 return false;
1040             }
1041         }
1042         if(typeof this.validator == "function"){
1043             var msg = this.validator(value);
1044             if(msg !== true){
1045                 return false;
1046             }
1047         }
1048         
1049         if(this.regex && !this.regex.test(value)){
1050             return false;
1051         }
1052         
1053         if(!this.parseDate(value)){
1054             return false;
1055         }
1056         
1057         if (this.endDate !== Infinity && this.parseDate(value).getTime() > this.endDate.getTime()) {
1058             return false;
1059         }      
1060         
1061         if (this.startDate !== -Infinity && this.parseDate(value).getTime() < this.startDate.getTime()) {
1062             return false;
1063         } 
1064         
1065         
1066         return true;
1067     },
1068     
1069     reset : function()
1070     {
1071         this.date = this.viewDate = '';
1072         
1073         Roo.bootstrap.form.DateField.superclass.setValue.call(this, '');
1074     }
1075    
1076 });
1077
1078 Roo.apply(Roo.bootstrap.form.DateField,  {
1079     
1080     head : {
1081         tag: 'thead',
1082         cn: [
1083         {
1084             tag: 'tr',
1085             cn: [
1086             {
1087                 tag: 'th',
1088                 cls: 'prev',
1089                 html: '<i class="fa fa-arrow-left"/>'
1090             },
1091             {
1092                 tag: 'th',
1093                 cls: 'switch',
1094                 colspan: '5'
1095             },
1096             {
1097                 tag: 'th',
1098                 cls: 'next',
1099                 html: '<i class="fa fa-arrow-right"/>'
1100             }
1101
1102             ]
1103         }
1104         ]
1105     },
1106     
1107     content : {
1108         tag: 'tbody',
1109         cn: [
1110         {
1111             tag: 'tr',
1112             cn: [
1113             {
1114                 tag: 'td',
1115                 colspan: '7'
1116             }
1117             ]
1118         }
1119         ]
1120     },
1121     
1122     footer : {
1123         tag: 'tfoot',
1124         cn: [
1125         {
1126             tag: 'tr',
1127             cn: [
1128             {
1129                 tag: 'th',
1130                 colspan: '7',
1131                 cls: 'today'
1132             }
1133                     
1134             ]
1135         }
1136         ]
1137     },
1138     
1139     dates : {},
1140
1141     todayText : "Today",
1142     
1143     modes: [
1144     {
1145         clsName: 'days',
1146         navFnc: 'Month',
1147         navStep: 1
1148     },
1149     {
1150         clsName: 'months',
1151         navFnc: 'FullYear',
1152         navStep: 1
1153     },
1154     {
1155         clsName: 'years',
1156         navFnc: 'FullYear',
1157         navStep: 10
1158     }]
1159 });
1160
1161 Roo.apply(Roo.bootstrap.form.DateField,  {
1162   
1163     template : {
1164         tag: 'div',
1165         cls: 'datepicker dropdown-menu roo-dynamic shadow',
1166         cn: [
1167         {
1168             tag: 'div',
1169             cls: 'datepicker-days',
1170             cn: [
1171             {
1172                 tag: 'table',
1173                 cls: 'table-condensed',
1174                 cn:[
1175                 Roo.bootstrap.form.DateField.head,
1176                 {
1177                     tag: 'tbody'
1178                 },
1179                 Roo.bootstrap.form.DateField.footer
1180                 ]
1181             }
1182             ]
1183         },
1184         {
1185             tag: 'div',
1186             cls: 'datepicker-months',
1187             cn: [
1188             {
1189                 tag: 'table',
1190                 cls: 'table-condensed',
1191                 cn:[
1192                 Roo.bootstrap.form.DateField.head,
1193                 Roo.bootstrap.form.DateField.content,
1194                 Roo.bootstrap.form.DateField.footer
1195                 ]
1196             }
1197             ]
1198         },
1199         {
1200             tag: 'div',
1201             cls: 'datepicker-years',
1202             cn: [
1203             {
1204                 tag: 'table',
1205                 cls: 'table-condensed',
1206                 cn:[
1207                 Roo.bootstrap.form.DateField.head,
1208                 Roo.bootstrap.form.DateField.content,
1209                 Roo.bootstrap.form.DateField.footer
1210                 ]
1211             }
1212             ]
1213         }
1214         ]
1215     }
1216 });
1217
1218  
1219
1220