mw.widgets.DateInputWidget: Allow not having a date selected
authorBartosz Dziewoński <matma.rex@gmail.com>
Thu, 30 Jul 2015 22:36:19 +0000 (00:36 +0200)
committerBartosz Dziewoński <matma.rex@gmail.com>
Fri, 31 Jul 2015 18:53:42 +0000 (18:53 +0000)
* Allow empty value, that is, no date being selected. Display
  an appropriate label when that is the case.
* Start with no date selected, rather than today's date.
* When the field is empty and gets focus, set the date to today, on
  the assumption that this is likely what the user wants. Permit
  emptying the field, though.
* Highlight today's date on the calendar.

Bonus cleanup:

* Update the UI from #setValue, immediately when needed.
* Improve validation of incoming values.
* Correct some documentation.
* Correct some bad copy-paste in styles which caused disabled widget
  to not display correctly.

Change-Id: I7a1f7ff20eb6fc21ea59ecfe48deb9305c8e29e8

languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.widgets/mw.widgets.CalendarWidget.less
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.less

index 508e254..f59df68 100644 (file)
        "special-characters-title-endash": "en dash",
        "special-characters-title-emdash": "em dash",
        "special-characters-title-minus": "minus sign",
+       "mw-widgets-dateinput-no-date": "No date selected",
        "mw-widgets-titleinput-description-new-page": "page does not exist yet",
        "mw-widgets-titleinput-description-redirect": "redirect to $1"
 }
index a0cfab2..53f5828 100644 (file)
        "special-characters-title-endash": "Title tooltip for the en dash character (–); See https://en.wikipedia.org/wiki/Dash",
        "special-characters-title-emdash": "Title tooltip for the em dash character (—); See https://en.wikipedia.org/wiki/Dash",
        "special-characters-title-minus": "Title tooltip for the minus sign character (−), not to be confused with a hyphen",
+       "mw-widgets-dateinput-no-date": "Label of a date input field when no date has been selected.",
        "mw-widgets-titleinput-description-new-page": "Description label for a new page in the title input widget.",
        "mw-widgets-titleinput-description-redirect": "Description label for a redirect in the title input widget."
 }
index fb0971e..a0c0744 100644 (file)
@@ -1809,6 +1809,7 @@ return array(
                        'oojs-ui',
                ),
                'messages' => array(
+                       'mw-widgets-dateinput-no-date',
                        'mw-widgets-titleinput-description-new-page',
                        'mw-widgets-titleinput-description-redirect',
                ),
