Monday, June 24, 2013

Selectmenu Test Drive

I took the jQuery UI selectmenu widget for a test drive today, and it's looking good. The widget itself isn't much more than a means to tie select elements into the theme framework, which is important in many circumstances. For example, this widget negates the need for programmers having to base a menu widget on select elements. Instead, the selectmenu uses a menu widget internally, utilizing the parts it needs. The user interaction is nice as well — all keyboard actions work as expected. There is a nice demo showing how to extend the rendering capabilities of the select menu, showing an icon for each item.

But the demo misses out on how to show the icon for the currently-selected item. It also did things a little differently than I'm used to, so here is my take on the demo, adding the selected icon capability.


The HTML for the select menu is pretty straightforward too, the difference here are the data attributes I've added for the icons.

<select name="files" id="files">
 <option value="jquery" data-icon="script">jQuery.js</option>
 <option value="jquerylogo" data-icon="image">jQuery Logo</option>
 <option value="jqueryui" data-icon="script">ui.jQuery.js</option>
 <option value="jqueryuilogo" selected="selected" data-icon="image">jQuery UI Logo</option>
 <option value="somefile">Some unknown file</option>
</select>

And here are the selectmenu widget modifications.

(function( $, undefined ) {

// We use this in a few select menu methods, so no need to'
// define it for every instance.
var iconClass = "ui-icon ui-icon-";

$.widget( "ab.selectmenu", $.ui.selectmenu, {

    // Adding the new item icons flag.
    options: {
        itemIcons: false
    },

    // Constructor.  First calls the real constructor.
    _create: function() {

        this._super();

        if ( !this.options.itemIcons ) {
            return;
        }

        // Select menu uses a menu widget internally,
        // and needs this class in order to work with
        // icons.
        this.menu.addClass( "ui-menu-icons" );

        var $element = this.element,
            $button  = this.button,
            iconData = $element.find( "option:selected" )
                               .data( "icon" );

        // Adds the icon for the selected option, using
        // the icon data attribute.
        $( "<span/>" ).addClass( iconClass + iconData )
                      .css( "left", "0.4em" )
                      .appendTo( $button );

        // Adjust the text of the selected option, making
        // room for the icon.
        $button.find( ".ui-selectmenu-text" )
               .css( "padding-left", "24px" );

    },

    // Renders the icon for each item in the drop-down.
    _renderItem: function( ul, item ) {

        // This is the rendered li element before we attach
        // an icon to it.
        var result = this._super( ul, item );

        if ( !this.options.itemIcons ) {
            return result;
        }

        var iconData  = item.element.data( "icon" ),
            $itemText = result.find( "a" )
                              .css( "padding-left", "2em" );

        // Adds the icon to the rendered item.
        $( "<span/>" ).addClass( iconClass + iconData )
                      .appendTo( $itemText );

        return result;

    },

    // Updates the selected item icon.
    _select: function( item, event ) {

        this._super( item, event );

        if ( !this.options.itemIcons ) {
            return;
        }

        var iconData = item.element.data( "icon" ),
            $icon = this.button.find( "span.ui-icon:last" );
        
        // Remove any old icon classes and add the newly 
        // selected one.
        $icon.removeClass()
             .addClass( iconClass + iconData );

    }

});

})( jQuery );

$(function() {

    $( "#files" ).selectmenu( { itemIcons: true } );

});