DateTimeInputWidget: Only show calendar when focusing date components, not time compo...
[lhc/web/wiklou.git] / resources / src / mediawiki.widgets.datetime / DateTimeInputWidget.js
1 ( function ( $, mw ) {
2
3 /**
4 * DateTimeInputWidgets can be used to input a date, a time, or a date and
5 * time, in either UTC or the user's local timezone.
6 * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
7 *
8 * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
9 *
10 * @example
11 * // Example of a text input widget
12 * var dateTimeInput = new mw.widgets.datetime.DateTimeInputWidget( {} )
13 * $( 'body' ).append( dateTimeInput.$element );
14 *
15 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
16 *
17 * @class
18 * @extends OO.ui.InputWidget
19 * @mixins OO.ui.mixin.IconElement
20 * @mixins OO.ui.mixin.IndicatorElement
21 * @mixins OO.ui.mixin.PendingElement
22 *
23 * @constructor
24 * @param {Object} [config] Configuration options
25 * @cfg {string} [type='datetime'] Whether to act like a 'date', 'time', or 'datetime' input.
26 * Affects values stored in the relevant <input> and the formatting and
27 * interpretation of values passed to/from getValue() and setValue(). It's up
28 * to the user to configure the DateTimeFormatter correctly.
29 * @cfg {Object|mw.widgets.datetime.DateTimeFormatter} [formatter={}] Configuration options for
30 * mw.widgets.datetime.ProlepticGregorianDateTimeFormatter (with 'format' defaulting to
31 * '@date', '@time', or '@datetime' depending on 'type'), or an
32 * mw.widgets.datetime.DateTimeFormatter instance to use.
33 * @cfg {Object|null} [calendar={}] Configuration options for
34 * mw.widgets.datetime.CalendarWidget; note certain settings will be forced based on the
35 * settings passed to this widget. Set null to disable the calendar.
36 * @cfg {boolean} [required=false] Whether a value is required.
37 * @cfg {boolean} [clearable=true] Whether to provide for blanking the value.
38 * @cfg {Date|null} [value=null] Default value for the widget
39 * @cfg {Date|string|null} [min=null] Minimum allowed date
40 * @cfg {Date|string|null} [max=null] Maximum allowed date
41 */
42 mw.widgets.datetime.DateTimeInputWidget = function MwWidgetsDatetimeDateTimeInputWidget( config ) {
43 // Configuration initialization
44 config = $.extend( {
45 type: 'datetime',
46 clearable: true,
47 required: false,
48 min: null,
49 max: null,
50 formatter: {},
51 calendar: {}
52 }, config );
53
54 if ( $.isPlainObject( config.formatter ) && config.formatter.format === undefined ) {
55 config.formatter.format = '@' + config.type;
56 }
57
58 // Early properties
59 this.type = config.type;
60
61 // Parent constructor
62 mw.widgets.datetime.DateTimeInputWidget[ 'super' ].call( this, config );
63
64 // Mixin constructors
65 OO.ui.mixin.IconElement.call( this, config );
66 OO.ui.mixin.IndicatorElement.call( this, config );
67 OO.ui.mixin.PendingElement.call( this, config );
68
69 // Properties
70 this.$handle = $( '<span>' );
71 this.$fields = $( '<span>' );
72 this.fields = [];
73 this.clearable = !!config.clearable;
74 this.required = !!config.required;
75
76 if ( typeof config.min === 'string' ) {
77 config.min = this.parseDateValue( config.min );
78 }
79 if ( config.min instanceof Date && config.min.getTime() >= -62167219200000 ) {
80 this.min = config.min;
81 } else {
82 this.min = new Date( -62167219200000 ); // 0000-01-01T00:00:00.000Z
83 }
84
85 if ( typeof config.max === 'string' ) {
86 config.max = this.parseDateValue( config.max );
87 }
88 if ( config.max instanceof Date && config.max.getTime() <= 253402300799999 ) {
89 this.max = config.max;
90 } else {
91 this.max = new Date( 253402300799999 ); // 9999-12-31T12:59:59.999Z
92 }
93
94 switch ( this.type ) {
95 case 'date':
96 this.min.setUTCHours( 0, 0, 0, 0 );
97 this.max.setUTCHours( 23, 59, 59, 999 );
98 break;
99 case 'time':
100 this.min.setUTCFullYear( 1970, 0, 1 );
101 this.max.setUTCFullYear( 1970, 0, 1 );
102 break;
103 }
104 if ( this.min > this.max ) {
105 throw new Error(
106 '"min" (' + this.min.toISOString() + ') must not be greater than ' +
107 '"max" (' + this.max.toISOString() + ')'
108 );
109 }
110
111 if ( config.formatter instanceof mw.widgets.datetime.DateTimeFormatter ) {
112 this.formatter = config.formatter;
113 } else if ( $.isPlainObject( config.formatter ) ) {
114 this.formatter = new mw.widgets.datetime.ProlepticGregorianDateTimeFormatter( config.formatter );
115 } else {
116 throw new Error( '"formatter" must be an mw.widgets.datetime.DateTimeFormatter or a plain object' );
117 }
118
119 if ( this.type === 'time' || config.calendar === null ) {
120 this.calendar = null;
121 } else {
122 config.calendar = $.extend( {}, config.calendar, {
123 formatter: this.formatter,
124 widget: this,
125 min: this.min,
126 max: this.max
127 } );
128 this.calendar = new mw.widgets.datetime.CalendarWidget( config.calendar );
129 }
130
131 // Events
132 this.$handle.on( {
133 click: this.onHandleClick.bind( this )
134 } );
135 this.connect( this, {
136 change: 'onChange'
137 } );
138 this.formatter.connect( this, {
139 local: 'onChange'
140 } );
141 if ( this.calendar ) {
142 this.calendar.connect( this, {
143 change: 'onCalendarChange'
144 } );
145 }
146
147 // Initialization
148 this.setTabIndex( -1 );
149
150 this.$fields.addClass( 'mw-widgets-datetime-dateTimeInputWidget-fields' );
151 this.setupFields();
152
153 this.$handle
154 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-handle' )
155 .append( this.$icon, this.$indicator, this.$fields );
156
157 this.$element
158 .addClass( 'mw-widgets-datetime-dateTimeInputWidget' )
159 .append( this.$handle );
160
161 if ( this.calendar ) {
162 this.$element.append( this.calendar.$element );
163 }
164 };
165
166 /* Setup */
167
168 OO.inheritClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.InputWidget );
169 OO.mixinClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.mixin.IconElement );
170 OO.mixinClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.mixin.IndicatorElement );
171 OO.mixinClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.mixin.PendingElement );
172
173 /* Static properties */
174
175 mw.widgets.datetime.DateTimeInputWidget[ 'static' ].supportsSimpleLabel = false;
176
177 /* Events */
178
179 /* Methods */
180
181 /**
182 * Convert a date string to a Date
183 *
184 * @private
185 * @param {string} value
186 * @return {Date|null}
187 */
188 mw.widgets.datetime.DateTimeInputWidget.prototype.parseDateValue = function ( value ) {
189 var date, m;
190
191 value = String( value );
192 switch ( this.type ) {
193 case 'date':
194 value = value + 'T00:00:00Z';
195 break;
196 case 'time':
197 value = '1970-01-01T' + value + 'Z';
198 break;
199 }
200 m = /^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?Z$/.exec( value );
201 if ( m ) {
202 if ( m[ 7 ] ) {
203 while ( m[ 7 ].length < 3 ) {
204 m[ 7 ] += '0';
205 }
206 } else {
207 m[ 7 ] = 0;
208 }
209 date = new Date();
210 date.setUTCFullYear( m[ 1 ], m[ 2 ] - 1, m[ 3 ] );
211 date.setUTCHours( m[ 4 ], m[ 5 ], m[ 6 ], m[ 7 ] );
212 if ( date.getTime() < -62167219200000 || date.getTime() > 253402300799999 ||
213 date.getUTCFullYear() !== +m[ 1 ] ||
214 date.getUTCMonth() + 1 !== +m[ 2 ] ||
215 date.getUTCDate() !== +m[ 3 ] ||
216 date.getUTCHours() !== +m[ 4 ] ||
217 date.getUTCMinutes() !== +m[ 5 ] ||
218 date.getUTCSeconds() !== +m[ 6 ] ||
219 date.getUTCMilliseconds() !== +m[ 7 ]
220 ) {
221 date = null;
222 }
223 } else {
224 date = null;
225 }
226
227 return date;
228 };
229
230 /**
231 * @inheritdoc
232 */
233 mw.widgets.datetime.DateTimeInputWidget.prototype.cleanUpValue = function ( value ) {
234 var date, pad;
235
236 if ( value === '' ) {
237 return '';
238 }
239
240 if ( value instanceof Date ) {
241 date = value;
242 } else {
243 date = this.parseDateValue( value );
244 }
245
246 if ( date instanceof Date ) {
247 pad = function ( v, l ) {
248 v = String( v );
249 while ( v.length < l ) {
250 v = '0' + v;
251 }
252 return v;
253 };
254
255 switch ( this.type ) {
256 case 'date':
257 value = pad( date.getUTCFullYear(), 4 ) +
258 '-' + pad( date.getUTCMonth() + 1, 2 ) +
259 '-' + pad( date.getUTCDate(), 2 );
260 break;
261
262 case 'time':
263 value = pad( date.getUTCHours(), 2 ) +
264 ':' + pad( date.getUTCMinutes(), 2 ) +
265 ':' + pad( date.getUTCSeconds(), 2 ) +
266 '.' + pad( date.getUTCMilliseconds(), 3 );
267 value = value.replace( /\.?0+$/, '' );
268 break;
269
270 default:
271 value = date.toISOString();
272 break;
273 }
274 } else {
275 value = '';
276 }
277
278 return value;
279 };
280
281 /**
282 * Get the value of the input as a Date object
283 *
284 * @return {Date|null}
285 */
286 mw.widgets.datetime.DateTimeInputWidget.prototype.getValueAsDate = function () {
287 return this.parseDateValue( this.getValue() );
288 };
289
290 /**
291 * Set up the UI fields
292 *
293 * @private
294 */
295 mw.widgets.datetime.DateTimeInputWidget.prototype.setupFields = function () {
296 var i, $field, spec, placeholder, sz, maxlength,
297 spanValFunc = function ( v ) {
298 if ( v === undefined ) {
299 return this.data( 'mw-widgets-datetime-dateTimeInputWidget-value' );
300 } else {
301 v = String( v );
302 this.data( 'mw-widgets-datetime-dateTimeInputWidget-value', v );
303 if ( v === '' ) {
304 v = this.data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder' );
305 }
306 this.text( v );
307 return this;
308 }
309 },
310 reduceFunc = function ( k, v ) {
311 maxlength = Math.max( maxlength, v );
312 },
313 disabled = this.isDisabled(),
314 specs = this.formatter.getFieldSpec();
315
316 this.$fields.empty();
317 this.clearButton = null;
318 this.fields = [];
319
320 for ( i = 0; i < specs.length; i++ ) {
321 spec = specs[ i ];
322 if ( typeof spec === 'string' ) {
323 $( '<span>' )
324 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-field' )
325 .text( spec )
326 .appendTo( this.$fields );
327 continue;
328 }
329
330 placeholder = '';
331 while ( placeholder.length < spec.size ) {
332 placeholder += '_';
333 }
334
335 if ( spec.type === 'number' ) {
336 // Numbers ''should'' be the same width. But we need some extra for
337 // IE, apparently.
338 sz = ( spec.size * 1.15 ) + 'ch';
339 } else {
340 // Add a little for padding
341 sz = ( spec.size * 1.15 ) + 'ch';
342 }
343 if ( spec.editable && spec.type !== 'static' ) {
344 if ( spec.type === 'boolean' || spec.type === 'toggleLocal' ) {
345 $field = $( '<span>' )
346 .attr( {
347 tabindex: disabled ? -1 : 0
348 } )
349 .width( sz )
350 .data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder', placeholder );
351 $field.on( {
352 keydown: this.onFieldKeyDown.bind( this, $field ),
353 focus: this.onFieldFocus.bind( this, $field ),
354 click: this.onFieldClick.bind( this, $field ),
355 'wheel mousewheel DOMMouseScroll': this.onFieldWheel.bind( this, $field )
356 } );
357 $field.val = spanValFunc;
358 } else {
359 maxlength = spec.size;
360 if ( spec.intercalarySize ) {
361 $.each( spec.intercalarySize, reduceFunc );
362 }
363 $field = $( '<input>' ).attr( 'type', 'text' )
364 .attr( {
365 tabindex: disabled ? -1 : 0,
366 size: spec.size,
367 maxlength: maxlength
368 } )
369 .prop( {
370 disabled: disabled,
371 placeholder: placeholder
372 } )
373 .width( sz );
374 $field.on( {
375 keydown: this.onFieldKeyDown.bind( this, $field ),
376 click: this.onFieldClick.bind( this, $field ),
377 focus: this.onFieldFocus.bind( this, $field ),
378 blur: this.onFieldBlur.bind( this, $field ),
379 change: this.onFieldChange.bind( this, $field ),
380 'wheel mousewheel DOMMouseScroll': this.onFieldWheel.bind( this, $field )
381 } );
382 }
383 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-editField' );
384 } else {
385 $field = $( '<span>' )
386 .width( sz )
387 .data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder', placeholder );
388 if ( spec.type !== 'static' ) {
389 $field.prop( 'tabIndex', -1 );
390 $field.on( 'focus', this.onFieldFocus.bind( this, $field ) );
391 }
392 if ( spec.type === 'static' ) {
393 $field.text( spec.value );
394 } else {
395 $field.val = spanValFunc;
396 }
397 }
398
399 this.fields.push( $field );
400 $field
401 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-field' )
402 .data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec', spec )
403 .appendTo( this.$fields );
404 }
405
406 if ( this.clearable ) {
407 this.clearButton = new OO.ui.ButtonWidget( {
408 classes: [ 'mw-widgets-datetime-dateTimeInputWidget-field', 'mw-widgets-datetime-dateTimeInputWidget-clearButton' ],
409 framed: false,
410 icon: 'remove',
411 disabled: disabled
412 } ).connect( this, {
413 click: 'onClearClick'
414 } );
415 this.$fields.append( this.clearButton.$element );
416 }
417
418 this.updateFieldsFromValue();
419 };
420
421 /**
422 * Update the UI fields from the current value
423 *
424 * @private
425 */
426 mw.widgets.datetime.DateTimeInputWidget.prototype.updateFieldsFromValue = function () {
427 var i, $field, spec, intercalary, sz,
428 date = this.getValueAsDate();
429
430 if ( date === null ) {
431 this.components = null;
432
433 for ( i = 0; i < this.fields.length; i++ ) {
434 $field = this.fields[ i ];
435 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
436
437 $field
438 .removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid oo-ui-element-hidden' )
439 .val( '' );
440
441 if ( spec.intercalarySize ) {
442 if ( spec.type === 'number' ) {
443 // Numbers ''should'' be the same width. But we need some extra for
444 // IE, apparently.
445 $field.width( ( spec.size * 1.15 ) + 'ch' );
446 } else {
447 // Add a little for padding
448 $field.width( ( spec.size * 1.15 ) + 'ch' );
449 }
450 }
451 }
452
453 this.setFlags( { invalid: this.required } );
454 } else {
455 this.components = this.formatter.getComponentsFromDate( date );
456 intercalary = this.components.intercalary;
457
458 for ( i = 0; i < this.fields.length; i++ ) {
459 $field = this.fields[ i ];
460 $field.removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
461 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
462 if ( spec.type !== 'static' ) {
463 $field.val( spec.formatValue( this.components[ spec.component ] ) );
464 }
465 if ( spec.intercalarySize ) {
466 if ( intercalary && spec.intercalarySize[ intercalary ] !== undefined ) {
467 sz = spec.intercalarySize[ intercalary ];
468 } else {
469 sz = spec.size;
470 }
471 $field.toggleClass( 'oo-ui-element-hidden', sz <= 0 );
472 if ( spec.type === 'number' ) {
473 // Numbers ''should'' be the same width. But we need some extra for
474 // IE, apparently.
475 this.fields[ i ].width( ( sz * 1.15 ) + 'ch' );
476 } else {
477 // Add a little for padding
478 this.fields[ i ].width( ( sz * 1.15 ) + 'ch' );
479 }
480 }
481 }
482
483 this.setFlags( { invalid: date < this.min || date > this.max } );
484 }
485
486 this.$element.toggleClass( 'mw-widgets-datetime-dateTimeInputWidget-empty', date === null );
487 };
488
489 /**
490 * Update the value with data from the UI fields
491 *
492 * @private
493 */
494 mw.widgets.datetime.DateTimeInputWidget.prototype.updateValueFromFields = function () {
495 var i, v, $field, spec, curDate, newDate,
496 components = {},
497 anyInvalid = false,
498 anyEmpty = false,
499 allEmpty = true;
500
501 for ( i = 0; i < this.fields.length; i++ ) {
502 $field = this.fields[ i ];
503 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
504 if ( spec.editable ) {
505 $field.removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
506 v = $field.val();
507 if ( v === '' ) {
508 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
509 anyEmpty = true;
510 } else {
511 allEmpty = false;
512 v = spec.parseValue( v );
513 if ( v === undefined ) {
514 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
515 anyInvalid = true;
516 } else {
517 components[ spec.component ] = v;
518 }
519 }
520 }
521 }
522
523 if ( allEmpty ) {
524 for ( i = 0; i < this.fields.length; i++ ) {
525 this.fields[ i ].removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
526 }
527 } else if ( anyEmpty ) {
528 anyInvalid = true;
529 }
530
531 if ( !anyInvalid ) {
532 curDate = this.getValueAsDate();
533 newDate = this.formatter.getDateFromComponents( components );
534 if ( !curDate || !newDate || curDate.getTime() !== newDate.getTime() ) {
535 this.setValue( newDate );
536 }
537 }
538 };
539
540 /**
541 * Handle change event
542 *
543 * @private
544 */
545 mw.widgets.datetime.DateTimeInputWidget.prototype.onChange = function () {
546 var date;
547
548 this.updateFieldsFromValue();
549
550 if ( this.calendar ) {
551 date = this.getValueAsDate();
552 this.calendar.setSelected( date );
553 if ( date ) {
554 this.calendar.setFocusedDate( date );
555 }
556 }
557 };
558
559 /**
560 * Handle clear button click event
561 *
562 * @private
563 */
564 mw.widgets.datetime.DateTimeInputWidget.prototype.onClearClick = function () {
565 this.blur();
566 this.setValue( '' );
567 };
568
569 /**
570 * Handle click on the widget background
571 *
572 * @private
573 * @param {jQuery.Event} e Click event
574 */
575 mw.widgets.datetime.DateTimeInputWidget.prototype.onHandleClick = function () {
576 this.focus();
577 };
578
579 /**
580 * Handle key down events on our field inputs.
581 *
582 * @private
583 * @param {jQuery} $field
584 * @param {jQuery.Event} e Key down event
585 */
586 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldKeyDown = function ( $field, e ) {
587 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
588
589 if ( !this.isDisabled() ) {
590 switch ( e.which ) {
591 case OO.ui.Keys.ENTER:
592 case OO.ui.Keys.SPACE:
593 if ( spec.type === 'boolean' ) {
594 this.setValue(
595 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, 1, 'wrap' )
596 );
597 return false;
598 } else if ( spec.type === 'toggleLocal' ) {
599 this.formatter.toggleLocal();
600 }
601 break;
602
603 case OO.ui.Keys.UP:
604 case OO.ui.Keys.DOWN:
605 if ( spec.type === 'toggleLocal' ) {
606 this.formatter.toggleLocal();
607 } else {
608 this.setValue(
609 this.formatter.adjustComponent( this.getValueAsDate(), spec.component,
610 e.keyCode === OO.ui.Keys.UP ? -1 : 1, 'wrap' )
611 );
612 }
613 if ( $field.is( ':input' ) ) {
614 $field.select();
615 }
616 return false;
617 }
618 }
619 };
620
621 /**
622 * Handle focus events on our field inputs.
623 *
624 * @private
625 * @param {jQuery} $field
626 * @param {jQuery.Event} e Focus event
627 */
628 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldFocus = function ( $field ) {
629 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
630
631 if ( !this.isDisabled() ) {
632 if ( this.getValueAsDate() === null ) {
633 this.setValue( this.formatter.getDefaultDate() );
634 }
635 if ( $field.is( ':input' ) ) {
636 $field.select();
637 }
638
639 if ( this.calendar ) {
640 this.calendar.toggle( !!spec.calendarComponent );
641 }
642 }
643 };
644
645 /**
646 * Handle click events on our field inputs.
647 *
648 * @private
649 * @param {jQuery} $field
650 * @param {jQuery.Event} e Click event
651 */
652 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldClick = function ( $field ) {
653 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
654
655 if ( !this.isDisabled() ) {
656 if ( spec.type === 'boolean' ) {
657 this.setValue(
658 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, 1, 'wrap' )
659 );
660 } else if ( spec.type === 'toggleLocal' ) {
661 this.formatter.toggleLocal();
662 }
663 }
664 };
665
666 /**
667 * Handle blur events on our field inputs.
668 *
669 * @private
670 * @param {jQuery} $field
671 * @param {jQuery.Event} e Blur event
672 */
673 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldBlur = function ( $field ) {
674 var v, date,
675 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
676
677 this.updateValueFromFields();
678
679 // Normalize
680 date = this.getValueAsDate();
681 if ( !date ) {
682 $field.val( '' );
683 } else {
684 v = spec.formatValue( this.formatter.getComponentsFromDate( date )[ spec.component ] );
685 if ( v !== $field.val() ) {
686 $field.val( v );
687 }
688 }
689 };
690
691 /**
692 * Handle change events on our field inputs.
693 *
694 * @private
695 * @param {jQuery} $field
696 * @param {jQuery.Event} e Change event
697 */
698 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldChange = function () {
699 this.updateValueFromFields();
700 };
701
702 /**
703 * Handle wheel events on our field inputs.
704 *
705 * @private
706 * @param {jQuery} $field
707 * @param {jQuery.Event} e Change event
708 */
709 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldWheel = function ( $field, e ) {
710 var delta = 0,
711 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
712
713 if ( this.isDisabled() ) {
714 return;
715 }
716
717 // Standard 'wheel' event
718 if ( e.originalEvent.deltaMode !== undefined ) {
719 this.sawWheelEvent = true;
720 }
721 if ( e.originalEvent.deltaY ) {
722 delta = -e.originalEvent.deltaY;
723 } else if ( e.originalEvent.deltaX ) {
724 delta = e.originalEvent.deltaX;
725 }
726
727 // Non-standard events
728 if ( !this.sawWheelEvent ) {
729 if ( e.originalEvent.wheelDeltaX ) {
730 delta = -e.originalEvent.wheelDeltaX;
731 } else if ( e.originalEvent.wheelDeltaY ) {
732 delta = e.originalEvent.wheelDeltaY;
733 } else if ( e.originalEvent.wheelDelta ) {
734 delta = e.originalEvent.wheelDelta;
735 } else if ( e.originalEvent.detail ) {
736 delta = -e.originalEvent.detail;
737 }
738 }
739
740 if ( delta && spec ) {
741 if ( spec.type === 'toggleLocal' ) {
742 this.formatter.toggleLocal();
743 } else {
744 this.setValue(
745 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, delta < 0 ? -1 : 1, 'wrap' )
746 );
747 }
748 return false;
749 }
750 };
751
752 /**
753 * Handle calendar change event
754 *
755 * @private
756 */
757 mw.widgets.datetime.DateTimeInputWidget.prototype.onCalendarChange = function () {
758 var curDate = this.getValueAsDate(),
759 newDate = this.calendar.getSelected()[ 0 ];
760
761 if ( newDate ) {
762 if ( !curDate || newDate.getTime() !== curDate.getTime() ) {
763 this.setValue( newDate );
764 }
765 }
766 };
767
768 /**
769 * @inheritdoc
770 * @private
771 */
772 mw.widgets.datetime.DateTimeInputWidget.prototype.getInputElement = function () {
773 return $( '<input>' ).attr( 'type', 'hidden' );
774 };
775
776 /**
777 * @inheritdoc
778 */
779 mw.widgets.datetime.DateTimeInputWidget.prototype.setDisabled = function ( disabled ) {
780 mw.widgets.datetime.DateTimeInputWidget[ 'super' ].prototype.setDisabled.call( this, disabled );
781
782 // Flag all our fields as disabled
783 if ( this.$fields ) {
784 this.$fields.find( 'input' ).prop( 'disabled', this.isDisabled() );
785 this.$fields.find( '[tabindex]' ).attr( 'tabindex', this.isDisabled() ? -1 : 0 );
786 }
787
788 if ( this.clearButton ) {
789 this.clearButton.setDisabled( disabled );
790 }
791
792 return this;
793 };
794
795 /**
796 * @inheritdoc
797 */
798 mw.widgets.datetime.DateTimeInputWidget.prototype.focus = function () {
799 if ( !this.$fields.find( document.activeElement ).length ) {
800 this.$fields.find( '.mw-widgets-datetime-dateTimeInputWidget-editField' ).first().focus();
801 }
802 return this;
803 };
804
805 /**
806 * @inheritdoc
807 */
808 mw.widgets.datetime.DateTimeInputWidget.prototype.blur = function () {
809 this.$fields.find( document.activeElement ).blur();
810 return this;
811 };
812
813 /**
814 * @inheritdoc
815 */
816 mw.widgets.datetime.DateTimeInputWidget.prototype.simulateLabelClick = function () {
817 this.focus();
818 };
819
820 }( jQuery, mediaWiki ) );