diff options
22 files changed, 1787 insertions, 2 deletions
diff --git a/Gruntfile.js b/Gruntfile.js index d5b1a42c4..e93df3238 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -35,6 +35,7 @@ var "progressbar", "resizable", "selectable", + "selectmenu", "slider", "spinner", "tabs", diff --git a/build/tasks/testswarm.js b/build/tasks/testswarm.js index 2db312048..825b23b0c 100644 --- a/build/tasks/testswarm.js +++ b/build/tasks/testswarm.js @@ -25,6 +25,7 @@ var versions = { "Progressbar": "progressbar/progressbar.html", "Resizable": "resizable/resizable.html", "Selectable": "selectable/selectable.html", + "Selectmenu": "selectmenu/selectmenu.html", "Slider": "slider/slider.html", "Sortable": "sortable/sortable.html", "Spinner": "spinner/spinner.html", diff --git a/demos/index.html b/demos/index.html index 4739d76cd..338da841a 100644 --- a/demos/index.html +++ b/demos/index.html @@ -24,6 +24,7 @@ <li><a href="removeClass/">removeClass</a></li> <li><a href="resizable/">resizable</a></li> <li><a href="selectable/">selectable</a></li> + <li><a href="selectmenu/">selectmenu</a></li> <li><a href="show/">show</a></li> <li><a href="slider/">slider</a></li> <li><a href="sortable/">sortable</a></li> diff --git a/demos/selectmenu/custom_render.html b/demos/selectmenu/custom_render.html new file mode 100644 index 000000000..941c94da6 --- /dev/null +++ b/demos/selectmenu/custom_render.html @@ -0,0 +1,150 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>jQuery UI Selectmenu - Default functionality</title> + <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css"> + <script src="../../jquery-1.9.1.js"></script> + <script src="../../ui/jquery.ui.core.js"></script> + <script src="../../ui/jquery.ui.widget.js"></script> + <script src="../../ui/jquery.ui.position.js"></script> + <script src="../../ui/jquery.ui.menu.js"></script> + <script src="../../ui/jquery.ui.selectmenu.js"></script> + <link rel="stylesheet" href="../demos.css"> + <script> + $(function() { + $.widget( "custom.iconselectmenu", $.ui.selectmenu, { + _renderItem: function( ul, item ) { + var a, span, + li = $( "<li>" ); + + if ( item.disabled ) { + li.addClass( "ui-state-disabled" ).text( item.label ); + } else { + a = $( "<a>", { + text: item.label, + href: "#" + }).appendTo( li ); + span = $( "<span>", { + style: item.element.attr( "style" ), + "class": "ui-icon " + item.element.attr( "class" ) + }).appendTo( a ); + } + + return li.appendTo( ul ); + } + }); + + $( "#filesA" ) + .iconselectmenu() + .iconselectmenu( "menuWidget" ) + .addClass( "ui-menu-icons" ); + + $( "#filesB" ) + .iconselectmenu() + .iconselectmenu( "menuWidget" ) + .addClass( "ui-menu-icons customicons" ); + + $( "#people" ) + .iconselectmenu() + .iconselectmenu( "menuWidget") + .addClass( "ui-menu-icons avatar" ); + }); + </script> + <style> + h2 { + margin: 30px 0 0 0 + } + fieldset { + border: 0; + } + label { + display: block; + } + select { + width: 200px; + } + + .ui-selectmenu-menu .ui-menu .ui-icon { + top: 0.4em; + } + .ui-selectmenu-menu .ui-menu .ui-menu-item a { + padding-left: 2em; + } + + /* select with custom icons */ + .ui-selectmenu-menu .ui-menu.customicons .ui-menu-item a { + padding: 0.5em 0 0.5em 3em; + } + .ui-selectmenu-menu .ui-menu.customicons .ui-menu-item a .ui-icon { + height: 24px; + width: 24px; + top: 0.2em; + } + .ui-icon.video { + background: url(images/24-video-square.png) 0 0 no-repeat; + } + .ui-icon.podcast { + background: url(images/24-podcast-square.png) 0 0 no-repeat; + } + .ui-icon.rss { + background: url(images/24-rss-square.png) 0 0 no-repeat; + } + + /* select with CSS avatar icons */ + option.avatar { + background-repeat: no-repeat !important; + padding-left: 20px; + } + .avatar .ui-icon { + background-position: left top; + } + </style> +</head> +<body> + +<div class="demo"> + +<form action="#"> + + <h2>Selectmenu with framework icons</h2> + <fieldset> + <label for="filesA">Select a File:</label> + <select name="filesA" id="filesA"> + <option value="jquery" class="ui-icon-script">jQuery.js</option> + <option value="jquerylogo" class="ui-icon-image">jQuery Logo</option> + <option value="jqueryui" class="ui-icon-script">ui.jQuery.js</option> + <option value="jqueryuilogo" selected="selected" class="ui-icon-image">jQuery UI Logo</option> + <option value="somefile">Some unknown file</option> + </select> + </fieldset> + + <h2>Selectmenu with custom icon images</h2> + <fieldset> + <label for="filesB">Select a podcast:</label> + <select name="filesB" id="filesB"> + <option value="mypodcast" class="podcast">John Resig Podcast</option> + <option value="myvideo" class="video">Scott Gonzales Video</option> + <option value="myrss" class="rss">jQuery RSS XML</option> + </select> + </fieldset> + + <h2>Selectmenu with custom avatar 16x16 images as CSS background</h2> + <fieldset> + <label for="people">Select a Person:</label> + <select name="people" id="people"> + <option value="1" class="avatar" style="background-image: url(http://www.gravatar.com/avatar/b3e04a46e85ad3e165d66f5d927eb609?d=monsterid&r=g&s=16);">John Resig</option> + <option value="2" class="avatar" style="background-image: url(http://www.gravatar.com/avatar/e42b1e5c7cfd2be0933e696e292a4d5f?d=monsterid&r=g&s=16);">Tauren Mills</option> + <option value="3" class="avatar" style="background-image: url(http://www.gravatar.com/avatar/bdeaec11dd663f26fa58ced0eb7facc8?d=monsterid&r=g&s=16);">Jane Doe</option> + </select> + </fieldset> + +</form> + +</div> + +<div class="demo-description"> +<p>The whole rendering process is extendable to make custom styling as easy as possible.</p> +</div> +</body> +</html> diff --git a/demos/selectmenu/default.html b/demos/selectmenu/default.html new file mode 100644 index 000000000..62da27788 --- /dev/null +++ b/demos/selectmenu/default.html @@ -0,0 +1,103 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>jQuery UI Selectmenu - Default functionality</title> + <link rel="stylesheet" href="../../themes/base/jquery.ui.all.css"> + <script src="../../jquery-1.9.1.js"></script> + <script src="../../ui/jquery.ui.core.js"></script> + <script src="../../ui/jquery.ui.widget.js"></script> + <script src="../../ui/jquery.ui.position.js"></script> + <script src="../../ui/jquery.ui.menu.js"></script> + <script src="../../ui/jquery.ui.selectmenu.js"></script> + <link rel="stylesheet" href="../demos.css"> + <script> + $(function() { + $( "#speed" ).selectmenu(); + + $( "#files" ).selectmenu(); + + $( "#number" ) + .selectmenu() + .selectmenu( "menuWidget" ) + .addClass( "overflow" ); + }); + </script> + <style> + fieldset { + border: 0; + } + label { + display: block; + margin: 30px 0 0 0; + } + select { + width: 200px; + } + .overflow { + height: 200px; + } + </style> +</head> +<body> + +<div class="demo"> + +<form action="#"> + + <fieldset> + <label for="speed">Select a speed</label> + <select name="speed" id="speed"> + <option value="Slower">Slower</option> + <option value="Slow">Slow</option> + <option value="Medium" selected="selected">Medium</option> + <option value="Fast">Fast</option> + <option value="Faster">Faster</option> + </select> + + <label for="files">Select a file</label> + <select name="files" id="files"> + <optgroup label="Scripts"> + <option value="jquery">jQuery.js</option> + <option value="jqueryui">ui.jQuery.js</option> + </optgroup> + <optgroup label="Other files"> + <option value="somefile">Some unknown file</option> + <option value="someotherfile">Some other file</option> + </optgroup> + </select> + + <label for="number">Select a number</label> + <select name="number" id="number"> + <option value="1">1</option> + <option value="2" selected="selected">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + <option value="10">10</option> + <option value="11">11</option> + <option value="12">12</option> + <option value="13">13</option> + <option value="14">14</option> + <option value="15">15</option> + <option value="16">16</option> + <option value="17">17</option> + <option value="18">18</option> + <option value="19">19</option> + </select> + </fieldset> + +</form> + +</div> + +<div class="demo-description"> +<p>The Selectmenu widgets provides a styleable select element replacement. It will act as a proxy back to the original select element, controlling its state for form submission or serialization </p> +<p>The datasource is a native select element. Supports optgroups.</p> +</div> +</body> +</html> diff --git a/demos/selectmenu/images/24-podcast-square.png b/demos/selectmenu/images/24-podcast-square.png Binary files differnew file mode 100644 index 000000000..3c3e38f3f --- /dev/null +++ b/demos/selectmenu/images/24-podcast-square.png diff --git a/demos/selectmenu/images/24-rss-square.png b/demos/selectmenu/images/24-rss-square.png Binary files differnew file mode 100644 index 000000000..f59b69ed3 --- /dev/null +++ b/demos/selectmenu/images/24-rss-square.png diff --git a/demos/selectmenu/images/24-video-square.png b/demos/selectmenu/images/24-video-square.png Binary files differnew file mode 100644 index 000000000..ce50ccfde --- /dev/null +++ b/demos/selectmenu/images/24-video-square.png diff --git a/demos/selectmenu/index.html b/demos/selectmenu/index.html new file mode 100644 index 000000000..ef7162c4d --- /dev/null +++ b/demos/selectmenu/index.html @@ -0,0 +1,15 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>jQuery UI Selectmenu Demos</title> +</head> +<body> + +<ul> + <li><a href="default.html">Default functionality</a></li> + <li><a href="custom_render.html">Custom item rendering functionality</a></li> +</ul> + +</body> +</html> diff --git a/tests/unit/selectmenu/all.html b/tests/unit/selectmenu/all.html new file mode 100644 index 000000000..c3089ae66 --- /dev/null +++ b/tests/unit/selectmenu/all.html @@ -0,0 +1,30 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>jQuery UI Selectmenu Test Suite</title> + + <script src="../../../jquery-1.9.1.js"></script> + + <link rel="stylesheet" href="../../../external/qunit.css"> + <link rel="stylesheet" href="../qunit-composite.css"> + <script src="../../../external/qunit.js"></script> + <script src="../qunit-composite.js"></script> + <script src="../subsuite.js"></script> + + <script> + testAllVersions( "selectmenu" ); + </script> +</head> +<body> + +<h1 id="qunit-header">jQuery UI Selectmenu Test Suite</h1> +<h2 id="qunit-banner"></h2> +<div id="qunit-testrunner-toolbar"></div> +<h2 id="qunit-userAgent"></h2> +<ol id="qunit-tests"></ol> +<div id="qunit-fixture"> + +</div> +</body> +</html> diff --git a/tests/unit/selectmenu/selectmenu.html b/tests/unit/selectmenu/selectmenu.html new file mode 100644 index 000000000..584a47b53 --- /dev/null +++ b/tests/unit/selectmenu/selectmenu.html @@ -0,0 +1,91 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>jQuery UI Selectmenu Test Suite</title> + + <script src="../../jquery.js"></script> + <link rel="stylesheet" href="../../../external/qunit.css"> + <script src="../../../external/qunit.js"></script> + <script src="../../jquery.simulate.js"></script> + <script src="../testsuite.js"></script> + <script> + TestHelpers.loadResources({ + css: [ "ui.core", "ui.menu" , "ui.selectmenu" ], + js: [ + "ui/jquery.ui.core.js", + "ui/jquery.ui.widget.js", + "ui/jquery.ui.position.js", + "ui/jquery.ui.menu.js", + "ui/jquery.ui.selectmenu.js" + ] + }); + </script> + + <script src="selectmenu_common.js"></script> + <script src="selectmenu_core.js"></script> + <script src="selectmenu_events.js"></script> + <script src="selectmenu_methods.js"></script> + <script src="selectmenu_options.js"></script> + + <script src="../swarminject.js"></script> +</head> +<body> + +<h1 id="qunit-header">jQuery UI Selectmenu Test Suite</h1> +<h2 id="qunit-banner"></h2> +<div id="qunit-testrunner-toolbar"></div> +<h2 id="qunit-userAgent"></h2> +<ol id="qunit-tests"></ol> +<div id="qunit-fixture"> + <div id="selectmenu-wrap1" class="selectmenu-wrap"></div> + + <div id="selectmenu-wrap2" class="selectmenu-wrap"> + <label for="speed">Select a speed:</label> + <select name="speed" id="speed"> + <option value="Slower">Slower</option> + <option value="Slow">Slow</option> + <option value="Medium" selected="selected">Medium</option> + <option value="Fast">Fast</option> + <option value="Faster">Faster</option> + </select> + </div> + + <label for="number">Select a number:</label> + <select name="number" id="number"> + <option value="1">1</option> + <option value="2" selected="selected">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + <option value="10">10</option> + <option value="11">11</option> + <option value="12">12</option> + <option value="13">13</option> + <option value="14">14</option> + <option value="15">15</option> + <option value="16">16</option> + <option value="17">17</option> + <option value="18">18</option> + <option value="19">19</option> + </select> + + <label for="files">Select a file:</label> + <select name="files" id="files"> + <optgroup label="Scripts"> + <option value="jquery">jQuery.js</option> + <option value="jqueryui">ui.jQuery.js</option> + </optgroup> + <optgroup label="Other files"> + <option value="somefile">Some unknown file</option> + <option value="someotherfile">Some other file</option> + </optgroup> + </select> + +</div> +</body> +</html> diff --git a/tests/unit/selectmenu/selectmenu_common.js b/tests/unit/selectmenu/selectmenu_common.js new file mode 100644 index 000000000..e34dd37fb --- /dev/null +++ b/tests/unit/selectmenu/selectmenu_common.js @@ -0,0 +1,22 @@ +TestHelpers.commonWidgetTests( "selectmenu", { + defaults: { + appendTo: null, + disabled: false, + icons: { + button: "ui-icon-triangle-1-s" + }, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + + // callbacks + create: null, + change: null, + close: null, + focus: null, + open: null, + select: null + } +}); diff --git a/tests/unit/selectmenu/selectmenu_core.js b/tests/unit/selectmenu/selectmenu_core.js new file mode 100644 index 000000000..c4f43b681 --- /dev/null +++ b/tests/unit/selectmenu/selectmenu_core.js @@ -0,0 +1,161 @@ +(function( $ ) { + +module( "selectmenu: core" ); + +asyncTest( "accessibility", function() { + var links, + element = $( "#speed" ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ); + + button.simulate( "focus" ); + links = menu.find( "li.ui-menu-item a" ); + + expect( 12 + links.length * 2 ); + + setTimeout(function() { + equal( button.attr( "role" ), "combobox", "button link role" ); + equal( button.attr( "aria-haspopup" ), "true", "button link aria-haspopup" ); + equal( button.attr( "aria-expanded" ), "false", "button link aria-expanded" ); + equal( button.attr( "aria-autocomplete" ), "list", "button link aria-autocomplete" ); + equal( button.attr( "aria-owns" ), menu.attr("id"), "button link aria-owns" ); + equal( + button.attr( "aria-labelledby" ), + links.eq( element[ 0 ].selectedIndex ).attr( "id" ), + "button link aria-labelledby" + ); + equal( button.attr( "tabindex" ), 0, "button link tabindex" ); + + equal( menu.attr( "role" ), "listbox", "menu role" ); + equal( menu.attr( "aria-labelledby" ), button.attr( "id" ), "menu aria-labelledby" ); + equal( menu.attr( "aria-hidden" ), "true", "menu aria-hidden" ); + equal( menu.attr( "tabindex" ), 0, "menu tabindex" ); + equal( + menu.attr( "aria-activedescendant" ), + links.eq( element[ 0 ].selectedIndex ).attr( "id" ), + "menu aria-activedescendant" + ); + $.each( links, function( index ){ + equal( $( this ).attr( "role" ), "option", "menu link #" + index +" role" ); + equal( $( this ).attr( "tabindex" ), -1, "menu link #" + index +" tabindex" ); + }); + start(); + }); +}); + + +$.each([ + { + type: "default", + selector: "#speed" + }, + { + type: "optgroups", + selector: "#files" + } +], function( i, settings ) { + asyncTest( "state synchronization - after keydown on button - " + settings.type, function () { + expect( 4 ); + + var links, + element = $( settings.selector ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ), + selected = element.find( "option:selected" ); + + button.simulate( "focus" ); + setTimeout(function() { + links = menu.find("li.ui-menu-item a"); + + button.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + equal( + menu.attr( "aria-activedescendant" ), + links.eq( element[ 0 ].selectedIndex ).attr( "id" ), + "menu aria-activedescendant" + ); + equal( + button.attr( "aria-activedescendant" ), + links.eq( element[ 0 ].selectedIndex ).attr( "id" ), + "button aria-activedescendant" + ); + equal( + element.find( "option:selected" ).val(), + selected.next( "option" ).val() , + "original select state" + ); + equal( button.text(), selected.next( "option" ).text(), "button text" ); + start(); + }, 1 ); + }); + + asyncTest( "state synchronization - after click on item - " + settings.type, function () { + expect( 4 ); + + var links, + element = $( settings.selector ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ); + + button.simulate( "focus" ); + setTimeout(function() { + links = menu.find("li.ui-menu-item a"); + + button.simulate( "click" ); + menu.find( "a" ).last().simulate( "mouseover" ).trigger( "click" ); + equal( + menu.attr( "aria-activedescendant" ), + links.eq( element[ 0 ].selectedIndex ).attr( "id" ), + "menu aria-activedescendant" + ); + equal( + button.attr( "aria-activedescendant" ), + links.eq( element[ 0 ].selectedIndex ).attr( "id" ), + "button aria-activedescendant" + ); + equal( + element.find( "option:selected" ).val(), + element.find( "option" ).last().val(), + "original select state" + ); + equal( button.text(), element.find( "option" ).last().text(), "button text" ); + start(); + }, 1 ); + }); + + asyncTest( "state synchronization - after focus item and keydown on button - " + settings.type, function () { + expect( 4 ); + + var links, + element = $( settings.selector ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ), + options = element.find( "option" ); + + // init menu + button.simulate( "focus" ); + + setTimeout(function() { + links = menu.find( "li.ui-menu-item a" ); + // open menu and click first item + button.simulate( "click" ); + links.first().simulate( "mouseover" ).trigger( "click" ); + // open menu again and hover item + button.simulate( "click" ); + links.eq( 3 ).simulate( "mouseover" ); + // close and use keyboard control on button + button.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } ); + button.simulate( "focus" ); + setTimeout(function() { + button.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + + equal( menu.attr( "aria-activedescendant" ), links.eq( 1 ).attr( "id" ), "menu aria-activedescendant" ); + equal( button.attr( "aria-activedescendant" ), links.eq( 1 ).attr( "id" ), "button aria-activedescendant" ); + equal( element.find( "option:selected" ).val(), options.eq( 1 ).val() , "original select state" ); + equal( button.text(), options.eq( 1 ).text(), "button text" ); + start(); + }, 1 ); + }, 1 ); + }); +}); + +})( jQuery ); diff --git a/tests/unit/selectmenu/selectmenu_events.js b/tests/unit/selectmenu/selectmenu_events.js new file mode 100644 index 000000000..572f502e8 --- /dev/null +++ b/tests/unit/selectmenu/selectmenu_events.js @@ -0,0 +1,135 @@ +(function ( $ ) { + +module( "selectmenu: events", { + setup: function () { + this.element = $( "#speed" ); + } +}); + +asyncTest( "change", function () { + expect( 5 ); + + var optionIndex = 1, + button, menu, options; + + this.element.selectmenu({ + change: function ( event, ui ) { + ok( event, "change event fired on change" ); + equal( event.type, "selectmenuchange", "event type set to selectmenuchange" ); + equal( ui.item.index, optionIndex, "ui.item.index contains correct option index" ); + equal( ui.item.element[ 0 ], options.eq( optionIndex )[ 0 ], "ui.item.element contains original option element" ); + equal( ui.item.value, options.eq( optionIndex ).text(), "ui.item.value property updated correctly" ); + } + }); + + button = this.element.selectmenu( "widget" ); + menu = this.element.selectmenu( "menuWidget" ).parent(); + options = this.element.find( "option" ); + + button.simulate( "focus" ); + + setTimeout(function() { + button.simulate( "click" ); + menu.find( "a" ).eq( optionIndex ).simulate( "mouseover" ).simulate( "click" ); + start(); + }, 1 ); +}); + + +test( "close", function () { + expect( 4 ); + + this.element.selectmenu({ + close: function ( event ) { + ok( event, "close event fired on close" ); + equal( event.type, "selectmenuclose", "event type set to selectmenuclose" ); + } + }); + + this.element.selectmenu( "open" ).selectmenu( "close" ); + + this.element.selectmenu( "open" ); + $( "body" ).simulate( "click" ); +}); + + +asyncTest( "focus", function () { + expect( 12 ); + + var that = this, + optionIndex = this.element[ 0 ].selectedIndex + 1, + options = this.element.find( "option" ), + button, menu, links; + + this.element.selectmenu({ + focus: function ( event, ui ) { + ok( event, "focus event fired on element #" + optionIndex + " mouseover" ); + equal( event.type, "selectmenufocus", "event type set to selectmenufocus" ); + equal( ui.item.index, optionIndex, "ui.item.index contains correct option index" ); + equal( ui.item.element[ 0 ], options.eq( optionIndex )[ 0 ], "ui.item.element contains original option element" ); + } + }); + + button = this.element.selectmenu( "widget" ); + menu = this.element.selectmenu( "menuWidget" ); + + button.simulate( "focus" ); + + setTimeout(function() { + button.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + + button.simulate( "click" ); + links = menu.find( "li.ui-menu-item a" ); + optionIndex = 0; + links.eq( optionIndex ).simulate( "mouseover" ); + optionIndex += 1; + links.eq( optionIndex ).simulate( "mouseover" ); + + // this tests for unwanted, additional focus event on close + that.element.selectmenu( "close" ); + start(); + }, 1 ); +}); + + +test( "open", function () { + expect( 2 ); + + this.element.selectmenu({ + open: function ( event ) { + ok( event, "open event fired on open" ); + equal( event.type, "selectmenuopen", "event type set to selectmenuopen" ); + } + }); + + this.element.selectmenu( "open" ); +}); + + +asyncTest( "select", function () { + expect( 4 ); + + this.element.selectmenu({ + select: function ( event, ui ) { + ok( event, "select event fired on item select" ); + equal( event.type, "selectmenuselect", "event type set to selectmenuselect" ); + equal( ui.item.index, optionIndex, "ui.item.index contains correct option index" ); + equal( ui.item.element[ 0 ], options.eq( optionIndex )[ 0 ], "ui.item.element contains original option element" ); + } + }); + + var button = this.element.selectmenu( "widget" ), + menu = this.element.selectmenu( "menuWidget" ).parent(), + options = this.element.find( "option" ), + optionIndex = 1; + + button.simulate( "focus" ); + + setTimeout(function() { + button.simulate( "click" ); + menu.find( "a" ).eq( optionIndex ).simulate( "mouseover" ).simulate( "click" ); + start(); + }, 1 ); +}); + +})(jQuery); diff --git a/tests/unit/selectmenu/selectmenu_methods.js b/tests/unit/selectmenu/selectmenu_methods.js new file mode 100644 index 000000000..a4b4fabbe --- /dev/null +++ b/tests/unit/selectmenu/selectmenu_methods.js @@ -0,0 +1,165 @@ +(function( $ ) { + +module( "selectmenu: methods" ); + +test( "destroy", function() { + expect( 1 ); + domEqual( "#speed", function() { + $( "#speed" ).selectmenu().selectmenu( "destroy" ); + }); +}); + + +test( "open / close", function() { + expect( 4 ); + + var element = $( "#speed" ).selectmenu(), + menu = element.selectmenu( "menuWidget" ); + + element.selectmenu( "open" ); + ok( menu.is( ":visible" ), "open: menu visible" ); + equal( menu.attr( "aria-hidden" ), "false", "open: menu aria-disabled" ); + + element.selectmenu( "close" ); + ok( menu.is( ":hidden" ), "close: menu hidden" ); + equal( menu.attr( "aria-hidden" ), "true", "close: menu aria-disabled" ); +}); + + +test( "enable / disable", function () { + expect(10); + + var element = $( "#speed" ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ); + + element.selectmenu( "disable" ); + ok( element.selectmenu( "option", "disabled" ), "disable: widget option" ); + equal( element.attr( "disabled" ), "disabled", "disable: native select disabled" ); + equal( button.attr( "aria-disabled" ), "true", "disable: button wrapper ARIA" ); + equal( button.attr( "tabindex" ), -1, "disable: button tabindex" ); + equal( menu.attr( "aria-disabled" ), "true", "disable: menu wrapper ARIA" ); + + element.selectmenu( "enable" ); + ok( !element.selectmenu( "option", "disabled" ), "enable: widget option" ); + equal( element.attr( "disabled" ), undefined, "enable: native select disabled" ); + equal( button.attr( "aria-disabled" ), "false", "enable: button wrapper ARIA" ); + equal( button.attr( "tabindex" ), 0, "enable: button tabindex" ); + equal( menu.attr( "aria-disabled" ), "false", "enable: menu wrapper ARIA" ); +}); + + +test( "refresh - structure", function () { + expect( 3 ); + + var element = $( "#speed" ).selectmenu(), + menu = element.selectmenu( "menuWidget" ).parent(); + + element.find( "option" ).eq( 2 ).remove(); + element.find( "option" ).eq( 3 ).remove(); + element.append( "<option value=\"added_option\">Added option</option>" ); + element.find( "option" ).first() + .attr( "value", "changed_value" ) + .text( "Changed value" ); + element.selectmenu( "refresh" ); + + equal( element.find( "option" ).length, menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).length, "menu item length" ); + equal( element.find( "option" ).last().text(), menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).last().text(), "added item" ); + equal( element.find( "option" ).first().text(), menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).first().text(), "changed item" ); +}); + +asyncTest( "refresh - change selected option", function () { + expect( 3 ); + + var element = $( "#speed" ).selectmenu(), + button = element.selectmenu( "widget" ); + + equal( element.find( "option:selected" ).text(), button.text(), "button text after init" ); + + button.simulate( "focus" ); + + setTimeout(function() { + equal( element.find( "option:selected" ).text(), button.text(), "button text after focus" ); + + element[ 0 ].selectedIndex = 0; + element.selectmenu( "refresh" ); + + equal( element.find( "option:selected" ).text(), button.text(), "button text after changing selected option" ); + + start(); + }, 1 ); +}); + + +test( "refresh - disabled select", function () { + expect( 4 ); + + var element = $( "#speed" ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ); + + element.attr( "disabled", "disabled" ); + element.selectmenu( "refresh" ); + + ok( element.selectmenu( "option", "disabled" ), "widget option" ); + equal( button.attr( "aria-disabled" ), "true", "button wrapper ARIA" ); + equal( button.attr( "tabindex" ), -1, "button tabindex" ); + equal( menu.attr( "aria-disabled" ), "true", "menu wrapper ARIA" ); +}); + + +test( "refresh - disabled option", function () { + expect(1); + + var disabledItem, + element = $( "#speed" ).selectmenu(), + menu = element.selectmenu( "menuWidget" ).parent(); + + element.attr( "disabled", "disabled" ); + element.find( "option" ).eq( 2 ).attr( "disabled", "disabled" ); + element.selectmenu( "refresh" ); + + disabledItem = menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).eq(2); + ok( disabledItem.hasClass( "ui-state-disabled" ), "class" ); +}); + + +test( "refresh - disabled optgroup", function () { + + var i, item, + element = $( "#files" ).selectmenu(), + menu = element.selectmenu( "menuWidget" ).parent(), + originalDisabledOptgroup = element.find( "optgroup" ).first(), + originalDisabledOptions = originalDisabledOptgroup.find( "option" ); + + expect( 2 + originalDisabledOptions.length ); + + originalDisabledOptgroup.attr( "disabled", "disabled" ); + element.selectmenu( "refresh" ); + + item = menu.find( "li.ui-selectmenu-optgroup" ).first(); + ok( item.hasClass( "ui-state-disabled" ), "class" ); + + equal( menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).filter( ".ui-state-disabled" ).length, originalDisabledOptions.length, "disabled options" ); + for ( i = 0; i < originalDisabledOptions.length; i++ ) { + item = item.next( "li" ); + ok( item.hasClass( "ui-state-disabled" ), "item #" + i + ": class" ); + } +}); + +test( "widget and menuWidget", function() { + expect( 4 ); + var element = $( "#speed" ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ); + + element.selectmenu( "refresh" ); + + equal( button.length, 1, "button: one element" ); + ok( button.is( ".ui-selectmenu-button" ), "button: class" ); + + equal( menu.length, 1, "Menu Widget: one element" ); + ok( menu.is( "ul.ui-menu" ), "Menu Widget: element and class" ); +}); + +})( jQuery ); diff --git a/tests/unit/selectmenu/selectmenu_options.js b/tests/unit/selectmenu/selectmenu_options.js new file mode 100644 index 000000000..82ea6a8b4 --- /dev/null +++ b/tests/unit/selectmenu/selectmenu_options.js @@ -0,0 +1,60 @@ +(function ( $ ) { + +module( "selectmenu: options" ); + +test( "appendTo another element", function () { + expect( 8 ); + + var detached = $( "<div>" ), + element = $( "#speed" ).selectmenu(); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], document.body, "defaults to body" ); + element.selectmenu( "destroy" ); + + element.selectmenu({ + appendTo: ".selectmenu-wrap" + }); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], $( "#selectmenu-wrap1" )[ 0 ], "first found element" ); + equal( $( "#selectmenu-wrap2 .ui-selectmenu" ).length, 0, "only appends to one element" ); + element.selectmenu( "destroy" ); + + $( "#selectmenu-wrap2" ).addClass( "ui-front" ); + element.selectmenu(); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], $( "#selectmenu-wrap2" )[ 0 ], "null, inside .ui-front" ); + element.selectmenu( "destroy" ); + $( "#selectmenu-wrap2" ).removeClass( "ui-front" ); + + element.selectmenu().selectmenu( "option", "appendTo", "#selectmenu-wrap1" ); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], $( "#selectmenu-wrap1" )[ 0 ], "modified after init" ); + element.selectmenu( "destroy" ); + + element.selectmenu({ + appendTo: detached + }); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], detached[ 0 ], "detached jQuery object" ); + element.selectmenu( "destroy" ); + + element.selectmenu({ + appendTo: detached[ 0 ] + }); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], detached[ 0 ], "detached DOM element" ); + element.selectmenu( "destroy" ); + + element.selectmenu().selectmenu( "option", "appendTo", detached ); + equal( element.selectmenu( "menuWidget" ).parent().parent()[ 0 ], detached[ 0 ], "detached DOM element via option()" ); + element.selectmenu( "destroy" ); +}); + + +test( "CSS styles", function () { + expect( 2 ); + + var element = $( "#speed" ).selectmenu(), + button = element.selectmenu( "widget" ), + menu = element.selectmenu( "menuWidget" ); + + element.selectmenu( "open" ); + ok( button.hasClass( "ui-corner-top" ) && !button.hasClass( "ui-corner-all" ) && button.find( "span.ui-icon" ).hasClass( "ui-icon-triangle-1-s" ), "button styles dropdown" ); + ok( menu.hasClass( "ui-corner-bottom" ) && !menu.hasClass( "ui-corner-all" ), "menu styles dropdown" ); +}); + +})( jQuery ); diff --git a/tests/visual/compound/dialog_widgets.html b/tests/visual/compound/dialog_widgets.html index bc4777a56..e6173f581 100644 --- a/tests/visual/compound/dialog_widgets.html +++ b/tests/visual/compound/dialog_widgets.html @@ -22,8 +22,10 @@ <script src="../../../ui/jquery.ui.slider.js"></script> <script src="../../../ui/jquery.ui.tabs.js"></script> <script src="../../../ui/jquery.ui.tooltip.js"></script> + <script src="../../../ui/jquery.ui.selectmenu.js"></script> <script> $(function() { + $( "#dialog" ).dialog(); $( "[title]" ).tooltip(); $( "#accordion" ).accordion(); $( "#autocomplete" ).autocomplete({ @@ -45,7 +47,7 @@ } }); $( "#tabs" ).tabs(); - $( "#dialog" ).dialog(); + $( '#select' ).selectmenu(); $( "#dialog2" ).dialog({ autoOpen: false, width: 100, @@ -91,7 +93,15 @@ <div id="tabs-1">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div> <div id="tabs-2">Phasellus mattis tincidunt nibh.</div> <div id="tabs-3">Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.</div> - </div> + </div> + <select id="select"> + <option>Slower</option> + <option>Slow</option> + <option>Medium</option> + <option>Fast</option> + <option>Faster</option> + </select> + </form> </div> <div id="dialog2"> Yay, another dialog. diff --git a/tests/visual/compound/tabs_selectmenu.html b/tests/visual/compound/tabs_selectmenu.html new file mode 100644 index 000000000..ca4e53e92 --- /dev/null +++ b/tests/visual/compound/tabs_selectmenu.html @@ -0,0 +1,53 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Compound Visual Test : Selectmenu in Tabs</title> + <link rel="stylesheet" href="../visual.css"> + <link rel="stylesheet" href="../../../themes/base/jquery.ui.all.css"> + <script src="../../../jquery-1.9.1.js"></script> + <script src="../../../ui/jquery.ui.core.js"></script> + <script src="../../../ui/jquery.ui.widget.js"></script> + <script src="../../../ui/jquery.ui.position.js"></script> + <script src="../../../ui/jquery.ui.menu.js"></script> + <script src="../../../ui/jquery.ui.selectmenu.js"></script> + <script src="../../../ui/jquery.ui.tabs.js"></script> + <script> + $(function() { + $( "#tabs" ).tabs(); + $( "select" ).selectmenu(); + }); + </script> + <style> + select { width: 200px; } + </style> +</head> +<body> + +<div id="tabs"> + <ul> + <li><a href="#tabs-1">First</a></li> + <li><a href="#tabs-2">Second</a></li> + </ul> + <div id="tabs-1"> + <select> + <option>Slower</option> + <option>Slow</option> + <option>Medium</option> + <option>Fast</option> + <option>Faster</option> + </select> + </div> + <div id="tabs-2"> + <select> + <option>Slower</option> + <option>Slow</option> + <option>Medium</option> + <option>Fast</option> + <option>Faster</option> + </select> + </div> +</div> + +</body> +</html> diff --git a/tests/visual/selectmenu/selectmenu.html b/tests/visual/selectmenu/selectmenu.html new file mode 100644 index 000000000..d3159c743 --- /dev/null +++ b/tests/visual/selectmenu/selectmenu.html @@ -0,0 +1,248 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Selectmenu Visual Test: Default</title> + <link rel="stylesheet" href="../../../themes/base/jquery.ui.all.css"> + <script src="../../../jquery-1.9.1.js"></script> + <script src="../../../ui/jquery.ui.core.js"></script> + <script src="../../../ui/jquery.ui.widget.js"></script> + <script src="../../../ui/jquery.ui.position.js"></script> + <script src="../../../ui/jquery.ui.menu.js"></script> + <script src="../../../ui/jquery.ui.selectmenu.js"></script> + <script> + $(function() { + var log = $("#log"), + index = 0, + text; + + function logger( event, ui ) { + text = "#" + index++ + " " + event.type.replace("selectmenu",""); + if ( ui.item ) { + text += ": " + ui.item.index + " => " + ui.item.label; + } + $( "<p>" ).text( text ).prependTo( "#log" ); + } + + /* events */ + var eventsSelectmenu = $('#control select').selectmenu({ + open: logger, + close: logger, + focus : logger, + select: logger, + change: logger + }); + eventsSelectmenu.show(); + + $("#destroy").click( function() { + eventsSelectmenu.selectmenu("destroy"); + return false; + }); + + $("#refresh_add").click( function() { + eventsSelectmenu.append('<option value="fastsound">Speed of light</option>'); + eventsSelectmenu.selectmenu("refresh"); + return false; + }); + + $("#refresh_selected").click( function() { + eventsSelectmenu[0].selectedIndex = 0; + eventsSelectmenu.selectmenu("refresh"); + return false; + }); + + $("#refresh").click( function() { + eventsSelectmenu.selectmenu("refresh"); + return false; + }); + + $("#open").click( function() { + eventsSelectmenu.selectmenu("open"); + return false; + }); + + $("#close").click( function() { + eventsSelectmenu.selectmenu("close"); + return false; + }); + + /* disabled */ + $('#disabled1, #disabled2, #disabled3').selectmenu(); + var disabled4 = $('#disabled4').selectmenu(); + + $("#disable_select").on("click", function() { + if (disable_select) { + disable_select = false; + disabled4.selectmenu("disable"); + } else { + disable_select = true; + disabled4.removeAttr("disabled"); + } + disabled4.selectmenu("refresh"); + return false; + }); + + $("#disable_option").on("click", function() { + if (disable_option) { + disable_option = false; + disabled4.find("option:eq(0)").attr("disabled", "disabled"); + } else { + disable_option = true; + disabled4.find("option:eq(0)").removeAttr("disabled"); + } + disabled4.selectmenu("refresh"); + return false; + }); + + $("#disable_optgroup").on("click", function() { + if (disable_optgroup) { + disable_optgroup = false; + disabled4.find("optgroup:eq(0)").attr("disabled", "disabled"); + } else { + disable_optgroup = true; + disabled4.find("optgroup:eq(0)").removeAttr("disabled"); + } + disabled4.selectmenu("refresh"); + return false; + }); + + /* empty */ + $('.empty select').selectmenu(); + }); + </script> + <style> + body { font-size:62.5%; } + fieldset { border: 0; } + label { display: block; } + select { width: 200px; } + + .ui-selectmenu-button { display: block; margin-bottom: 1em;} + </style> +</head> +<body> + +<div id="control"> + <h2>Event logging tests</h2> + <form action="#"> + <button id="open">Open</button> + <button id="close">Close</button> + <button id="refresh_add">Add item</button> + <button id="refresh_selected">Change to first item</button> + <button id="refresh">Refresh</button> + <button id="destroy">Destroy</button> + <fieldset> + <select> + <option value="Slower">Slower</option> + <option value="Slow">Slow</option> + <option value="Medium" selected="selected">Medium</option> + <option value="Fast">Fast</option> + <option value="Faster">Faster</option> + </select> + </fieldset> + </form> +</div> + +<form action="#"> + <h2>Disabled tests</h2> + <fieldset> + <label for="disabled1">Disabled select</label> + <select disabled="disabled" name="disabled1" id="disabled1"> + <option value="Slower">Slower</option> + <option value="Slow">Slow</option> + <option value="Medium" selected="selected">Medium</option> + <option value="Fast">Fast</option> + <option value="Faster">Faster</option> + </select> + + <label for="disabled2">Disabled options</label> + <select name="disabled2" id="disabled2"> + <option value="1">1</option> + <option value="2" selected="selected">2</option> + <option value="3">3</option> + <option disabled="disabled" value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option disabled="disabled" value="8">8</option> + <option value="9">9</option> + <option value="10">10</option> + <option disabled="disabled" value="11">11</option> + <option value="12">12</option> + <option value="13">13</option> + <option disabled="disabled" value="14">14</option> + <option disabled="disabled" value="15">15</option> + <option value="16">16</option> + <option value="17">17</option> + <option value="18">18</option> + <option value="19">19</option> + </select> + + <label for="disabled3">Disabled optgroup</label> + <select name="disabled3" id="disabled3"> + <optgroup disabled="disabled" label="Scripts"> + <option value="jquery">jQuery.js</option> + <option value="jqueryui">ui.jQuery.js</option> + </optgroup> + <optgroup label="Other files"> + <option value="somefile">Some unknown file</option> + <option value="someotherfile">Some other file</option> + </optgroup> + </select> + + <label for="disabled4">Disable specific element and refresh selectmenu on button click</label> + <select name="disabled4" id="disabled4"> + <optgroup label="Scripts"> + <option value="jquery">jQuery.js</option> + <option value="jqueryui">ui.jQuery.js</option> + </optgroup> + <optgroup label="Other files"> + <option value="somefile">Some unknown file</option> + <option value="someotherfile">Some other file</option> + </optgroup> + </select> + <button id="disable_select">Toggle disable select</button> + <button id="disable_option">Toggle disable option</button> + <button id="disable_optgroup">Toggle disable optgroup</button> + </fieldset> + + <h2>Empty tests</h2> + <fieldset class="empty"> + <label for="empty1">Select with no option elements</label> + <select name="empty1" id="empty1"></select> + + <label for="empty2">Select with one empty option element</label> + <select name="empty2" id="empty2"> + <option value=""></option> + </select> + + <label for="empty3">Select with some empty option elements</label> + <select name="empty3" id="empty3"> + <option value="1">1</option> + <option value="2" selected="selected">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value=""></option> + <option value="6">6</option> + <option value="7">7</option> + <option value=""></option> + <option value="9">9</option> + </select> + + <label for="empty4">Select with empty optgroup</label> + <select name="empty4" class="empty4"> + <optgroup label="Scripts"></optgroup> + <optgroup label="Other files"> + <option value="somefile">Some unknown file</option> + <option value="someotherfile">Some other file</option> + </optgroup> + </select> + </fieldset> +</form> + +<div style="position: absolute; top: 1em; right: 1em;"> + Log: + <div id="log" style="height: 400px; width: 300px; overflow: auto; border: 1px solid #000;"></div> +</div> + +</body> +</html> diff --git a/themes/base/jquery.ui.base.css b/themes/base/jquery.ui.base.css index 29dad37d1..752dd9715 100644 --- a/themes/base/jquery.ui.base.css +++ b/themes/base/jquery.ui.base.css @@ -19,6 +19,7 @@ @import url("jquery.ui.progressbar.css"); @import url("jquery.ui.resizable.css"); @import url("jquery.ui.selectable.css"); +@import url("jquery.ui.selectmenu.css"); @import url("jquery.ui.slider.css"); @import url("jquery.ui.spinner.css"); @import url("jquery.ui.tabs.css"); diff --git a/themes/base/jquery.ui.selectmenu.css b/themes/base/jquery.ui.selectmenu.css new file mode 100644 index 000000000..71d9d3dee --- /dev/null +++ b/themes/base/jquery.ui.selectmenu.css @@ -0,0 +1,58 @@ +/*! + * jQuery UI Selectmenu @VERSION + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectmenu#theming + */ +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + /* Support: IE7 */ + overflow-x: hidden; +} +.ui-selectmenu-menu .ui-menu-item a { + padding: 0.3em 1em; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-button { + display: inline-block; + overflow: hidden; + position: relative; + text-decoration: none; + cursor: pointer; +} +.ui-selectmenu-button span.ui-icon { + right: 0.5em; + left: auto; + margin-top: -8px; + position: absolute; + top: 50%; +} +.ui-selectmenu-button span.ui-selectmenu-text { + text-align: left; + padding: 0.4em 2.1em 0.4em 1em; + display: block; + line-height: 1.4; +} diff --git a/ui/jquery.ui.selectmenu.js b/ui/jquery.ui.selectmenu.js new file mode 100644 index 000000000..d58d4ff7b --- /dev/null +++ b/ui/jquery.ui.selectmenu.js @@ -0,0 +1,480 @@ +/*! + * jQuery UI Selectmenu @VERSION + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/selectmenu + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + * jquery.ui.menu.js + */ +(function( $, undefined ) { + +$.widget( "ui.selectmenu", { + version: "@VERSION", + defaultElement: "<select>", + options: { + appendTo: null, + icons: { + button: "ui-icon-triangle-1-s" + }, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + + // 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(); + + if ( this.options.disabled ) { + this.disable(); + } + }, + + _drawButton: function() { + var tabindex = this.element.attr( "tabindex" ); + + // Fix existing label + this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button ); + this._on( this.label, { + click: function( event ) { + this.button.focus(); + event.preventDefault(); + } + }); + + // Hide original select tag + this.element.hide(); + + // Create button + this.button = $( "<span>", { + "class": "ui-selectmenu-button ui-widget ui-state-default ui-corner-all", + tabindex: tabindex || this.options.disabled ? -1 : 0, + id: this.ids.button, + width: this.element.outerWidth(), + role: "combobox", + "aria-expanded": "false", + "aria-autocomplete": "list", + "aria-owns": this.ids.menu, + "aria-haspopup": "true" + }) + .insertAfter( this.element ); + + $( "<span>", { + "class": "ui-icon " + this.options.icons.button + }).prependTo( this.button ); + + this.buttonText = $( "<span>", { + "class": "ui-selectmenu-text" + }) + .appendTo( this.button ); + this._setText( this.buttonText, this.element.find( "option:selected" ).text() ); + + this._on( this.button, this._buttonEvents ); + this._hoverable( this.button ); + this._focusable( this.button ); + }, + + _drawMenu: function() { + var that = this; + + // Create menu portion, append to body + this.menu = $( "<ul>", { + "aria-hidden": "true", + "aria-labelledby": this.ids.button, + id: this.ids.menu + }); + + // Wrap menu + this.menuWrap = $( "<div>", { + "class": "ui-selectmenu-menu", + outerWidth: this.button.outerWidth() + }) + .append( this.menu ) + .appendTo( this._appendTo() ); + + // Init menu widget + this.menuInstance = this.menu.menu({ + select: function( event, ui ) { + var item = ui.item.data( "ui-selectmenu-item" ); + + that._select( item, event ); + + if ( that.isOpen ) { + event.preventDefault(); + that.close( event ); + } + }, + focus: function( event, ui ) { + var item = ui.item.data( "ui-selectmenu-item" ); + + // prevent inital focus from firing and checks 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" ) ); + }, + role: "listbox" + }) + .menu( "instance" ); + + // adjust menu styles to dropdown + this.menu.addClass( "ui-corner-bottom" ).removeClass( "ui-corner-all" ); + + // Make sure focus stays on selected item + this.menuInstance.delay = 999999999; + // Unbind uneeded Menu events + this.menuInstance._off( this.menu, "mouseleave" ); + }, + + refresh: function() { + this.menu.empty(); + + var item, + options = this.element.find( "option" ); + + if ( !options.length ) { + return; + } + + this._readOptions( options ); + this._renderMenu( this.menu, this.items ); + + this.menu.menu( "refresh" ); + this.menuItems = this.menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).find( "a" ); + + item = this._getSelectedItem(); + + // Make sure menu is selected item aware + this.menu.menu( "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; + } + // Support: IE6-IE9 click doesn't trigger focus on the button + if ( !this.menuItems ) { + this.refresh(); + } + + this.isOpen = true; + this._toggleAttr(); + this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) ); + + this._on( this.document, this._documentClick ); + + this._trigger( "open", event ); + }, + + close: function( event ) { + if ( !this.isOpen ) { + return; + } + + this.isOpen = false; + this._toggleAttr(); + + // Check if we have an item to select + if ( this.menuItems ) { + this.menuInstance.active = this._getSelectedItem(); + } + + this._off( this.document ); + + this._trigger( "close", event ); + }, + + widget: function() { + return this.button; + }, + + menuWidget: function() { + return this.menu; + }, + + _renderMenu: function( ul, items ) { + var that = this, + currentOptgroup = ""; + + $.each( items, function( index, item ) { + if ( item.optgroup !== currentOptgroup ) { + $( "<li>", { + "class": "ui-selectmenu-optgroup" + ( item.element.parent( "optgroup" ).attr( "disabled" ) ? " ui-state-disabled" : "" ), + text: item.optgroup + }).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>" ), + a = $( "<a>", { href: "#" }); + + if ( item.disabled ) { + li.addClass( "ui-state-disabled" ); + } + this._setText( a, item.label ); + + return li.append( a ).appendTo( ul ); + }, + + _setText: function( element, value ) { + if ( value ) { + element.text( value ); + } else { + element.html( " " ); + } + }, + + _move: function( direction, event ) { + if ( direction === "first" || direction === "last" ) { + // Set focus manually for first or last item + this.menu.menu( "focus", event, this.menuItems[ direction ]() ); + } else { + // Move to and focus next or prev item + this.menu.menu( direction, event ); + } + }, + + _getSelectedItem: function() { + return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); + }, + + _toggle: function( event ) { + if ( this.isOpen ) { + this.close( event ); + } else { + this.open( event ); + } + }, + + _documentClick: { + click: function( event ) { + if ( this.isOpen && !$( event.target ).closest( "li.ui-state-disabled, li.ui-selectmenu-optgroup, #" + this.ids.button ).length ) { + this.close( event ); + } + } + }, + + _buttonEvents: { + focus: function() { + // Init Menu on first focus + this.refresh(); + // Reset focus class as its removed by ui.widget._setOption + this.button.addClass( "ui-state-focus" ); + this._off( this.button, "focus" ); + }, + click: function( event ) { + this._toggle( event ); + event.preventDefault(); + }, + keydown: function( event ) { + var prevDef = true; + switch ( event.keyCode ) { + case $.ui.keyCode.TAB: + case $.ui.keyCode.ESCAPE: + if ( this.isOpen ) { + this.close( event ); + } + prevDef = false; + break; + case $.ui.keyCode.ENTER: + if ( this.isOpen ) { + this.menu.menu( "select", event ); + } + break; + case $.ui.keyCode.UP: + if ( event.altKey ) { + this._toggle( event ); + } else { + this._move( "previous", 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.menu.menu( "select", event ); + } else { + this._toggle( event ); + } + break; + case $.ui.keyCode.LEFT: + this._move( "previous", 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 ); + prevDef = false; + } + if ( prevDef ) { + event.preventDefault(); + } + } + }, + + _select: function( item, event ) { + var oldIndex = this.element[ 0 ].selectedIndex; + // Change native select element + this.element[ 0 ].selectedIndex = item.index; + this._setText( this.buttonText, item.label ); + this._setAria( item ); + this._trigger( "select", event, { item: item } ); + + if ( item.index !== oldIndex ) { + this._trigger( "change", event, { item: item } ); + } + }, + + _setAria: function( item ) { + var link = this.menuItems.eq( item.index ), + id = link.attr( "id" ); + + this.button.attr({ + "aria-labelledby": id, + "aria-activedescendant": id + }); + this.menu.attr( "aria-activedescendant", id ); + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + this.button.find( "span.ui-icon" ) + .removeClass( this.options.icons.button ) + .addClass( value.button ); + } + + this._super( key, value ); + + if ( key === "appendTo" ) { + this.menuWrap.appendTo( this._appendTo() ); + } + if ( key === "disabled" ) { + this.menu.menu( "option", "disabled", value ); + this.button + .toggleClass( "ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + + if ( value ) { + this.element.attr( "disabled", "disabled" ); + this.button.attr( "tabindex", -1 ); + this.close(); + } else { + this.element.removeAttr( "disabled" ); + 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 = this.element.closest( ".ui-front" ); + } + + if ( !element.length ) { + element = this.document[ 0 ].body; + } + + return element; + }, + + _toggleAttr: function(){ + this.button.toggleClass( "ui-corner-top", this.isOpen ).toggleClass( "ui-corner-all", !this.isOpen ); + this.menuWrap.toggleClass( "ui-selectmenu-open", this.isOpen ); + this.menu.attr( "aria-hidden", !this.isOpen); + this.button.attr( "aria-expanded", this.isOpen); + }, + + _getCreateOptions: function() { + return { disabled: !!this.element.prop( "disabled" ) }; + }, + + _readOptions: function( options ) { + var data = []; + options.each( function( index, item ) { + var option = $( item ), + optgroup = option.parent( "optgroup" ); + data.push({ + element: option, + index: index, + value: option.attr( "value" ), + label: option.text(), + optgroup: optgroup.attr( "label" ) || "", + disabled: optgroup.attr( "disabled" ) || option.attr( "disabled" ) + }); + }); + this.items = data; + }, + + _destroy: function() { + this.menuWrap.remove(); + this.button.remove(); + this.element.show(); + this.element.removeUniqueId(); + this.label.attr( "for", this.ids.element ); + } +}); + +}( jQuery )); |