index 276bc65..582a316 100644 (file)
 }
 
 .mw-widget-calendarWidget-day-today {
-       // Intentionally left blank.
+       border: 1px solid #3787fb;
+       border-radius: ((@calendarHeight / 7) / 2);
+       margin: -1px;
 }
 
 .mw-widget-calendarWidget-item-selected {
        &.mw-widget-calendarWidget-day,
        &.mw-widget-calendarWidget-day-heading {
                border-radius: ((@calendarHeight / 7) / 4);
+               // Hide the border from .mw-widget-calendarWidget-day-today
+               border: 0;
+               margin: 0;
        }
 
        &.mw-widget-calendarWidget-month {
index 1820dda..3888fc7 100644 (file)
@@ -16,8 +16,8 @@
         * @constructor
         * @param {Object} [config] Configuration options
         * @cfg {string} [precision='day'] Date precision to use, 'day' or 'month'
-        * @cfg {string|null} [date=null] Day or month date (depending on `precision`), in the
-        *     format 'YYYY-MM-DD' or 'YYYY-MM'. When null, defaults to current date.
+        * @cfg {string} [value] Day or month date (depending on `precision`), in the format 'YYYY-MM-DD'
+        *     or 'YYYY-MM'. If not given or empty string, no date is selected.
         * @cfg {string} [inputFormat] Date format string to use for the textual input field. Displayed
         *     while the widget is active, and the user can type in a date in this format. Should be short
         *     and easy to type. When not given, defaults to 'YYYY-MM-DD' or 'YYYY-MM', depending on
@@ -75,6 +75,7 @@
                        .addClass( 'mw-widget-dateInputWidget' )
                        .append( this.handle.$element, this.textInput.$element, this.calendar.$element );
                // Set handle label and hide stuff
+               this.updateUI();
                this.deactivate();
        };
 
                        value = this.textInput.getValue();
                this.inTextInput++;
                this.textInput.isValid().done( function ( valid ) {
-                       if ( valid ) {
+                       if ( value === '' ) {
+                               // No date selected
+                               widget.setValue( '' );
+                       } else if ( valid ) {
                                // Well-formed date value, parse and set it
                                var mom = moment( value, widget.getInputFormat() );
                                // Use English locale to avoid number formatting
         * @inheritdoc
         */
        mw.widgets.DateInputWidget.prototype.setValue = function ( value ) {
-               if ( value === undefined || value === null ) {
-                       // Default to today
-                       value = this.calendar.getDate();
-               }
-
                var oldValue = this.value;
 
+               if ( !moment( value, this.getInternalFormat() ).isValid() ) {
+                       value = '';
+               }
+
                mw.widgets.DateInputWidget.parent.prototype.setValue.call( this, value );
 
                if ( this.value !== oldValue ) {
-                       if ( !this.inCalendar ) {
-                               this.calendar.setDate( this.getValue() );
-                       }
-                       if ( !this.inTextInput ) {
-                               this.textInput.setValue( this.getMoment().format( this.getInputFormat() ) );
-                       }
+                       this.updateUI();
                }
 
                return this;
        };
 
        /**
-        * Deactivate this input field for data entry. Opens the calendar and shows the text field.
+        * @inheritdoc
+        */
+       mw.widgets.DateInputWidget.prototype.focus = function () {
+               this.activate();
+               return this;
+       };
+
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.DateInputWidget.prototype.blur = function () {
+               this.deactivate();
+               return this;
+       };
+
+       /**
+        * Update the contents of the label, text input and status of calendar to reflect selected value.
         *
         * @private
         */
-       mw.widgets.DateInputWidget.prototype.deactivate = function () {
-               this.textInput.setValue( this.getMoment().format( this.getInputFormat() ) );
-               this.calendar.setDate( this.getValue() );
-               this.handle.setLabel( this.getMoment().format( this.getDisplayFormat() ) );
+       mw.widgets.DateInputWidget.prototype.updateUI = function () {
+               if ( this.getValue() === '' ) {
+                       this.textInput.setValue( '' );
+                       this.calendar.setDate( null );
+                       this.handle.setLabel( mw.msg( 'mw-widgets-dateinput-no-date' ) );
+                       this.$element.addClass( 'mw-widget-dateInputWidget-empty' );
+               } else {
+                       if ( !this.inTextInput ) {
+                               this.textInput.setValue( this.getMoment().format( this.getInputFormat() ) );
+                       }
+                       if ( !this.inCalendar ) {
+                               this.calendar.setDate( this.getValue() );
+                       }
+                       this.handle.setLabel( this.getMoment().format( this.getDisplayFormat() ) );
+                       this.$element.removeClass( 'mw-widget-dateInputWidget-empty' );
+               }
+       };
 
+       /**
+        * Deactivate this input field for data entry. Closes the calendar and hides the text field.
+        *
+        * @private
+        */
+       mw.widgets.DateInputWidget.prototype.deactivate = function () {
                this.$element.removeClass( 'mw-widget-dateInputWidget-active' );
                this.handle.toggle( true );
                this.textInput.toggle( false );
        };
 
        /**
-        * Activate this input field for data entry. Closes the calendar and hides the text field.
+        * Activate this input field for data entry. Opens the calendar and shows the text field.
         *
         * @private
         */
        mw.widgets.DateInputWidget.prototype.activate = function () {
-               this.setValue( this.getValue() );
+               if ( this.getValue() === '' ) {
+                       // Setting today's date is probably more helpful than leaving the widget empty? We could just
+                       // display the placeholder and leave it there, but it's likely that at least the year will be
+                       // the same as today's.
+
+                       // Use English locale to avoid number formatting
+                       this.setValue( moment().locale( 'en' ).format( this.getInternalFormat() ) );
+               }
 
                this.$element.addClass( 'mw-widget-dateInputWidget-active' );
                this.handle.toggle( false );
 
        /**
         * @private
-        * @param {string} date Date string, must be in 'YYYY-MM-DD' or 'YYYY-MM' format to be valid
+        * @param {string} date Date string, to be valid, must be empty (no date selected) or in
+        *     'YYYY-MM-DD' or 'YYYY-MM' format to be valid
         */
        mw.widgets.DateInputWidget.prototype.validateDate = function ( date ) {
+               if ( date === '' ) {
+                       return true;
+               }
+
                // "Half-strict mode": for example, for the format 'YYYY-MM-DD', 2015-1-3 instead of 2015-01-03
                // is okay, but 2015-01 isn't, and neither is 2015-01-foo. Use Moment's "fuzzy" mode and check
                // parsing flags for the details (stoled from implementation of #isValid).
index 33e3406..f87869c 100644 (file)
@@ -39,7 +39,7 @@
                .oo-ui-box-sizing(border-box);
        }
 
-       &.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
+       &.oo-ui-widget-disabled .mw-widget-dateInputWidget-handle {
                cursor: default;
        }
 
                border: 1px solid #ccc;
                border-radius: 0.1em;
                line-height: 1.275em;
-
-               &:hover {
-                       border-color: #347bff;
-               }
        }
 
        > .oo-ui-textInputWidget input {
                }
        }
 
-       &:hover .oo-ui-dropdownWidget-handle {
-               border-color: #aaa;
+       &.oo-ui-widget-enabled {
+               .mw-widget-dateInputWidget-handle:hover {
+                       border-color: #347bff;
+               }
        }
 
        &.oo-ui-widget-disabled {
-               .oo-ui-dropdownWidget-handle {
+               .mw-widget-dateInputWidget-handle {
                        color: #ccc;
                        text-shadow: 0 1px 1px #fff;
                        border-color: #ddd;
                        background-color: #f3f3f3;
                }
        }
+
+       &-empty {
+               .mw-widget-dateInputWidget-handle {
+                       color: #ccc;
+               }
+       }
 }