/**
* 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;
};