/** * ComboBoxWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value * can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which * a value can be chosen instead). Users can choose options from the combo box in one of two ways: * * - by typing a value in the text input field. If the value exactly matches the value of a menu * option, that option will appear to be selected. * - by choosing a value from the menu. The value of the chosen option will then appear in the text * input field. * * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1]. * * @example * // Example: A ComboBoxWidget. * var comboBox = new OO.ui.ComboBoxWidget( { * label: 'ComboBoxWidget', * input: { value: 'Option One' }, * menu: { * items: [ * new OO.ui.MenuOptionWidget( { * data: 'Option 1', * label: 'Option One' * } ), * new OO.ui.MenuOptionWidget( { * data: 'Option 2', * label: 'Option Two' * } ), * new OO.ui.MenuOptionWidget( { * data: 'Option 3', * label: 'Option Three' * } ), * new OO.ui.MenuOptionWidget( { * data: 'Option 4', * label: 'Option Four' * } ), * new OO.ui.MenuOptionWidget( { * data: 'Option 5', * label: 'Option Five' * } ) * ] * } * } ); * $( 'body' ).append( comboBox.$element ); * * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options * * @class * @extends OO.ui.Widget * @mixins OO.ui.TabIndexedElement * * @constructor * @param {Object} [config] Configuration options * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}. * @cfg {Object} [input] Configuration options to pass to the {@link OO.ui.TextInputWidget text input widget}. * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where * the expanded menu is larger than its containing `
`. The specified overlay layer is usually on top of the * containing `
` and has a larger area. By default, the menu uses relative positioning. */ OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) { // Configuration initialization config = config || {}; // Parent constructor OO.ui.ComboBoxWidget.super.call( this, config ); // Properties (must be set before TabIndexedElement constructor call) this.$indicator = this.$( '' ); // Mixin constructors OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) ); // Properties this.$overlay = config.$overlay || this.$element; this.input = new OO.ui.TextInputWidget( $.extend( { indicator: 'down', $indicator: this.$indicator, disabled: this.isDisabled() }, config.input ) ); this.input.$input.eq( 0 ).attr( { role: 'combobox', 'aria-autocomplete': 'list' } ); this.menu = new OO.ui.TextInputMenuSelectWidget( this.input, $.extend( { widget: this, input: this.input, disabled: this.isDisabled() }, config.menu ) ); // Events this.$indicator.on( { click: this.onClick.bind( this ), keypress: this.onKeyPress.bind( this ) } ); this.input.connect( this, { change: 'onInputChange', enter: 'onInputEnter' } ); this.menu.connect( this, { choose: 'onMenuChoose', add: 'onMenuItemsChange', remove: 'onMenuItemsChange' } ); // Initialization this.$element.addClass( 'oo-ui-comboBoxWidget' ).append( this.input.$element ); this.$overlay.append( this.menu.$element ); this.onMenuItemsChange(); }; /* Setup */ OO.inheritClass( OO.ui.ComboBoxWidget, OO.ui.Widget ); OO.mixinClass( OO.ui.ComboBoxWidget, OO.ui.TabIndexedElement ); /* Methods */ /** * Get the combobox's menu. * @return {OO.ui.TextInputMenuSelectWidget} Menu widget */ OO.ui.ComboBoxWidget.prototype.getMenu = function () { return this.menu; }; /** * Handle input change events. * * @private * @param {string} value New value */ OO.ui.ComboBoxWidget.prototype.onInputChange = function ( value ) { var match = this.menu.getItemFromData( value ); this.menu.selectItem( match ); if ( this.menu.getHighlightedItem() ) { this.menu.highlightItem( match ); } if ( !this.isDisabled() ) { this.menu.toggle( true ); } }; /** * Handle mouse click events. * * * @private * @param {jQuery.Event} e Mouse click event */ OO.ui.ComboBoxWidget.prototype.onClick = function ( e ) { if ( !this.isDisabled() && e.which === 1 ) { this.menu.toggle(); this.input.$input[ 0 ].focus(); } return false; }; /** * Handle key press events. * * * @private * @param {jQuery.Event} e Key press event */ OO.ui.ComboBoxWidget.prototype.onKeyPress = function ( e ) { if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { this.menu.toggle(); this.input.$input[ 0 ].focus(); return false; } }; /** * Handle input enter events. * * @private */ OO.ui.ComboBoxWidget.prototype.onInputEnter = function () { if ( !this.isDisabled() ) { this.menu.toggle( false ); } }; /** * Handle menu choose events. * * @private * @param {OO.ui.OptionWidget} item Chosen item */ OO.ui.ComboBoxWidget.prototype.onMenuChoose = function ( item ) { this.input.setValue( item.getData() ); }; /** * Handle menu item change events. * * @private */ OO.ui.ComboBoxWidget.prototype.onMenuItemsChange = function () { var match = this.menu.getItemFromData( this.input.getValue() ); this.menu.selectItem( match ); if ( this.menu.getHighlightedItem() ) { this.menu.highlightItem( match ); } 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; };