jenkins-bot has submitted this change and it was merged.

Change subject: Create a combo box widget
......................................................................


Create a combo box widget

Changes:

TextInputWidget.js
* Added optional icon and indicator using mixin, replacing the ad-hoc
  icon implementation
* Added icon and indicator events which fire when the icon or indicator
  is clicked

MenuWidget.js
* Fixed toggle to actually toggle

ComboBoxWidget.js
* New class, a mixture of an indicated TextInputWidget and MenuWidget.
  Provides a drop-down type menu while allowing the user to specify a
  custom value. Intended to be used for VE's reference group input,
  among other things.

GroupElement.js
* Added isEmpty method for convenience

Bug: 61573
Change-Id: Ic5b939256eda6e0fa6d9f5c9ce4127abec499292
---
M build/modules.json
M demos/widgets.js
M src/elements/GroupElement.js
A src/styles/widgets/ComboBoxWidget.less
M src/styles/widgets/TextInputWidget.less
A src/themes/apex/widgets/ComboBoxWidget.less
M src/themes/apex/widgets/TextInputWidget.less
A src/widgets/ComboBoxWidget.js
M src/widgets/MenuWidget.js
M src/widgets/TextInputWidget.js
10 files changed, 372 insertions(+), 75 deletions(-)

Approvals:
  Catrope: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/build/modules.json b/build/modules.json
index 23c4660..311789c 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -67,6 +67,8 @@
                        "src/widgets/InputWidget.js",
                                "src/widgets/CheckboxInputWidget.js",
                                "src/widgets/TextInputWidget.js",
+                                       "src/widgets/ComboBoxWidget.js",
+                                               
"src/widgets/ComboBoxInputWidget.js",
                        "src/widgets/LabelWidget.js",
                        "src/widgets/OptionWidget.js",
                                "src/widgets/DecoratedOptionWidget.js",
@@ -115,6 +117,7 @@
                        "src/styles/widgets/OptionWidget.less",
                        "src/styles/widgets/DecoratedOptionWidget.less",
                        "src/styles/widgets/MenuWidget.less",
+                       "src/styles/widgets/ComboBoxWidget.less",
                        "src/styles/widgets/PopupWidget.less",
                        "src/styles/widgets/ButtonGroupWidget.less",
                        "src/styles/widgets/ButtonOptionWidget.less",
@@ -160,6 +163,7 @@
                        "src/themes/apex/widgets/ButtonGroupWidget.less",
                        "src/themes/apex/widgets/ButtonOptionWidget.less",
                        "src/themes/apex/widgets/ButtonSelectWidget.less",
+                       "src/themes/apex/widgets/ComboBoxWidget.less",
                        "src/themes/apex/widgets/InlineMenuWidget.less",
                        "src/themes/apex/widgets/MenuItemWidget.less",
                        "src/themes/apex/widgets/MenuSectionItemWidget.less",
