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