fix #7905 - time picker onblur issue
[roojs1] / Roo / bootstrap / form / TimeField.js
1 /*
2  * - LGPL
3  *
4  * TimeField
5  * 
6  */
7
8 /**
9  * @class Roo.bootstrap.form.TimeField
10  * @extends Roo.bootstrap.form.Input
11  * Bootstrap DateField class
12  * @cfg {Number} minuteStep the minutes is always the multiple of a fixed number, default 1
13  * 
14  * 
15  * @constructor
16  * Create a new TimeField
17  * @param {Object} config The config object
18  */
19
20 Roo.bootstrap.form.TimeField = function(config){
21     Roo.bootstrap.form.TimeField.superclass.constructor.call(this, config);
22     this.addEvents({
23             /**
24              * @event show
25              * Fires when this field show.
26              * @param {Roo.bootstrap.form.DateField} thisthis
27              * @param {Mixed} date The date value
28              */
29             show : true,
30             /**
31              * @event show
32              * Fires when this field hide.
33              * @param {Roo.bootstrap.form.DateField} this
34              * @param {Mixed} date The date value
35              */
36             hide : true,
37             /**
38              * @event select
39              * Fires when select a date.
40              * @param {Roo.bootstrap.form.DateField} this
41              * @param {Mixed} date The date value
42              */
43             select : true
44         });
45 };
46
47 Roo.extend(Roo.bootstrap.form.TimeField, Roo.bootstrap.form.Input,  {
48     
49     /**
50      * @cfg {String} format
51      * The default time format string which can be overriden for localization support.  The format must be
52      * valid according to {@link Date#parseDate} (defaults to 'H:i').
53      */
54     format : "H:i",
55     minuteStep : 1,
56     language : 'en',
57     hiddenField : false,
58     getAutoCreate : function()
59     {
60         this.after = '<i class="fa far fa-clock"></i>';
61         return Roo.bootstrap.form.TimeField.superclass.getAutoCreate.call(this);
62         
63          
64     },
65     onRender: function(ct, position)
66     {
67         
68         Roo.bootstrap.form.TimeField.superclass.onRender.call(this, ct, position);
69
70         this.language = this.language in Roo.bootstrap.form.TimeField.periodText ? this.language : "en";
71                 
72         this.pickerEl = Roo.get(document.body).createChild(Roo.bootstrap.form.TimeField.template);
73         
74         this.picker().setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
75         
76         this.pop = this.picker().select('>.datepicker-time',true).first();
77         this.pop.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
78         
79         this.picker().on('mousedown', this.onMousedown, this);
80         this.picker().on('click', this.onClick, this);
81         
82         this.picker().addClass('datepicker-dropdown');
83     
84         this.fillTime();
85         this.update();
86             
87         this.pop.select('.hours-up', true).first().on('click', this.onIncrementHours, this);
88         this.pop.select('.hours-down', true).first().on('click', this.onDecrementHours, this);
89         this.pop.select('.minutes-up', true).first().on('click', this.onIncrementMinutes, this);
90         this.pop.select('.minutes-down', true).first().on('click', this.onDecrementMinutes, this);
91         this.pop.select('button.period', true).first().on('click', this.onTogglePeriod, this);
92         this.pop.select('button.ok', true).first().on('click', this.setTime, this);
93         this.pop.select('button.ok', true).first().dom.innerHTML = Roo.bootstrap.form.TimeField.okText;
94
95         this.hiddenField = this.inputEl().insertSibling(
96             {tag : 'input', type : 'hidden', name : this.name},
97             'before',
98             true
99         );
100         this.inputEl().dom.setAttribute('name', this.name + '____hidden___');
101
102     },
103     
104     fireKey: function(e){
105         if (!this.picker().isVisible()){
106             if (e.keyCode == 27) { // allow escape to hide and re-show picker
107                 this.show();
108             }
109             return;
110         }
111
112         e.preventDefault();
113         
114         switch(e.keyCode){
115             case 27: // escape
116                 this.hide();
117                 break;
118             case 37: // left
119             case 39: // right
120                 this.onTogglePeriod();
121                 break;
122             case 38: // up
123                 this.onIncrementMinutes();
124                 break;
125             case 40: // down
126                 this.onDecrementMinutes();
127                 break;
128             case 13: // enter
129             case 9: // tab
130                 this.setTime();
131                 break;
132         }
133     },
134     
135     onClick: function(e) {
136         e.stopPropagation();
137         e.preventDefault();
138     },
139     
140     picker : function()
141     {
142         return this.pickerEl;
143     },
144     
145     fillTime: function()
146     {    
147         var time = this.pop.select('tbody', true).first();
148         
149         time.dom.innerHTML = '';
150         
151         time.createChild({
152             tag: 'tr',
153             cn: [
154                 {
155                     tag: 'td',
156                     cn: [
157                         {
158                             tag: 'a',
159                             href: '#',
160                             cls: 'btn',
161                             cn: [
162                                 {
163                                     tag: 'i',
164                                     cls: 'hours-up fa fas fa-chevron-up'
165                                 }
166                             ]
167                         } 
168                     ]
169                 },
170                 {
171                     tag: 'td',
172                     cls: 'separator'
173                 },
174                 {
175                     tag: 'td',
176                     cn: [
177                         {
178                             tag: 'a',
179                             href: '#',
180                             cls: 'btn',
181                             cn: [
182                                 {
183                                     tag: 'i',
184                                     cls: 'minutes-up fa fas fa-chevron-up'
185                                 }
186                             ]
187                         }
188                     ]
189                 },
190                 {
191                     tag: 'td',
192                     cls: 'separator'
193                 }
194             ]
195         });
196         
197         time.createChild({
198             tag: 'tr',
199             cn: [
200                 {
201                     tag: 'td',
202                     cn: [
203                         {
204                             tag: 'span',
205                             cls: 'timepicker-hour',
206                             html: '00'
207                         }  
208                     ]
209                 },
210                 {
211                     tag: 'td',
212                     cls: 'separator',
213                     html: ':'
214                 },
215                 {
216                     tag: 'td',
217                     cn: [
218                         {
219                             tag: 'span',
220                             cls: 'timepicker-minute',
221                             html: '00'
222                         }  
223                     ]
224                 },
225                 {
226                     tag: 'td',
227                     cls: 'separator'
228                 },
229                 {
230                     tag: 'td',
231                     cn: [
232                         {
233                             tag: 'button',
234                             type: 'button',
235                             cls: 'btn btn-primary period',
236                             html: 'AM'
237                             
238                         }
239                     ]
240                 }
241             ]
242         });
243         
244         time.createChild({
245             tag: 'tr',
246             cn: [
247                 {
248                     tag: 'td',
249                     cn: [
250                         {
251                             tag: 'a',
252                             href: '#',
253                             cls: 'btn',
254                             cn: [
255                                 {
256                                     tag: 'span',
257                                     cls: 'hours-down fa fas fa-chevron-down'
258                                 }
259                             ]
260                         }
261                     ]
262                 },
263                 {
264                     tag: 'td',
265                     cls: 'separator'
266                 },
267                 {
268                     tag: 'td',
269                     cn: [
270                         {
271                             tag: 'a',
272                             href: '#',
273                             cls: 'btn',
274                             cn: [
275                                 {
276                                     tag: 'span',
277                                     cls: 'minutes-down fa fas fa-chevron-down'
278                                 }
279                             ]
280                         }
281                     ]
282                 },
283                 {
284                     tag: 'td',
285                     cls: 'separator'
286                 }
287             ]
288         });
289         
290     },
291     
292     update: function()
293     {
294         // default minute is a multiple of minuteStep
295         if(typeof(this.time) === 'undefined' || this.time.length == 0) {
296             this.time = new Date();
297             this.time = this.time.add(Date.MINUTE, Math.round(parseInt(this.time.format('i')) / this.minuteStep) * this.minuteStep - parseInt(this.time.format('i')));
298         }
299         this.time = (typeof(this.time) === 'undefined' || this.time.length == 0) ? new Date() : this.time;
300         
301         this.fill();
302     },
303     
304     fill: function() 
305     {
306         var hours = this.time.getHours();
307         var minutes = this.time.getMinutes();
308         var period = Roo.bootstrap.form.TimeField.periodText[this.language]['am'];
309         
310         if(hours > 11){
311             period = Roo.bootstrap.form.TimeField.periodText[this.language]['pm'];
312         }
313         
314         if(hours == 0){
315             hours = 12;
316         }
317         
318         
319         if(hours > 12){
320             hours = hours - 12;
321         }
322         
323         if(hours < 10){
324             hours = '0' + hours;
325         }
326         
327         if(minutes < 10){
328             minutes = '0' + minutes;
329         }
330         
331         this.pop.select('.timepicker-hour', true).first().dom.innerHTML = hours;
332         this.pop.select('.timepicker-minute', true).first().dom.innerHTML = minutes;
333         this.pop.select('button', true).first().dom.innerHTML = period;
334         
335     },
336     
337     place: function()
338     {   
339         this.picker().removeClass(['bottom-left', 'bottom-right', 'top-left', 'top-right']);
340         
341         var cls = ['bottom'];
342         
343         if((Roo.lib.Dom.getViewHeight() + Roo.get(document.body).getScroll().top) - (this.inputEl().getBottom() + this.picker().getHeight()) < 0){ // top
344             cls.pop();
345             cls.push('top');
346         }
347         
348         cls.push('right');
349         
350         if((Roo.lib.Dom.getViewWidth() + Roo.get(document.body).getScroll().left) - (this.inputEl().getLeft() + this.picker().getWidth()) < 0){ // left
351             cls.pop();
352             cls.push('left');
353         }
354         //this.picker().setXY(20000,20000);
355         this.picker().addClass(cls.join('-'));
356         
357         var _this = this;
358         
359         Roo.each(cls, function(c){
360             if(c == 'bottom'){
361                 (function() {
362                  //  
363                 }).defer(200);
364                  _this.picker().alignTo(_this.inputEl(),   "tr-br", [0, 10], false);
365                 //_this.picker().setTop(_this.inputEl().getHeight());
366                 return;
367             }
368             if(c == 'top'){
369                  _this.picker().alignTo(_this.inputEl(),   "br-tr", [0, 10], false);
370                 
371                 //_this.picker().setTop(0 - _this.picker().getHeight());
372                 return;
373             }
374             /*
375             if(c == 'left'){
376                 _this.picker().setLeft(_this.inputEl().getLeft() + _this.inputEl().getWidth() - _this.el.getLeft() - _this.picker().getWidth());
377                 return;
378             }
379             if(c == 'right'){
380                 _this.picker().setLeft(_this.inputEl().getLeft() - _this.el.getLeft());
381                 return;
382             }
383             */
384         });
385         
386     },
387   
388     onFocus : function()
389     {
390         Roo.bootstrap.form.TimeField.superclass.onFocus.call(this);
391         this.show();
392     },
393     
394     onBlur : function()
395     {
396         Roo.bootstrap.form.TimeField.superclass.onBlur.call(this);
397         this.hide();
398     },
399     
400     show : function()
401     {
402         this.picker().show();
403         this.pop.show();
404         this.update();
405         this.place();
406         
407         this.fireEvent('show', this, this.time);
408     },
409     
410     hide : function()
411     {
412         this.picker().hide();
413         this.pop.hide();
414
415         this.inputEl().blur();
416         
417         this.fireEvent('hide', this, this.time);
418     },
419     
420     setTime : function()
421     {
422         this.hide();
423         this.setValue(this.time);
424         
425         this.fireEvent('select', this, this.time);
426         
427         
428     },
429
430     // return false when it fails
431     parseTime : function(value)
432     {
433         if(!value) {
434             return false;
435         }
436         if(value instanceof Date){
437             return value;
438         }
439         var v = Date.parseDate(value, 'H:i:s');
440
441         return (typeof(v) == 'undefined') ? false : v;
442     },
443
444     translateTime : function(time)
445     {
446         switch(this.language) {
447             case 'zh_CN':
448                 return new Intl.DateTimeFormat('zh-CN', {
449                     hour : 'numeric',
450                     minute : 'numeric',
451                     hour12 : true
452                 }).format(time);
453             default :
454                 return time.format(this.format);
455         }
456     },
457
458     setValue: function(v)
459     {
460         var t = this.parseTime(v);
461
462         if(!t) {
463             this.time = this.value = this.hiddenField.value =  '';
464             if(this.rendered){
465                 this.inputEl().dom.value = '';
466                 this.validate();
467             }
468             return;
469         }
470
471         this.value = this.hiddenField.value = t.dateFormat('H:i:s');
472
473         v = this.translateTime(t);
474
475         if(this.rendered){
476             this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
477             this.validate();
478         }
479
480         this.time = t;
481
482         this.update();
483     },
484
485     setRawValue: function(v)
486     {
487         var t = this.parseTime(v);
488
489         if(!t) {
490             this.time = this.value = this.hiddenField.value =  '';
491             if(this.rendered){
492                 this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
493             }
494             return;
495         }
496
497         this.value = this.hiddenField.value = t.dateFormat('H:i:s');
498
499         v = this.translateTime(t);
500
501         if(this.rendered){
502             this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
503         }
504
505         this.time = t;
506
507         this.update();
508     },
509
510     getValue: function()
511     {
512         return this.value;
513     },
514
515     getRawValue : function(){
516         return this.getValue();
517     },
518     
519     onMousedown: function(e){
520         e.stopPropagation();
521         e.preventDefault();
522     },
523     
524     onIncrementHours: function()
525     {
526         Roo.log('onIncrementHours');
527         this.time = this.time.add(Date.HOUR, 1);
528         this.update();
529         
530     },
531     
532     onDecrementHours: function()
533     {
534         Roo.log('onDecrementHours');
535         this.time = this.time.add(Date.HOUR, -1);
536         this.update();
537     },
538     
539     onIncrementMinutes: function()
540     {
541         Roo.log('onIncrementMinutes');
542         var minutesToAdd = Math.round((parseInt(this.time.format('i')) + this.minuteStep) / this.minuteStep) * this.minuteStep - parseInt(this.time.format('i'));
543         this.time = this.time.add(Date.MINUTE, minutesToAdd);
544         this.update();
545     },
546     
547     onDecrementMinutes: function()
548     {
549         Roo.log('onDecrementMinutes');
550         var minutesToSubtract = parseInt(this.time.format('i')) - Math.round((parseInt(this.time.format('i')) - this.minuteStep) / this.minuteStep) * this.minuteStep;
551         this.time = this.time.add(Date.MINUTE, -1 * minutesToSubtract);
552         this.update();
553     },
554     
555     onTogglePeriod: function()
556     {
557         Roo.log('onTogglePeriod');
558         this.time = this.time.add(Date.HOUR, 12);
559         this.update();
560     }
561     
562    
563 });
564 Roo.apply(Roo.bootstrap.form.TimeField,  {
565     okText : 'OK',
566     periodText : {
567         en : {
568             am : 'AM',
569             pm : 'PM'
570         },
571         zh_CN : {
572             am : '上午',
573             pm : '下午'
574         }
575     }
576 });
577
578 Roo.apply(Roo.bootstrap.form.TimeField,  {
579     template : {
580         tag: 'div',
581         cls: 'datepicker dropdown-menu',
582         cn: [
583             {
584                 tag: 'div',
585                 cls: 'datepicker-time',
586                 cn: [
587                 {
588                     tag: 'table',
589                     cls: 'table-condensed',
590                     cn:[
591                         {
592                             tag: 'tbody',
593                             cn: [
594                                 {
595                                     tag: 'tr',
596                                     cn: [
597                                     {
598                                         tag: 'td',
599                                         colspan: '7'
600                                     }
601                                     ]
602                                 }
603                             ]
604                         },
605                         {
606                             tag: 'tfoot',
607                             cn: [
608                                 {
609                                     tag: 'tr',
610                                     cn: [
611                                     {
612                                         tag: 'th',
613                                         colspan: '7',
614                                         cls: '',
615                                         cn: [
616                                             {
617                                                 tag: 'button',
618                                                 cls: 'btn btn-info ok',
619                                                 html: "OK" // this is overridden on construciton
620                                             }
621                                         ]
622                                     }
623                     
624                                     ]
625                                 }
626                             ]
627                         }
628                     ]
629                 }
630                 ]
631             }
632         ]
633     }
634 });
635
636  
637
638