diff --git a/demos/widgets.js b/demos/widgets.js
index 004afa3..8dbd57c 100644
--- a/demos/widgets.js
+++ b/demos/widgets.js
@@ -394,61 +394,6 @@
                                }
                        ),
                        new OO.ui.FieldLayout(
-                               new OO.ui.TextInputWidget( { value: 'Text 
input' } ),
-                               {
-                                       label: 'TextInputWidget',
-                                       align: 'top'
-                               }
-                       ),
-                       new OO.ui.FieldLayout(
-                               new OO.ui.TextInputWidget( { placeholder: 
'Placeholder' } ),
-                               {
-                                       label: 'TextInputWidget (placeholder)',
-                                       align: 'top'
-                               }
-                       ),
-                       new OO.ui.FieldLayout(
-                               new OO.ui.TextInputWidget( {
-                                       value: 'Readonly',
-                                       readOnly: true
-                               } ),
-                               {
-                                       label: 'TextInputWidget (readonly)',
-                                       align: 'top'
-                               }
-                       ),
-                       new OO.ui.FieldLayout(
-                               new OO.ui.TextInputWidget( {
-                                       value: 'Disabled',
-                                       disabled: true
-                               } ),
-                               {
-                                       label: 'TextInputWidget (disabled)',
-                                       align: 'top'
-                               }
-                       ),
-                       new OO.ui.FieldLayout(
-                               new OO.ui.TextInputWidget( {
-                                       multiline: true,
-                                       value: 'Multiline'
-                               } ),
-                               {
-                                       label: 'TextInputWidget (multiline)',
-                                       align: 'top'
-                               }
-                       ),
-                       new OO.ui.FieldLayout(
-                               new OO.ui.TextInputWidget( {
-                                       multiline: true,
-                                       autosize: true,
-                                       value: 'Autosize'
-                               } ),
-                               {
-                                       label: 'TextInputWidget (autosize)',
-                                       align: 'top'
-                               }
-                       ),
-                       new OO.ui.FieldLayout(
                                new OO.ui.InlineMenuWidget( {
                                        label: 'Select one',
                                        align: 'top',
@@ -532,6 +477,117 @@
                                        label: 'InlineMenuWidget (disabled)',
                                        align: 'top'
                                }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.ComboBoxWidget( {
+                                       menu: {
+                                               items: [
+                                                       new 
OO.ui.MenuItemWidget( 'asd', { label: 'Label for asd' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'fgh', { label: 'Label for fgh' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'jkl', { label: 'Label for jkl' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'zxc', { label: 'Label for zxc' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'vbn', { label: 'Label for vbn' } )
+                                               ]
+                                       }
+                               } ),
+                               {
+                                       label: 'ComboBoxWidget',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.ComboBoxWidget( {
+                                       disabled: true,
+                                       menu: {
+                                               items: [
+                                                       new 
OO.ui.MenuItemWidget( 'asd', { label: 'Label for asd' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'fgh', { label: 'Label for fgh' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'jkl', { label: 'Label for jkl' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'zxc', { label: 'Label for zxc' } ),
+                                                       new 
OO.ui.MenuItemWidget( 'vbn', { label: 'Label for vbn' } )
+                                               ]
+                                       }
+                               } ),
+                               {
+                                       label: 'ComboBoxWidget (disabled)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.ComboBoxWidget(),
+                               {
+                                       label: 'ComboBoxWidget (empty)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( { value: 'Text 
input' } ),
+                               {
+                                       label: 'TextInputWidget',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( { icon: 'search' } ),
+                               {
+                                       label: 'TextInputWidget (icon)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( { indicator: 
'required' } ),
+                               {
+                                       label: 'TextInputWidget (indicator)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( { placeholder: 
'Placeholder' } ),
+                               {
+                                       label: 'TextInputWidget (placeholder)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( {
+                                       value: 'Readonly',
+                                       readOnly: true
+                               } ),
+                               {
+                                       label: 'TextInputWidget (readonly)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( {
+                                       value: 'Disabled',
+                                       disabled: true
+                               } ),
+                               {
+                                       label: 'TextInputWidget (disabled)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( {
+                                       multiline: true,
+                                       value: 'Multiline'
+                               } ),
+                               {
+                                       label: 'TextInputWidget (multiline)',
+                                       align: 'top'
+                               }
+                       ),
+                       new OO.ui.FieldLayout(
+                               new OO.ui.TextInputWidget( {
+                                       multiline: true,
+                                       autosize: true,
+                                       value: 'Autosize'
+                               } ),
+                               {
+                                       label: 'TextInputWidget (autosize)',
+                                       align: 'top'
+                               }
                        )
                ];
 
diff --git a/src/elements/GroupElement.js b/src/elements/GroupElement.js
index 0e54f43..57ac1df 100644
--- a/src/elements/GroupElement.js
+++ b/src/elements/GroupElement.js
@@ -21,6 +21,15 @@
 /* Methods */
 
 /**
+ * Check if there are no items.
+ *
+ * @return {boolean} Group is empty
+ */
+OO.ui.GroupElement.prototype.isEmpty = function () {
+       return !this.items.length;
+};
+
+/**
  * Get items.
  *
  * @return {OO.ui.Element[]} Items
diff --git a/src/styles/widgets/ComboBoxWidget.less 
b/src/styles/widgets/ComboBoxWidget.less
new file mode 100644
index 0000000..e96f615
--- /dev/null
+++ b/src/styles/widgets/ComboBoxWidget.less
@@ -0,0 +1,6 @@
+@import '../mixins';
+
+.oo-ui-comboBoxWidget > .oo-ui-selectWidget {
+       z-index: 1;
+       min-width: 20em;
+}
diff --git a/src/styles/widgets/TextInputWidget.less 
b/src/styles/widgets/TextInputWidget.less
index 5457749..bc2ad1f 100644
--- a/src/styles/widgets/TextInputWidget.less
+++ b/src/styles/widgets/TextInputWidget.less
@@ -12,12 +12,22 @@
                .oo-ui-box-sizing(border-box);
        }
 
-       &-icon {
+       > .oo-ui-iconedElement-icon,
+       > .oo-ui-indicatedElement-indicator {
                position: absolute;
                top: 0;
-               left: 0;
                height: 100%;
-               background-position: right center;
                background-repeat: no-repeat;
+               cursor: pointer;
+
+               .oo-ui-unselectable();
+       }
+
+       > .oo-ui-iconedElement-icon {
+               left: 0;
+       }
+
+       > .oo-ui-indicatedElement-indicator {
+               right: 0;
        }
 }
diff --git a/src/themes/apex/widgets/ComboBoxWidget.less 
b/src/themes/apex/widgets/ComboBoxWidget.less
new file mode 100644
index 0000000..927c76f
--- /dev/null
+++ b/src/themes/apex/widgets/ComboBoxWidget.less
@@ -0,0 +1,22 @@
+@import '../../../styles/mixins';
+
+.oo-ui-comboBoxWidget {
+       &-handle {
+               border: solid 1px rgba(0,0,0,0.1);
+               border-radius: 0.25em;
+
+               &:hover {
+                       border-color: rgba(0,0,0,0.2);
+               }
+       }
+
+       &.oo-ui-widget-disabled,
+       &-empty {
+               .oo-ui-textInputWidget.oo-ui-indicatedElement {
+                       .oo-ui-indicatedElement-indicator {
+                               cursor: default;
+                               opacity: 0.2;
+                       }
+               }
+       }
+}
diff --git a/src/themes/apex/widgets/TextInputWidget.less 
b/src/themes/apex/widgets/TextInputWidget.less
index a1c18e6..6b68af0 100644
--- a/src/themes/apex/widgets/TextInputWidget.less
+++ b/src/themes/apex/widgets/TextInputWidget.less
@@ -59,4 +59,33 @@
                        background-color: #f3f3f3;
                }
        }
+
+       .oo-ui-iconedElement-icon,
+       .oo-ui-indicatedElement-indicator {
+               opacity: 0.8;
+       }
+
+       &.oo-ui-iconedElement {
+               input,
+               textarea {
+                       padding-left: 2em;
+               }
+
+               .oo-ui-iconedElement-icon {
+                       width: 2em;
+                       background-position: right center;
+               }
+       }
+
+       &.oo-ui-indicatedElement {
+               input,
+               textarea {
+                       padding-right: 1.5em;
+               }
+
+               .oo-ui-indicatedElement-indicator {
+                       width: 1.5em;
+                       background-position: left center;
+               }
+       }
 }
diff --git a/src/widgets/ComboBoxWidget.js b/src/widgets/ComboBoxWidget.js
new file mode 100644
index 0000000..3ea9a2e
--- /dev/null
+++ b/src/widgets/ComboBoxWidget.js
@@ -0,0 +1,121 @@
+/**
+ * Text input with a menu of optional values.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [menu] Configuration options to pass to menu widget
+ * @cfg {Object} [input] Configuration options to pass to input widget
+ */
+OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.ComboBoxWidget.super.call( this, config );
+
+       // Properties
+       this.input = new OO.ui.TextInputWidget( $.extend(
+               { $: this.$, indicator: 'down', disabled: this.isDisabled() },
+               config.input
+       ) );
+       this.menu = new OO.ui.MenuWidget( $.extend(
+               { $: this.$, widget: this, input: this.input, disabled: 
this.isDisabled() },
+               config.menu
+       ) );
+
+       // Events
+       this.input.connect( this, {
+               change: 'onInputChange',
+               indicator: 'onInputIndicator',
+               enter: 'onInputEnter'
+       } );
+       this.menu.connect( this, {
+               choose: 'onMenuChoose',
+               add: 'onMenuItemsChange',
+               remove: 'onMenuItemsChange'
+       } );
+
+       // Initialization
+       this.$element.addClass( 'oo-ui-comboBoxWidget' ).append(
+               this.input.$element,
+               this.menu.$element
+       );
+       this.onMenuItemsChange();
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ComboBoxWidget, OO.ui.Widget );
+
+/* Methods */
+
+/**
+ * Handle input change events.
+ *
+ * @param {string} value New value
+ */
+OO.ui.ComboBoxWidget.prototype.onInputChange = function ( value ) {
+       var match = this.menu.getItemFromData( value );
+
+       this.menu.selectItem( match );
+
+       if ( !this.isDisabled() ) {
+               this.menu.toggle( true );
+       }
+};
+
+/**
+ * Handle input indicator events.
+ */
+OO.ui.ComboBoxWidget.prototype.onInputIndicator = function () {
+       if ( !this.isDisabled() ) {
+               this.menu.toggle();
+       }
+};
+
+/**
+ * Handle input enter events.
+ */
+OO.ui.ComboBoxWidget.prototype.onInputEnter = function () {
+       if ( !this.isDisabled() ) {
+               this.menu.toggle( false );
+       }
+};
+
+/**
+ * Handle menu choose events.
+ *
+ * @param {OO.ui.OptionWidget} item Chosen item
+ */
+OO.ui.ComboBoxWidget.prototype.onMenuChoose = function ( item ) {
+       if ( item ) {
+               this.input.setValue( item.getData() );
+       }
+};
+
+/**
+ * Handle menu item change events.
+ */
+OO.ui.ComboBoxWidget.prototype.onMenuItemsChange = function () {
+       this.$element.toggleClass( 'oo-ui-comboBoxWidget-empty', 
this.menu.isEmpty() );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ComboBoxWidget.prototype.setDisabled = function ( disabled ) {
+       // Parent method
+       OO.ui.ComboBoxWidget.super.prototype.setDisabled.call( this, disabled );
+
+       if ( this.input ) {
+               this.input.setDisabled( this.isDisabled() );
+       }
+       if ( this.menu ) {
+               this.menu.setDisabled( this.isDisabled() );
+       }
+
+       return this;
+};
diff --git a/src/widgets/MenuWidget.js b/src/widgets/MenuWidget.js
index 9975f1b..8470299 100644
--- a/src/widgets/MenuWidget.js
+++ b/src/widgets/MenuWidget.js
@@ -199,7 +199,7 @@
  * @inheritdoc
  */
 OO.ui.MenuWidget.prototype.toggle = function ( visible ) {
-       visible = !!visible && !!this.items.length;
+       visible = ( visible === undefined ? !this.visible : !!visible ) && 
!!this.items.length;
 
        var i, len,
                change = visible !== this.isVisible();
diff --git a/src/widgets/TextInputWidget.js b/src/widgets/TextInputWidget.js
index fb872f8..aa143da 100644
--- a/src/widgets/TextInputWidget.js
+++ b/src/widgets/TextInputWidget.js
@@ -3,45 +3,43 @@
  *
  * @class
  * @extends OO.ui.InputWidget
+ * @mixins OO.ui.IconedElement
+ * @mixins OO.ui.IndicatedElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
  * @cfg {string} [placeholder] Placeholder text
- * @cfg {string} [icon] Symbolic name of icon
  * @cfg {boolean} [multiline=false] Allow multiple lines of text
  * @cfg {boolean} [autosize=false] Automatically resize to fit content
  * @cfg {boolean} [maxRows=10] Maximum number of rows to make visible when 
autosizing
  */
 OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
-       var widget = this;
-       config = $.extend( { maxRows: 10 }, config );
+       // Configuration initialization
+       config = config || {};
 
        // Parent constructor
        OO.ui.TextInputWidget.super.call( this, config );
+
+       // Mixin constructors
+       OO.ui.IconedElement.call( this, this.$( '<span>' ), config );
+       OO.ui.IndicatedElement.call( this, this.$( '<span>' ), config );
 
        // Properties
        this.pending = 0;
        this.multiline = !!config.multiline;
        this.autosize = !!config.autosize;
-       this.maxRows = config.maxRows;
+       this.maxRows = config.maxRows !== undefined ? config.maxRows : 10;
 
        // Events
        this.$input.on( 'keypress', OO.ui.bind( this.onKeyPress, this ) );
        this.$element.on( 'DOMNodeInsertedIntoDocument', OO.ui.bind( 
this.onElementAttach, this ) );
+       this.$icon.on( 'mousedown', OO.ui.bind( this.onIconMouseDown, this ) );
+       this.$indicator.on( 'mousedown', OO.ui.bind( this.onIndicatorMouseDown, 
this ) );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-textInputWidget' );
-       if ( config.icon ) {
-               this.$element.addClass( 'oo-ui-textInputWidget-decorated' );
-               this.$element.append(
-                       this.$( '<span>' )
-                               .addClass( 'oo-ui-textInputWidget-icon 
oo-ui-icon-' + config.icon )
-                               .mousedown( function () {
-                                       widget.$input[0].focus();
-                                       return false;
-                               } )
-               );
-       }
+       this.$element
+               .addClass( 'oo-ui-textInputWidget' )
+               .append( this.$icon, this.$indicator );
        if ( config.placeholder ) {
                this.$input.attr( 'placeholder', config.placeholder );
        }
@@ -51,6 +49,8 @@
 /* Setup */
 
 OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IconedElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IndicatedElement );
 
 /* Events */
 
@@ -62,9 +62,49 @@
  * @event enter
  */
 
+/**
+ * User clicks the icon.
+ *
+ * @event icon
+ */
+
+/**
+ * User clicks the indicator.
+ *
+ * @event indicator
+ */
+
 /* Methods */
 
 /**
+ * Handle icon mouse down events.
+ *
+ * @param {jQuery.Event} e Mouse down event
+ * @fires icon
+ */
+OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) {
+       if ( e.which === 1 ) {
+               this.$input[0].focus();
+               this.emit( 'icon' );
+               return false;
+       }
+};
+
+/**
+ * Handle indicator mouse down events.
+ *
+ * @param {jQuery.Event} e Mouse down event
+ * @fires indicator
+ */
+OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
+       if ( e.which === 1 ) {
+               this.$input[0].focus();
+               this.emit( 'indicator' );
+               return false;
+       }
+};
+
+/**
  * Handle key press events.
  *
  * @param {jQuery.Event} e Key press event

-- 
To view, visit https://gerrit.wikimedia.org/r/143097
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic5b939256eda6e0fa6d9f5c9ce4127abec499292
Gerrit-PatchSet: 16
Gerrit-Project: oojs/ui
Gerrit-Branch: master
Gerrit-Owner: Alex Monk <[email protected]>
Gerrit-Reviewer: Alex Monk <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to