HTMLForm: Add HTMLDateTimeField
[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 // Our form input *should* be type="hidden". But if we're infusing from
148 // PHP, it's not.
149 if ( this.$input.attr( 'type' ) !== 'hidden' ) {
150 try {
151 this.$input.attr( 'type', 'hidden' );
152 } catch ( e ) {
153 }
154 // IE <= 8, and IE 9 in quirks mode, doesn't allow changing the
155 // type, so just hide the field with CSS. IE 9 in quirks mode
156 // doesn't even throw an error, so do that unconditionally. Sigh.
157 this.$input.css( 'display', 'none' );
158 }
159
160 // Initialization
161 this.setTabIndex( -1 );
162
163 this.$fields.addClass( 'mw-widgets-datetime-dateTimeInputWidget-fields' );
164 this.setupFields();
165
166 this.$handle
167 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-handle' )
168 .append( this.$icon, this.$indicator, this.$fields );
169
170 this.$element
171 .addClass( 'mw-widgets-datetime-dateTimeInputWidget' )
172 .append( this.$handle );
173
174 if ( this.calendar ) {
175 this.$element.append( this.calendar.$element );
176 }
177 };
178
179 /* Setup */
180
181 OO.inheritClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.InputWidget );
182 OO.mixinClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.mixin.IconElement );
183 OO.mixinClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.mixin.IndicatorElement );
184 OO.mixinClass( mw.widgets.datetime.DateTimeInputWidget, OO.ui.mixin.PendingElement );
185
186 /* Static properties */
187
188 mw.widgets.datetime.DateTimeInputWidget[ 'static' ].supportsSimpleLabel = false;
189
190 /* Events */
191
192 /* Methods */
193
194 /**
195 * Convert a date string to a Date
196 *
197 * @private
198 * @param {string} value
199 * @return {Date|null}
200 */
201 mw.widgets.datetime.DateTimeInputWidget.prototype.parseDateValue = function ( value ) {
202 var date, m;
203
204 value = String( value );
205 switch ( this.type ) {
206 case 'date':
207 value = value + 'T00:00:00Z';
208 break;
209 case 'time':
210 value = '1970-01-01T' + value + 'Z';
211 break;
212 }
213 m = /^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?Z$/.exec( value );
214 if ( m ) {
215 if ( m[ 7 ] ) {
216 while ( m[ 7 ].length < 3 ) {
217 m[ 7 ] += '0';
218 }
219 } else {
220 m[ 7 ] = 0;
221 }
222 date = new Date();
223 date.setUTCFullYear( m[ 1 ], m[ 2 ] - 1, m[ 3 ] );
224 date.setUTCHours( m[ 4 ], m[ 5 ], m[ 6 ], m[ 7 ] );
225 if ( date.getTime() < -62167219200000 || date.getTime() > 253402300799999 ||
226 date.getUTCFullYear() !== +m[ 1 ] ||
227 date.getUTCMonth() + 1 !== +m[ 2 ] ||
228 date.getUTCDate() !== +m[ 3 ] ||
229 date.getUTCHours() !== +m[ 4 ] ||
230 date.getUTCMinutes() !== +m[ 5 ] ||
231 date.getUTCSeconds() !== +m[ 6 ] ||
232 date.getUTCMilliseconds() !== +m[ 7 ]
233 ) {
234 date = null;
235 }
236 } else {
237 date = null;
238 }
239
240 return date;
241 };
242
243 /**
244 * @inheritdoc
245 */
246 mw.widgets.datetime.DateTimeInputWidget.prototype.cleanUpValue = function ( value ) {
247 var date, pad;
248
249 if ( value === '' ) {
250 return '';
251 }
252
253 if ( value instanceof Date ) {
254 date = value;
255 } else {
256 date = this.parseDateValue( value );
257 }
258
259 if ( date instanceof Date ) {
260 pad = function ( v, l ) {
261 v = String( v );
262 while ( v.length < l ) {
263 v = '0' + v;
264 }
265 return v;
266 };
267
268 switch ( this.type ) {
269 case 'date':
270 value = pad( date.getUTCFullYear(), 4 ) +
271 '-' + pad( date.getUTCMonth() + 1, 2 ) +
272 '-' + pad( date.getUTCDate(), 2 );
273 break;
274
275 case 'time':
276 value = pad( date.getUTCHours(), 2 ) +
277 ':' + pad( date.getUTCMinutes(), 2 ) +
278 ':' + pad( date.getUTCSeconds(), 2 ) +
279 '.' + pad( date.getUTCMilliseconds(), 3 );
280 value = value.replace( /\.?0+$/, '' );
281 break;
282
283 default:
284 value = date.toISOString();
285 break;
286 }
287 } else {
288 value = '';
289 }
290
291 return value;
292 };
293
294 /**
295 * Get the value of the input as a Date object
296 *
297 * @return {Date|null}
298 */
299 mw.widgets.datetime.DateTimeInputWidget.prototype.getValueAsDate = function () {
300 return this.parseDateValue( this.getValue() );
301 };
302
303 /**
304 * Set up the UI fields
305 *
306 * @private
307 */
308 mw.widgets.datetime.DateTimeInputWidget.prototype.setupFields = function () {
309 var i, $field, spec, placeholder, sz, maxlength,
310 spanValFunc = function ( v ) {
311 if ( v === undefined ) {
312 return this.data( 'mw-widgets-datetime-dateTimeInputWidget-value' );
313 } else {
314 v = String( v );
315 this.data( 'mw-widgets-datetime-dateTimeInputWidget-value', v );
316 if ( v === '' ) {
317 v = this.data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder' );
318 }
319 this.text( v );
320 return this;
321 }
322 },
323 reduceFunc = function ( k, v ) {
324 maxlength = Math.max( maxlength, v );
325 },
326 disabled = this.isDisabled(),
327 specs = this.formatter.getFieldSpec();
328
329 this.$fields.empty();
330 this.clearButton = null;
331 this.fields = [];
332
333 for ( i = 0; i < specs.length; i++ ) {
334 spec = specs[ i ];
335 if ( typeof spec === 'string' ) {
336 $( '<span>' )
337 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-field' )
338 .text( spec )
339 .appendTo( this.$fields );
340 continue;
341 }
342
343 placeholder = '';
344 while ( placeholder.length < spec.size ) {
345 placeholder += '_';
346 }
347
348 if ( spec.type === 'number' ) {
349 // Numbers ''should'' be the same width. But we need some extra for
350 // IE, apparently.
351 sz = ( spec.size * 1.15 ) + 'ch';
352 } else {
353 // Add a little for padding
354 sz = ( spec.size * 1.15 ) + 'ch';
355 }
356 if ( spec.editable && spec.type !== 'static' ) {
357 if ( spec.type === 'boolean' || spec.type === 'toggleLocal' ) {
358 $field = $( '<span>' )
359 .attr( {
360 tabindex: disabled ? -1 : 0
361 } )
362 .width( sz )
363 .data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder', placeholder );
364 $field.on( {
365 keydown: this.onFieldKeyDown.bind( this, $field ),
366 focus: this.onFieldFocus.bind( this, $field ),
367 click: this.onFieldClick.bind( this, $field ),
368 'wheel mousewheel DOMMouseScroll': this.onFieldWheel.bind( this, $field )
369 } );
370 $field.val = spanValFunc;
371 } else {
372 maxlength = spec.size;
373 if ( spec.intercalarySize ) {
374 $.each( spec.intercalarySize, reduceFunc );
375 }
376 $field = $( '<input>' ).attr( 'type', 'text' )
377 .attr( {
378 tabindex: disabled ? -1 : 0,
379 size: spec.size,
380 maxlength: maxlength
381 } )
382 .prop( {
383 disabled: disabled,
384 placeholder: placeholder
385 } )
386 .width( sz );
387 $field.on( {
388 keydown: this.onFieldKeyDown.bind( this, $field ),
389 click: this.onFieldClick.bind( this, $field ),
390 focus: this.onFieldFocus.bind( this, $field ),
391 blur: this.onFieldBlur.bind( this, $field ),
392 change: this.onFieldChange.bind( this, $field ),
393 'wheel mousewheel DOMMouseScroll': this.onFieldWheel.bind( this, $field )
394 } );
395 }
396 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-editField' );
397 } else {
398 $field = $( '<span>' )
399 .width( sz )
400 .data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder', placeholder );
401 if ( spec.type === 'static' ) {
402 $field.text( spec.value );
403 } else {
404 $field.val = spanValFunc;
405 }
406 }
407
408 this.fields.push( $field );
409 $field
410 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-field' )
411 .data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec', spec )
412 .appendTo( this.$fields );
413 }
414
415 if ( this.clearable ) {
416 this.clearButton = new OO.ui.ButtonWidget( {
417 classes: [ 'mw-widgets-datetime-dateTimeInputWidget-field', 'mw-widgets-datetime-dateTimeInputWidget-clearButton' ],
418 framed: false,
419 icon: 'remove',
420 disabled: disabled
421 } ).connect( this, {
422 click: 'onClearClick'
423 } );
424 this.$fields.append( this.clearButton.$element );
425 }
426
427 this.updateFieldsFromValue();
428 };
429
430 /**
431 * Update the UI fields from the current value
432 *
433 * @private
434 */
435 mw.widgets.datetime.DateTimeInputWidget.prototype.updateFieldsFromValue = function () {
436 var i, $field, spec, intercalary, sz,
437 date = this.getValueAsDate();
438
439 if ( date === null ) {
440 this.components = null;
441
442 for ( i = 0; i < this.fields.length; i++ ) {
443 $field = this.fields[ i ];
444 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
445
446 $field
447 .removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid oo-ui-element-hidden' )
448 .val( '' );
449
450 if ( spec.intercalarySize ) {
451 if ( spec.type === 'number' ) {
452 // Numbers ''should'' be the same width. But we need some extra for
453 // IE, apparently.
454 $field.width( ( spec.size * 1.15 ) + 'ch' );
455 } else {
456 // Add a little for padding
457 $field.width( ( spec.size * 1.15 ) + 'ch' );
458 }
459 }
460 }
461
462 this.setFlags( { invalid: this.required } );
463 } else {
464 this.components = this.formatter.getComponentsFromDate( date );
465 intercalary = this.components.intercalary;
466
467 for ( i = 0; i < this.fields.length; i++ ) {
468 $field = this.fields[ i ];
469 $field.removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
470 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
471 if ( spec.type !== 'static' ) {
472 $field.val( spec.formatValue( this.components[ spec.component ] ) );
473 }
474 if ( spec.intercalarySize ) {
475 if ( intercalary && spec.intercalarySize[ intercalary ] !== undefined ) {
476 sz = spec.intercalarySize[ intercalary ];
477 } else {
478 sz = spec.size;
479 }
480 $field.toggleClass( 'oo-ui-element-hidden', sz <= 0 );
481 if ( spec.type === 'number' ) {
482 // Numbers ''should'' be the same width. But we need some extra for
483 // IE, apparently.
484 this.fields[ i ].width( ( sz * 1.15 ) + 'ch' );
485 } else {
486 // Add a little for padding
487 this.fields[ i ].width( ( sz * 1.15 ) + 'ch' );
488 }
489 }
490 }
491
492 this.setFlags( { invalid: date < this.min || date > this.max } );
493 }
494
495 this.$element.toggleClass( 'mw-widgets-datetime-dateTimeInputWidget-empty', date === null );
496 };
497
498 /**
499 * Update the value with data from the UI fields
500 *
501 * @private
502 */
503 mw.widgets.datetime.DateTimeInputWidget.prototype.updateValueFromFields = function () {
504 var i, v, $field, spec, curDate, newDate,
505 components = {},
506 anyInvalid = false,
507 anyEmpty = false,
508 allEmpty = true;
509
510 for ( i = 0; i < this.fields.length; i++ ) {
511 $field = this.fields[ i ];
512 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
513 if ( spec.editable ) {
514 $field.removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
515 v = $field.val();
516 if ( v === '' ) {
517 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
518 anyEmpty = true;
519 } else {
520 allEmpty = false;
521 v = spec.parseValue( v );
522 if ( v === undefined ) {
523 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
524 anyInvalid = true;
525 } else {
526 components[ spec.component ] = v;
527 }
528 }
529 }
530 }
531
532 if ( allEmpty ) {
533 for ( i = 0; i < this.fields.length; i++ ) {
534 this.fields[ i ].removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
535 }
536 } else if ( anyEmpty ) {
537 anyInvalid = true;
538 }
539
540 if ( !anyInvalid ) {
541 curDate = this.getValueAsDate();
542 newDate = this.formatter.getDateFromComponents( components );
543 if ( !curDate || !newDate || curDate.getTime() !== newDate.getTime() ) {
544 this.setValue( newDate );
545 }
546 }
547 };
548
549 /**
550 * Handle change event
551 *
552 * @private
553 */
554 mw.widgets.datetime.DateTimeInputWidget.prototype.onChange = function () {
555 var date;
556
557 this.updateFieldsFromValue();
558
559 if ( this.calendar ) {
560 date = this.getValueAsDate();
561 this.calendar.setSelected( date );
562 if ( date ) {
563 this.calendar.setFocusedDate( date );
564 }
565 }
566 };
567
568 /**
569 * Handle clear button click event
570 *
571 * @private
572 */
573 mw.widgets.datetime.DateTimeInputWidget.prototype.onClearClick = function () {
574 this.blur();
575 this.setValue( '' );
576 };
577
578 /**
579 * Handle click on the widget background
580 *
581 * @private
582 * @param {jQuery.Event} e Click event
583 */
584 mw.widgets.datetime.DateTimeInputWidget.prototype.onHandleClick = function () {
585 this.focus();
586 };
587
588 /**
589 * Handle key down events on our field inputs.
590 *
591 * @private
592 * @param {jQuery} $field
593 * @param {jQuery.Event} e Key down event
594 */
595 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldKeyDown = function ( $field, e ) {
596 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
597
598 if ( !this.isDisabled() ) {
599 switch ( e.which ) {
600 case OO.ui.Keys.ENTER:
601 case OO.ui.Keys.SPACE:
602 if ( spec.type === 'boolean' ) {
603 this.setValue(
604 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, 1, 'wrap' )
605 );
606 return false;
607 } else if ( spec.type === 'toggleLocal' ) {
608 this.formatter.toggleLocal();
609 }
610 break;
611
612 case OO.ui.Keys.UP:
613 case OO.ui.Keys.DOWN:
614 if ( spec.type === 'toggleLocal' ) {
615 this.formatter.toggleLocal();
616 } else {
617 this.setValue(
618 this.formatter.adjustComponent( this.getValueAsDate(), spec.component,
619 e.keyCode === OO.ui.Keys.UP ? -1 : 1, 'wrap' )
620 );
621 }
622 if ( $field.is( ':input' ) ) {
623 $field.select();
624 }
625 return false;
626 }
627 }
628 };
629
630 /**
631 * Handle focus events on our field inputs.
632 *
633 * @private
634 * @param {jQuery} $field
635 * @param {jQuery.Event} e Focus event
636 */
637 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldFocus = function ( $field ) {
638 if ( !this.isDisabled() ) {
639 if ( this.getValueAsDate() === null ) {
640 this.setValue( this.formatter.getDefaultDate() );
641 }
642 if ( $field.is( ':input' ) ) {
643 $field.select();
644 }
645
646 if ( this.calendar ) {
647 this.calendar.toggle( true );
648 }
649 }
650 };
651
652 /**
653 * Handle click events on our field inputs.
654 *
655 * @private
656 * @param {jQuery} $field
657 * @param {jQuery.Event} e Click event
658 */
659 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldClick = function ( $field ) {
660 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
661
662 if ( !this.isDisabled() ) {
663 if ( spec.type === 'boolean' ) {
664 this.setValue(
665 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, 1, 'wrap' )
666 );
667 } else if ( spec.type === 'toggleLocal' ) {
668 this.formatter.toggleLocal();
669 }
670 }
671 };
672
673 /**
674 * Handle blur events on our field inputs.
675 *
676 * @private
677 * @param {jQuery} $field
678 * @param {jQuery.Event} e Blur event
679 */
680 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldBlur = function ( $field ) {
681 var v, date,
682 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
683
684 this.updateValueFromFields();
685
686 // Normalize
687 date = this.getValueAsDate();
688 if ( !date ) {
689 $field.val( '' );
690 } else {
691 v = spec.formatValue( this.formatter.getComponentsFromDate( date )[ spec.component ] );
692 if ( v !== $field.val() ) {
693 $field.val( v );
694 }
695 }
696 };
697
698 /**
699 * Handle change events on our field inputs.
700 *
701 * @private
702 * @param {jQuery} $field
703 * @param {jQuery.Event} e Change event
704 */
705 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldChange = function () {
706 this.updateValueFromFields();
707 };
708
709 /**
710 * Handle wheel events on our field inputs.
711 *
712 * @private
713 * @param {jQuery} $field
714 * @param {jQuery.Event} e Change event
715 */
716 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldWheel = function ( $field, e ) {
717 var delta = 0,
718 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
719
720 if ( this.isDisabled() ) {
721 return;
722 }
723
724 // Standard 'wheel' event
725 if ( e.originalEvent.deltaMode !== undefined ) {
726 this.sawWheelEvent = true;
727 }
728 if ( e.originalEvent.deltaY ) {
729 delta = -e.originalEvent.deltaY;
730 } else if ( e.originalEvent.deltaX ) {
731 delta = e.originalEvent.deltaX;
732 }
733
734 // Non-standard events
735 if ( !this.sawWheelEvent ) {
736 if ( e.originalEvent.wheelDeltaX ) {
737 delta = -e.originalEvent.wheelDeltaX;
738 } else if ( e.originalEvent.wheelDeltaY ) {
739 delta = e.originalEvent.wheelDeltaY;
740 } else if ( e.originalEvent.wheelDelta ) {
741 delta = e.originalEvent.wheelDelta;
742 } else if ( e.originalEvent.detail ) {
743 delta = -e.originalEvent.detail;
744 }
745 }
746
747 if ( delta && spec ) {
748 if ( spec.type === 'toggleLocal' ) {
749 this.formatter.toggleLocal();
750 } else {
751 this.setValue(
752 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, delta < 0 ? -1 : 1, 'wrap' )
753 );
754 }
755 return false;
756 }
757 };
758
759 /**
760 * Handle calendar change event
761 *
762 * @private
763 */
764 mw.widgets.datetime.DateTimeInputWidget.prototype.onCalendarChange = function () {
765 var curDate = this.getValueAsDate(),
766 newDate = this.calendar.getSelected()[ 0 ];
767
768 if ( newDate ) {
769 if ( !curDate || newDate.getTime() !== curDate.getTime() ) {
770 this.setValue( newDate );
771 }
772 }
773 };
774
775 /**
776 * @inheritdoc
777 * @private
778 */
779 mw.widgets.datetime.DateTimeInputWidget.prototype.getInputElement = function () {
780 return $( '<input>' ).attr( 'type', 'hidden' );
781 };
782
783 /**
784 * @inheritdoc
785 */
786 mw.widgets.datetime.DateTimeInputWidget.prototype.setDisabled = function ( disabled ) {
787 mw.widgets.datetime.DateTimeInputWidget[ 'super' ].prototype.setDisabled.call( this, disabled );
788
789 // Flag all our fields as disabled
790 if ( this.$fields ) {
791 this.$fields.find( 'input' ).prop( 'disabled', this.isDisabled() );
792 this.$fields.find( '[tabindex]' ).attr( 'tabindex', this.isDisabled() ? -1 : 0 );
793 }
794
795 if ( this.clearButton ) {
796 this.clearButton.setDisabled( disabled );
797 }
798
799 return this;
800 };
801
802 /**
803 * @inheritdoc
804 */
805 mw.widgets.datetime.DateTimeInputWidget.prototype.focus = function () {
806 if ( !this.$fields.find( document.activeElement ).length ) {
807 this.$fields.find( '.mw-widgets-datetime-dateTimeInputWidget-editField' ).first().focus();
808 }
809 return this;
810 };
811
812 /**
813 * @inheritdoc
814 */
815 mw.widgets.datetime.DateTimeInputWidget.prototype.blur = function () {
816 this.$fields.find( document.activeElement ).blur();
817 return this;
818 };
819
820 /**
821 * @inheritdoc
822 */
823 mw.widgets.datetime.DateTimeInputWidget.prototype.simulateLabelClick = function () {
824 this.focus();
825 };
826
827 }( jQuery, mediaWiki ) );