mirror of
https://github.com/jquery/jquery-ui.git
synced 2024-11-21 11:04:24 +00:00
cb69f0025f
Collapses "UI Core" and "Core" into just "Core". Fixes bad paths for CSS dependencies. Regressed when moving widgets into the widgets subfolder.
687 lines
16 KiB
JavaScript
687 lines
16 KiB
JavaScript
/*!
|
|
* jQuery UI Selectmenu @VERSION
|
|
* http://jqueryui.com
|
|
*
|
|
* Copyright jQuery Foundation and other contributors
|
|
* Released under the MIT license.
|
|
* http://jquery.org/license
|
|
*/
|
|
|
|
//>>label: Selectmenu
|
|
//>>group: Widgets
|
|
//>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.
|
|
//>>docs: http://api.jqueryui.com/selectmenu/
|
|
//>>demos: http://jqueryui.com/selectmenu/
|
|
//>>css.structure: ../../themes/base/core.css
|
|
//>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css
|
|
//>>css.theme: ../../themes/base/theme.css
|
|
|
|
( function( factory ) {
|
|
if ( typeof define === "function" && define.amd ) {
|
|
|
|
// AMD. Register as an anonymous module.
|
|
define( [
|
|
"jquery",
|
|
"./menu",
|
|
"../escape-selector",
|
|
"../keycode",
|
|
"../labels",
|
|
"../position",
|
|
"../unique-id",
|
|
"../version",
|
|
"../widget"
|
|
], factory );
|
|
} else {
|
|
|
|
// Browser globals
|
|
factory( jQuery );
|
|
}
|
|
}( function( $ ) {
|
|
|
|
return $.widget( "ui.selectmenu", {
|
|
version: "@VERSION",
|
|
defaultElement: "<select>",
|
|
options: {
|
|
appendTo: null,
|
|
classes: {
|
|
"ui-selectmenu-button-open": "ui-corner-top",
|
|
"ui-selectmenu-button-closed": "ui-corner-all"
|
|
},
|
|
disabled: null,
|
|
icons: {
|
|
button: "ui-icon-triangle-1-s"
|
|
},
|
|
position: {
|
|
my: "left top",
|
|
at: "left bottom",
|
|
collision: "none"
|
|
},
|
|
width: false,
|
|
|
|
// Callbacks
|
|
change: null,
|
|
close: null,
|
|
focus: null,
|
|
open: null,
|
|
select: null
|
|
},
|
|
|
|
_create: function() {
|
|
var selectmenuId = this.element.uniqueId().attr( "id" );
|
|
this.ids = {
|
|
element: selectmenuId,
|
|
button: selectmenuId + "-button",
|
|
menu: selectmenuId + "-menu"
|
|
};
|
|
|
|
this._drawButton();
|
|
this._drawMenu();
|
|
|
|
this._rendered = false;
|
|
this.menuItems = $();
|
|
},
|
|
|
|
_drawButton: function() {
|
|
var icon, space,
|
|
that = this,
|
|
item = this._parseOption(
|
|
this.element.find( "option:selected" ),
|
|
this.element[ 0 ].selectedIndex
|
|
);
|
|
|
|
// Associate existing label with the new button
|
|
this.labels = this.element.labels().attr( "for", this.ids.button );
|
|
this._on( this.labels, {
|
|
click: function( event ) {
|
|
this.button.focus();
|
|
event.preventDefault();
|
|
}
|
|
} );
|
|
|
|
// Hide original select element
|
|
this.element.hide();
|
|
|
|
// Create button
|
|
this.button = $( "<span>", {
|
|
tabindex: this.options.disabled ? -1 : 0,
|
|
id: this.ids.button,
|
|
role: "combobox",
|
|
"aria-expanded": "false",
|
|
"aria-autocomplete": "list",
|
|
"aria-owns": this.ids.menu,
|
|
"aria-haspopup": "true",
|
|
title: this.element.attr( "title" )
|
|
} )
|
|
.insertAfter( this.element );
|
|
|
|
this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed",
|
|
"ui-button ui-widget" );
|
|
|
|
icon = $( "<span>" ).prependTo( this.button );
|
|
space = $( "<span> </span>" );
|
|
this._addClass( space, "ui-selectmenu-icon-space" );
|
|
this._addClass( icon, null, "ui-icon " + this.options.icons.button );
|
|
icon.after( space );
|
|
|
|
this.buttonItem = this._renderButtonItem( item )
|
|
.appendTo( this.button );
|
|
|
|
if ( this.options.width !== false ) {
|
|
this._resizeButton();
|
|
}
|
|
|
|
this._on( this.button, this._buttonEvents );
|
|
this.button.one( "focusin", function() {
|
|
|
|
// Delay rendering the menu items until the button receives focus.
|
|
// The menu may have already been rendered via a programmatic open.
|
|
if ( !that._rendered ) {
|
|
that._refreshMenu();
|
|
}
|
|
} );
|
|
},
|
|
|
|
_drawMenu: function() {
|
|
var that = this;
|
|
|
|
// Create menu
|
|
this.menu = $( "<ul>", {
|
|
"aria-hidden": "true",
|
|
"aria-labelledby": this.ids.button,
|
|
id: this.ids.menu
|
|
} );
|
|
|
|
// Wrap menu
|
|
this.menuWrap = $( "<div>" ).append( this.menu );
|
|
this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" );
|
|
this.menuWrap.appendTo( this._appendTo() );
|
|
|
|
// Initialize menu widget
|
|
this.menuInstance = this.menu
|
|
.menu( {
|
|
classes: {
|
|
"ui-menu": "ui-corner-bottom"
|
|
},
|
|
role: "listbox",
|
|
select: function( event, ui ) {
|
|
event.preventDefault();
|
|
|
|
// Support: IE8
|
|
// If the item was selected via a click, the text selection
|
|
// will be destroyed in IE
|
|
that._setSelection();
|
|
|
|
that._select( ui.item.data( "ui-selectmenu-item" ), event );
|
|
},
|
|
focus: function( event, ui ) {
|
|
var item = ui.item.data( "ui-selectmenu-item" );
|
|
|
|
// Prevent inital focus from firing and check if its a newly focused item
|
|
if ( that.focusIndex != null && item.index !== that.focusIndex ) {
|
|
that._trigger( "focus", event, { item: item } );
|
|
if ( !that.isOpen ) {
|
|
that._select( item, event );
|
|
}
|
|
}
|
|
that.focusIndex = item.index;
|
|
|
|
that.button.attr( "aria-activedescendant",
|
|
that.menuItems.eq( item.index ).attr( "id" ) );
|
|
}
|
|
} )
|
|
.menu( "instance" );
|
|
|
|
// Don't close the menu on mouseleave
|
|
this.menuInstance._off( this.menu, "mouseleave" );
|
|
|
|
// Cancel the menu's collapseAll on document click
|
|
this.menuInstance._closeOnDocumentClick = function() {
|
|
return false;
|
|
};
|
|
|
|
// Selects often contain empty items, but never contain dividers
|
|
this.menuInstance._isDivider = function() {
|
|
return false;
|
|
};
|
|
},
|
|
|
|
refresh: function() {
|
|
this._refreshMenu();
|
|
this.buttonItem.replaceWith(
|
|
this.buttonItem = this._renderButtonItem(
|
|
|
|
// Fall back to an empty object in case there are no options
|
|
this._getSelectedItem().data( "ui-selectmenu-item" ) || {}
|
|
)
|
|
);
|
|
if ( this.options.width === null ) {
|
|
this._resizeButton();
|
|
}
|
|
},
|
|
|
|
_refreshMenu: function() {
|
|
var item,
|
|
options = this.element.find( "option" );
|
|
|
|
this.menu.empty();
|
|
|
|
this._parseOptions( options );
|
|
this._renderMenu( this.menu, this.items );
|
|
|
|
this.menuInstance.refresh();
|
|
this.menuItems = this.menu.find( "li" )
|
|
.not( ".ui-selectmenu-optgroup" )
|
|
.find( ".ui-menu-item-wrapper" );
|
|
|
|
this._rendered = true;
|
|
|
|
if ( !options.length ) {
|
|
return;
|
|
}
|
|
|
|
item = this._getSelectedItem();
|
|
|
|
// Update the menu to have the correct item focused
|
|
this.menuInstance.focus( null, item );
|
|
this._setAria( item.data( "ui-selectmenu-item" ) );
|
|
|
|
// Set disabled state
|
|
this._setOption( "disabled", this.element.prop( "disabled" ) );
|
|
},
|
|
|
|
open: function( event ) {
|
|
if ( this.options.disabled ) {
|
|
return;
|
|
}
|
|
|
|
// If this is the first time the menu is being opened, render the items
|
|
if ( !this._rendered ) {
|
|
this._refreshMenu();
|
|
} else {
|
|
|
|
// Menu clears focus on close, reset focus to selected item
|
|
this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" );
|
|
this.menuInstance.focus( null, this._getSelectedItem() );
|
|
}
|
|
|
|
// If there are no options, don't open the menu
|
|
if ( !this.menuItems.length ) {
|
|
return;
|
|
}
|
|
|
|
this.isOpen = true;
|
|
this._toggleAttr();
|
|
this._resizeMenu();
|
|
this._position();
|
|
|
|
this._on( this.document, this._documentClick );
|
|
|
|
this._trigger( "open", event );
|
|
},
|
|
|
|
_position: function() {
|
|
this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
|
|
},
|
|
|
|
close: function( event ) {
|
|
if ( !this.isOpen ) {
|
|
return;
|
|
}
|
|
|
|
this.isOpen = false;
|
|
this._toggleAttr();
|
|
|
|
this.range = null;
|
|
this._off( this.document );
|
|
|
|
this._trigger( "close", event );
|
|
},
|
|
|
|
widget: function() {
|
|
return this.button;
|
|
},
|
|
|
|
menuWidget: function() {
|
|
return this.menu;
|
|
},
|
|
|
|
_renderButtonItem: function( item ) {
|
|
var buttonItem = $( "<span>" );
|
|
|
|
this._setText( buttonItem, item.label );
|
|
this._addClass( buttonItem, "ui-selectmenu-text" );
|
|
|
|
return buttonItem;
|
|
},
|
|
|
|
_renderMenu: function( ul, items ) {
|
|
var that = this,
|
|
currentOptgroup = "";
|
|
|
|
$.each( items, function( index, item ) {
|
|
var li;
|
|
|
|
if ( item.optgroup !== currentOptgroup ) {
|
|
li = $( "<li>", {
|
|
text: item.optgroup
|
|
} );
|
|
that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" +
|
|
( item.element.parent( "optgroup" ).prop( "disabled" ) ?
|
|
" ui-state-disabled" :
|
|
"" ) );
|
|
|
|
li.appendTo( ul );
|
|
|
|
currentOptgroup = item.optgroup;
|
|
}
|
|
|
|
that._renderItemData( ul, item );
|
|
} );
|
|
},
|
|
|
|
_renderItemData: function( ul, item ) {
|
|
return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
|
|
},
|
|
|
|
_renderItem: function( ul, item ) {
|
|
var li = $( "<li>" ),
|
|
wrapper = $( "<div>", {
|
|
title: item.element.attr( "title" )
|
|
} );
|
|
|
|
if ( item.disabled ) {
|
|
this._addClass( li, null, "ui-state-disabled" );
|
|
}
|
|
this._setText( wrapper, item.label );
|
|
|
|
return li.append( wrapper ).appendTo( ul );
|
|
},
|
|
|
|
_setText: function( element, value ) {
|
|
if ( value ) {
|
|
element.text( value );
|
|
} else {
|
|
element.html( " " );
|
|
}
|
|
},
|
|
|
|
_move: function( direction, event ) {
|
|
var item, next,
|
|
filter = ".ui-menu-item";
|
|
|
|
if ( this.isOpen ) {
|
|
item = this.menuItems.eq( this.focusIndex ).parent( "li" );
|
|
} else {
|
|
item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
|
|
filter += ":not(.ui-state-disabled)";
|
|
}
|
|
|
|
if ( direction === "first" || direction === "last" ) {
|
|
next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
|
|
} else {
|
|
next = item[ direction + "All" ]( filter ).eq( 0 );
|
|
}
|
|
|
|
if ( next.length ) {
|
|
this.menuInstance.focus( event, next );
|
|
}
|
|
},
|
|
|
|
_getSelectedItem: function() {
|
|
return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
|
|
},
|
|
|
|
_toggle: function( event ) {
|
|
this[ this.isOpen ? "close" : "open" ]( event );
|
|
},
|
|
|
|
_setSelection: function() {
|
|
var selection;
|
|
|
|
if ( !this.range ) {
|
|
return;
|
|
}
|
|
|
|
if ( window.getSelection ) {
|
|
selection = window.getSelection();
|
|
selection.removeAllRanges();
|
|
selection.addRange( this.range );
|
|
|
|
// Support: IE8
|
|
} else {
|
|
this.range.select();
|
|
}
|
|
|
|
// Support: IE
|
|
// Setting the text selection kills the button focus in IE, but
|
|
// restoring the focus doesn't kill the selection.
|
|
this.button.focus();
|
|
},
|
|
|
|
_documentClick: {
|
|
mousedown: function( event ) {
|
|
if ( !this.isOpen ) {
|
|
return;
|
|
}
|
|
|
|
if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" +
|
|
$.ui.escapeSelector( this.ids.button ) ).length ) {
|
|
this.close( event );
|
|
}
|
|
}
|
|
},
|
|
|
|
_buttonEvents: {
|
|
|
|
// Prevent text selection from being reset when interacting with the selectmenu (#10144)
|
|
mousedown: function() {
|
|
var selection;
|
|
|
|
if ( window.getSelection ) {
|
|
selection = window.getSelection();
|
|
if ( selection.rangeCount ) {
|
|
this.range = selection.getRangeAt( 0 );
|
|
}
|
|
|
|
// Support: IE8
|
|
} else {
|
|
this.range = document.selection.createRange();
|
|
}
|
|
},
|
|
|
|
click: function( event ) {
|
|
this._setSelection();
|
|
this._toggle( event );
|
|
},
|
|
|
|
keydown: function( event ) {
|
|
var preventDefault = true;
|
|
switch ( event.keyCode ) {
|
|
case $.ui.keyCode.TAB:
|
|
case $.ui.keyCode.ESCAPE:
|
|
this.close( event );
|
|
preventDefault = false;
|
|
break;
|
|
case $.ui.keyCode.ENTER:
|
|
if ( this.isOpen ) {
|
|
this._selectFocusedItem( event );
|
|
}
|
|
break;
|
|
case $.ui.keyCode.UP:
|
|
if ( event.altKey ) {
|
|
this._toggle( event );
|
|
} else {
|
|
this._move( "prev", event );
|
|
}
|
|
break;
|
|
case $.ui.keyCode.DOWN:
|
|
if ( event.altKey ) {
|
|
this._toggle( event );
|
|
} else {
|
|
this._move( "next", event );
|
|
}
|
|
break;
|
|
case $.ui.keyCode.SPACE:
|
|
if ( this.isOpen ) {
|
|
this._selectFocusedItem( event );
|
|
} else {
|
|
this._toggle( event );
|
|
}
|
|
break;
|
|
case $.ui.keyCode.LEFT:
|
|
this._move( "prev", event );
|
|
break;
|
|
case $.ui.keyCode.RIGHT:
|
|
this._move( "next", event );
|
|
break;
|
|
case $.ui.keyCode.HOME:
|
|
case $.ui.keyCode.PAGE_UP:
|
|
this._move( "first", event );
|
|
break;
|
|
case $.ui.keyCode.END:
|
|
case $.ui.keyCode.PAGE_DOWN:
|
|
this._move( "last", event );
|
|
break;
|
|
default:
|
|
this.menu.trigger( event );
|
|
preventDefault = false;
|
|
}
|
|
|
|
if ( preventDefault ) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
},
|
|
|
|
_selectFocusedItem: function( event ) {
|
|
var item = this.menuItems.eq( this.focusIndex ).parent( "li" );
|
|
if ( !item.hasClass( "ui-state-disabled" ) ) {
|
|
this._select( item.data( "ui-selectmenu-item" ), event );
|
|
}
|
|
},
|
|
|
|
_select: function( item, event ) {
|
|
var oldIndex = this.element[ 0 ].selectedIndex;
|
|
|
|
// Change native select element
|
|
this.element[ 0 ].selectedIndex = item.index;
|
|
this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );
|
|
this._setAria( item );
|
|
this._trigger( "select", event, { item: item } );
|
|
|
|
if ( item.index !== oldIndex ) {
|
|
this._trigger( "change", event, { item: item } );
|
|
}
|
|
|
|
this.close( event );
|
|
},
|
|
|
|
_setAria: function( item ) {
|
|
var id = this.menuItems.eq( item.index ).attr( "id" );
|
|
|
|
this.button.attr( {
|
|
"aria-labelledby": id,
|
|
"aria-activedescendant": id
|
|
} );
|
|
this.menu.attr( "aria-activedescendant", id );
|
|
},
|
|
|
|
_setOption: function( key, value ) {
|
|
if ( key === "icons" ) {
|
|
var icon = this.button.find( "span.ui-icon" );
|
|
this._removeClass( icon, null, this.options.icons.button )
|
|
._addClass( icon, null, value.button );
|
|
}
|
|
|
|
this._super( key, value );
|
|
|
|
if ( key === "appendTo" ) {
|
|
this.menuWrap.appendTo( this._appendTo() );
|
|
}
|
|
|
|
if ( key === "width" ) {
|
|
this._resizeButton();
|
|
}
|
|
},
|
|
|
|
_setOptionDisabled: function( value ) {
|
|
this._super( value );
|
|
|
|
this.menuInstance.option( "disabled", value );
|
|
this.button.attr( "aria-disabled", value );
|
|
this._toggleClass( this.button, null, "ui-state-disabled", value );
|
|
|
|
this.element.prop( "disabled", value );
|
|
if ( value ) {
|
|
this.button.attr( "tabindex", -1 );
|
|
this.close();
|
|
} else {
|
|
this.button.attr( "tabindex", 0 );
|
|
}
|
|
},
|
|
|
|
_appendTo: function() {
|
|
var element = this.options.appendTo;
|
|
|
|
if ( element ) {
|
|
element = element.jquery || element.nodeType ?
|
|
$( element ) :
|
|
this.document.find( element ).eq( 0 );
|
|
}
|
|
|
|
if ( !element || !element[ 0 ] ) {
|
|
element = this.element.closest( ".ui-front, dialog" );
|
|
}
|
|
|
|
if ( !element.length ) {
|
|
element = this.document[ 0 ].body;
|
|
}
|
|
|
|
return element;
|
|
},
|
|
|
|
_toggleAttr: function() {
|
|
this.button.attr( "aria-expanded", this.isOpen );
|
|
|
|
// We can't use two _toggleClass() calls here, because we need to make sure
|
|
// we always remove classes first and add them second, otherwise if both classes have the
|
|
// same theme class, it will be removed after we add it.
|
|
this._removeClass( this.button, "ui-selectmenu-button-" +
|
|
( this.isOpen ? "closed" : "open" ) )
|
|
._addClass( this.button, "ui-selectmenu-button-" +
|
|
( this.isOpen ? "open" : "closed" ) )
|
|
._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen );
|
|
|
|
this.menu.attr( "aria-hidden", !this.isOpen );
|
|
},
|
|
|
|
_resizeButton: function() {
|
|
var width = this.options.width;
|
|
|
|
// For `width: false`, just remove inline style and stop
|
|
if ( width === false ) {
|
|
this.button.css( "width", "" );
|
|
return;
|
|
}
|
|
|
|
// For `width: null`, match the width of the original element
|
|
if ( width === null ) {
|
|
width = this.element.show().outerWidth();
|
|
this.element.hide();
|
|
}
|
|
|
|
this.button.outerWidth( width );
|
|
},
|
|
|
|
_resizeMenu: function() {
|
|
this.menu.outerWidth( Math.max(
|
|
this.button.outerWidth(),
|
|
|
|
// Support: IE10
|
|
// IE10 wraps long text (possibly a rounding bug)
|
|
// so we add 1px to avoid the wrapping
|
|
this.menu.width( "" ).outerWidth() + 1
|
|
) );
|
|
},
|
|
|
|
_getCreateOptions: function() {
|
|
var options = this._super();
|
|
|
|
options.disabled = this.element.prop( "disabled" );
|
|
|
|
return options;
|
|
},
|
|
|
|
_parseOptions: function( options ) {
|
|
var that = this,
|
|
data = [];
|
|
options.each( function( index, item ) {
|
|
data.push( that._parseOption( $( item ), index ) );
|
|
} );
|
|
this.items = data;
|
|
},
|
|
|
|
_parseOption: function( option, index ) {
|
|
var optgroup = option.parent( "optgroup" );
|
|
|
|
return {
|
|
element: option,
|
|
index: index,
|
|
value: option.val(),
|
|
label: option.text(),
|
|
optgroup: optgroup.attr( "label" ) || "",
|
|
disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
|
|
};
|
|
},
|
|
|
|
_destroy: function() {
|
|
this.menuWrap.remove();
|
|
this.button.remove();
|
|
this.element.show();
|
|
this.element.removeUniqueId();
|
|
this.labels.attr( "for", this.ids.element );
|
|
}
|
|
} );
|
|
|
|
} ) );
|