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