Merge "DateTimeInputWidget: Only show calendar when focusing date components, not...
[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 // 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 * Convert a date string to a Date
188 *
189 * @private
190 * @param {string} value
191 * @return {Date|null}
192 */
193 mw.widgets.datetime.DateTimeInputWidget.prototype.parseDateValue = function ( value ) {
194 var date, m;
195
196 value = String( value );
197 switch ( this.type ) {
198 case 'date':
199 value = value + 'T00:00:00Z';
200 break;
201 case 'time':
202 value = '1970-01-01T' + value + 'Z';
203 break;
204 }
205 m = /^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?Z$/.exec( value );
206 if ( m ) {
207 if ( m[ 7 ] ) {
208 while ( m[ 7 ].length < 3 ) {
209 m[ 7 ] += '0';
210 }
211 } else {
212 m[ 7 ] = 0;
213 }
214 date = new Date();
215 date.setUTCFullYear( m[ 1 ], m[ 2 ] - 1, m[ 3 ] );
216 date.setUTCHours( m[ 4 ], m[ 5 ], m[ 6 ], m[ 7 ] );
217 if ( date.getTime() < -62167219200000 || date.getTime() > 253402300799999 ||
218 date.getUTCFullYear() !== +m[ 1 ] ||
219 date.getUTCMonth() + 1 !== +m[ 2 ] ||
220 date.getUTCDate() !== +m[ 3 ] ||
221 date.getUTCHours() !== +m[ 4 ] ||
222 date.getUTCMinutes() !== +m[ 5 ] ||
223 date.getUTCSeconds() !== +m[ 6 ] ||
224 date.getUTCMilliseconds() !== +m[ 7 ]
225 ) {
226 date = null;
227 }
228 } else {
229 date = null;
230 }
231
232 return date;
233 };
234
235 /**
236 * @inheritdoc
237 */
238 mw.widgets.datetime.DateTimeInputWidget.prototype.cleanUpValue = function ( value ) {
239 var date, pad;
240
241 if ( value === '' ) {
242 return '';
243 }
244
245 if ( value instanceof Date ) {
246 date = value;
247 } else {
248 date = this.parseDateValue( value );
249 }
250
251 if ( date instanceof Date ) {
252 pad = function ( v, l ) {
253 v = String( v );
254 while ( v.length < l ) {
255 v = '0' + v;
256 }
257 return v;
258 };
259
260 switch ( this.type ) {
261 case 'date':
262 value = pad( date.getUTCFullYear(), 4 ) +
263 '-' + pad( date.getUTCMonth() + 1, 2 ) +
264 '-' + pad( date.getUTCDate(), 2 );
265 break;
266
267 case 'time':
268 value = pad( date.getUTCHours(), 2 ) +
269 ':' + pad( date.getUTCMinutes(), 2 ) +
270 ':' + pad( date.getUTCSeconds(), 2 ) +
271 '.' + pad( date.getUTCMilliseconds(), 3 );
272 value = value.replace( /\.?0+$/, '' );
273 break;
274
275 default:
276 value = date.toISOString();
277 break;
278 }
279 } else {
280 value = '';
281 }
282
283 return value;
284 };
285
286 /**
287 * Get the value of the input as a Date object
288 *
289 * @return {Date|null}
290 */
291 mw.widgets.datetime.DateTimeInputWidget.prototype.getValueAsDate = function () {
292 return this.parseDateValue( this.getValue() );
293 };
294
295 /**
296 * Set up the UI fields
297 *
298 * @private
299 */
300 mw.widgets.datetime.DateTimeInputWidget.prototype.setupFields = function () {
301 var i, $field, spec, placeholder, sz, maxlength,
302 spanValFunc = function ( v ) {
303 if ( v === undefined ) {
304 return this.data( 'mw-widgets-datetime-dateTimeInputWidget-value' );
305 } else {
306 v = String( v );
307 this.data( 'mw-widgets-datetime-dateTimeInputWidget-value', v );
308 if ( v === '' ) {
309 v = this.data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder' );
310 }
311 this.text( v );
312 return this;
313 }
314 },
315 reduceFunc = function ( k, v ) {
316 maxlength = Math.max( maxlength, v );
317 },
318 disabled = this.isDisabled(),
319 specs = this.formatter.getFieldSpec();
320
321 this.$fields.empty();
322 this.clearButton = null;
323 this.fields = [];
324
325 for ( i = 0; i < specs.length; i++ ) {
326 spec = specs[ i ];
327 if ( typeof spec === 'string' ) {
328 $( '<span>' )
329 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-field' )
330 .text( spec )
331 .appendTo( this.$fields );
332 continue;
333 }
334
335 placeholder = '';
336 while ( placeholder.length < spec.size ) {
337 placeholder += '_';
338 }
339
340 if ( spec.type === 'number' ) {
341 // Numbers ''should'' be the same width. But we need some extra for
342 // IE, apparently.
343 sz = ( spec.size * 1.15 ) + 'ch';
344 } else {
345 // Add a little for padding
346 sz = ( spec.size * 1.15 ) + 'ch';
347 }
348 if ( spec.editable && spec.type !== 'static' ) {
349 if ( spec.type === 'boolean' || spec.type === 'toggleLocal' ) {
350 $field = $( '<span>' )
351 .attr( {
352 tabindex: disabled ? -1 : 0
353 } )
354 .width( sz )
355 .data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder', placeholder );
356 $field.on( {
357 keydown: this.onFieldKeyDown.bind( this, $field ),
358 focus: this.onFieldFocus.bind( this, $field ),
359 click: this.onFieldClick.bind( this, $field ),
360 'wheel mousewheel DOMMouseScroll': this.onFieldWheel.bind( this, $field )
361 } );
362 $field.val = spanValFunc;
363 } else {
364 maxlength = spec.size;
365 if ( spec.intercalarySize ) {
366 $.each( spec.intercalarySize, reduceFunc );
367 }
368 $field = $( '<input>' ).attr( 'type', 'text' )
369 .attr( {
370 tabindex: disabled ? -1 : 0,
371 size: spec.size,
372 maxlength: maxlength
373 } )
374 .prop( {
375 disabled: disabled,
376 placeholder: placeholder
377 } )
378 .width( sz );
379 $field.on( {
380 keydown: this.onFieldKeyDown.bind( this, $field ),
381 click: this.onFieldClick.bind( this, $field ),
382 focus: this.onFieldFocus.bind( this, $field ),
383 blur: this.onFieldBlur.bind( this, $field ),
384 change: this.onFieldChange.bind( this, $field ),
385 'wheel mousewheel DOMMouseScroll': this.onFieldWheel.bind( this, $field )
386 } );
387 }
388 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-editField' );
389 } else {
390 $field = $( '<span>' )
391 .width( sz )
392 .data( 'mw-widgets-datetime-dateTimeInputWidget-placeholder', placeholder );
393 if ( spec.type !== 'static' ) {
394 $field.prop( 'tabIndex', -1 );
395 $field.on( 'focus', this.onFieldFocus.bind( this, $field ) );
396 }
397 if ( spec.type === 'static' ) {
398 $field.text( spec.value );
399 } else {
400 $field.val = spanValFunc;
401 }
402 }
403
404 this.fields.push( $field );
405 $field
406 .addClass( 'mw-widgets-datetime-dateTimeInputWidget-field' )
407 .data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec', spec )
408 .appendTo( this.$fields );
409 }
410
411 if ( this.clearable ) {
412 this.clearButton = new OO.ui.ButtonWidget( {
413 classes: [ 'mw-widgets-datetime-dateTimeInputWidget-field', 'mw-widgets-datetime-dateTimeInputWidget-clearButton' ],
414 framed: false,
415 icon: 'remove',
416 disabled: disabled
417 } ).connect( this, {
418 click: 'onClearClick'
419 } );
420 this.$fields.append( this.clearButton.$element );
421 }
422
423 this.updateFieldsFromValue();
424 };
425
426 /**
427 * Update the UI fields from the current value
428 *
429 * @private
430 */
431 mw.widgets.datetime.DateTimeInputWidget.prototype.updateFieldsFromValue = function () {
432 var i, $field, spec, intercalary, sz,
433 date = this.getValueAsDate();
434
435 if ( date === null ) {
436 this.components = null;
437
438 for ( i = 0; i < this.fields.length; i++ ) {
439 $field = this.fields[ i ];
440 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
441
442 $field
443 .removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid oo-ui-element-hidden' )
444 .val( '' );
445
446 if ( spec.intercalarySize ) {
447 if ( spec.type === 'number' ) {
448 // Numbers ''should'' be the same width. But we need some extra for
449 // IE, apparently.
450 $field.width( ( spec.size * 1.15 ) + 'ch' );
451 } else {
452 // Add a little for padding
453 $field.width( ( spec.size * 1.15 ) + 'ch' );
454 }
455 }
456 }
457
458 this.setFlags( { invalid: this.required } );
459 } else {
460 this.components = this.formatter.getComponentsFromDate( date );
461 intercalary = this.components.intercalary;
462
463 for ( i = 0; i < this.fields.length; i++ ) {
464 $field = this.fields[ i ];
465 $field.removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
466 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
467 if ( spec.type !== 'static' ) {
468 $field.val( spec.formatValue( this.components[ spec.component ] ) );
469 }
470 if ( spec.intercalarySize ) {
471 if ( intercalary && spec.intercalarySize[ intercalary ] !== undefined ) {
472 sz = spec.intercalarySize[ intercalary ];
473 } else {
474 sz = spec.size;
475 }
476 $field.toggleClass( 'oo-ui-element-hidden', sz <= 0 );
477 if ( spec.type === 'number' ) {
478 // Numbers ''should'' be the same width. But we need some extra for
479 // IE, apparently.
480 this.fields[ i ].width( ( sz * 1.15 ) + 'ch' );
481 } else {
482 // Add a little for padding
483 this.fields[ i ].width( ( sz * 1.15 ) + 'ch' );
484 }
485 }
486 }
487
488 this.setFlags( { invalid: date < this.min || date > this.max } );
489 }
490
491 this.$element.toggleClass( 'mw-widgets-datetime-dateTimeInputWidget-empty', date === null );
492 };
493
494 /**
495 * Update the value with data from the UI fields
496 *
497 * @private
498 */
499 mw.widgets.datetime.DateTimeInputWidget.prototype.updateValueFromFields = function () {
500 var i, v, $field, spec, curDate, newDate,
501 components = {},
502 anyInvalid = false,
503 anyEmpty = false,
504 allEmpty = true;
505
506 for ( i = 0; i < this.fields.length; i++ ) {
507 $field = this.fields[ i ];
508 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
509 if ( spec.editable ) {
510 $field.removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
511 v = $field.val();
512 if ( v === '' ) {
513 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
514 anyEmpty = true;
515 } else {
516 allEmpty = false;
517 v = spec.parseValue( v );
518 if ( v === undefined ) {
519 $field.addClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
520 anyInvalid = true;
521 } else {
522 components[ spec.component ] = v;
523 }
524 }
525 }
526 }
527
528 if ( allEmpty ) {
529 for ( i = 0; i < this.fields.length; i++ ) {
530 this.fields[ i ].removeClass( 'mw-widgets-datetime-dateTimeInputWidget-invalid' );
531 }
532 } else if ( anyEmpty ) {
533 anyInvalid = true;
534 }
535
536 if ( !anyInvalid ) {
537 curDate = this.getValueAsDate();
538 newDate = this.formatter.getDateFromComponents( components );
539 if ( !curDate || !newDate || curDate.getTime() !== newDate.getTime() ) {
540 this.setValue( newDate );
541 }
542 }
543 };
544
545 /**
546 * Handle change event
547 *
548 * @private
549 */
550 mw.widgets.datetime.DateTimeInputWidget.prototype.onChange = function () {
551 var date;
552
553 this.updateFieldsFromValue();
554
555 if ( this.calendar ) {
556 date = this.getValueAsDate();
557 this.calendar.setSelected( date );
558 if ( date ) {
559 this.calendar.setFocusedDate( date );
560 }
561 }
562 };
563
564 /**
565 * Handle clear button click event
566 *
567 * @private
568 */
569 mw.widgets.datetime.DateTimeInputWidget.prototype.onClearClick = function () {
570 this.blur();
571 this.setValue( '' );
572 };
573
574 /**
575 * Handle click on the widget background
576 *
577 * @private
578 * @param {jQuery.Event} e Click event
579 */
580 mw.widgets.datetime.DateTimeInputWidget.prototype.onHandleClick = function () {
581 this.focus();
582 };
583
584 /**
585 * Handle key down events on our field inputs.
586 *
587 * @private
588 * @param {jQuery} $field
589 * @param {jQuery.Event} e Key down event
590 * @return {boolean} False to cancel the default event
591 */
592 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldKeyDown = function ( $field, e ) {
593 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
594
595 if ( !this.isDisabled() ) {
596 switch ( e.which ) {
597 case OO.ui.Keys.ENTER:
598 case OO.ui.Keys.SPACE:
599 if ( spec.type === 'boolean' ) {
600 this.setValue(
601 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, 1, 'wrap' )
602 );
603 return false;
604 } else if ( spec.type === 'toggleLocal' ) {
605 this.formatter.toggleLocal();
606 }
607 break;
608
609 case OO.ui.Keys.UP:
610 case OO.ui.Keys.DOWN:
611 if ( spec.type === 'toggleLocal' ) {
612 this.formatter.toggleLocal();
613 } else {
614 this.setValue(
615 this.formatter.adjustComponent( this.getValueAsDate(), spec.component,
616 e.keyCode === OO.ui.Keys.UP ? -1 : 1, 'wrap' )
617 );
618 }
619 if ( $field.is( ':input' ) ) {
620 $field.select();
621 }
622 return false;
623 }
624 }
625 };
626
627 /**
628 * Handle focus events on our field inputs.
629 *
630 * @private
631 * @param {jQuery} $field
632 * @param {jQuery.Event} e Focus event
633 */
634 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldFocus = function ( $field ) {
635 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
636
637 if ( !this.isDisabled() ) {
638 if ( this.getValueAsDate() === null ) {
639 this.setValue( this.formatter.getDefaultDate() );
640 }
641 if ( $field.is( ':input' ) ) {
642 $field.select();
643 }
644
645 if ( this.calendar ) {
646 this.calendar.toggle( !!spec.calendarComponent );
647 }
648 }
649 };
650
651 /**
652 * Handle click events on our field inputs.
653 *
654 * @private
655 * @param {jQuery} $field
656 * @param {jQuery.Event} e Click event
657 */
658 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldClick = function ( $field ) {
659 var spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
660
661 if ( !this.isDisabled() ) {
662 if ( spec.type === 'boolean' ) {
663 this.setValue(
664 this.formatter.adjustComponent( this.getValueAsDate(), spec.component, 1, 'wrap' )
665 );
666 } else if ( spec.type === 'toggleLocal' ) {
667 this.formatter.toggleLocal();
668 }
669 }
670 };
671
672 /**
673 * Handle blur events on our field inputs.
674 *
675 * @private
676 * @param {jQuery} $field
677 * @param {jQuery.Event} e Blur event
678 */
679 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldBlur = function ( $field ) {
680 var v, date,
681 spec = $field.data( 'mw-widgets-datetime-dateTimeInputWidget-fieldSpec' );
682
683 this.updateValueFromFields();
684
685 // Normalize
686 date = this.getValueAsDate();
687 if ( !date ) {
688 $field.val( '' );
689 } else {
690 v = spec.formatValue( this.formatter.getComponentsFromDate( date )[ spec.component ] );
691 if ( v !== $field.val() ) {
692 $field.val( v );
693 }
694 }
695 };
696
697 /**
698 * Handle change events on our field inputs.
699 *
700 * @private
701 * @param {jQuery} $field
702 * @param {jQuery.Event} e Change event
703 */
704 mw.widgets.datetime.DateTimeInputWidget.prototype.onFieldChange = function () {
705 this.updateValueFromFields();
706 };
707
708 /**
709 * Handle wheel events on our field inputs.
710 *
711 * @private
712 * @param {jQuery} $field
713 * @param {jQuery.Event} e Change event
714 * @return {boolean} False to cancel the default 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 ) );