Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery UI Autocomplete Category How to Skip Category Headers

I've got a working autocomplete field in my web application and I'm looking for a way to increase the usability of the field by somehow automatically skipping the category fields when an arrow key is used to scroll down the available choices (after typing in a partial search term).

For example, if a user starts typing "an", the autocomplete will show two categories with items in each. The user wants to select one of the items in the list under "People". They use the arrow key to move down the list. Currently, this code inserts the categories in the results as a list item. When using the arrow keys, you must move passed them in order to highlight and select a result. Any way the application could automatically skip those category headers?

$.widget( "custom.catcomplete", $.ui.autocomplete, {
        _renderMenu: function( ul, items ) {
            var self = this,
                currentCategory = "";
            $.each( items, function( index, item ) {
                if ( item.category != currentCategory ) {
                    ul.append( "<li class='ui-menu-item ui-category'>" + item.category + "</li>" );
                    currentCategory = item.category;
                }
                self._renderItem( ul, item );
            });
        }
    });

    var data = [
        { label: "annk K12", category: "Products" },
        { label: "annttop C13", category: "Products" },
        { label: "anders andersson", category: "People" },
        { label: "andreas andersson", category: "People" },
        { label: "andreas johnson", category: "People" }
    ];

    $( "#textfield" ).catcomplete({
        source: data,
        select: function(event, ui) {
            window.location.hash = "id_"+escape(ui.item.id);
        }
    });
like image 211
arcdegree Avatar asked May 31 '11 16:05

arcdegree


2 Answers

This line:

ul.append( "<li class='ui-menu-item ui-category'>" + item.category + "</li>" );

is causing the problem.

Internally, the widget uses list items with a class ui-menu-item to distinguish whether or not an li is an actual menu item that can be selected. When you press the 'down' key, the widget finds the next item with a class ui-menu-item and moves to it.

Remove the class and your code works like you want it to:

ul.append( "<li class='ui-category'>" + item.category + "</li>" );

Here it is working:

http://jsfiddle.net/andrewwhitaker/pkFCF/

like image 158
Andrew Whitaker Avatar answered Oct 23 '22 06:10

Andrew Whitaker


Since the accepted answer doesn't work in latest versions of jQueryUI (>1.10.4) I'll post my hack, maybe someone will find it useful.

I'm using jQueryUI 1.12.0

While appending category I added new class, i called it "categoryItem":

ul.append( "<li class='ui-autocomplete-category categoryItem'>" + "Category" + "</li>" );

Some of jQueryUI functions also need to be overridden to force jquery to ignore items with "categoryItem" class (two lines are changed).

$.widget("ui.menu", $.extend({}, $.ui.menu.prototype, {
  refresh: function() {
    var menus, items, newSubmenus, newItems, newWrappers,
        that = this,
        icon = this.options.icons.submenu,
        submenus = this.element.find( this.options.menus );

    this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
    // Initialize nested menus
    newSubmenus = submenus.filter( ":not(.ui-menu)" )
        .hide()
        .attr( {
            role: this.options.role,
            "aria-hidden": "true",
            "aria-expanded": "false"
        } )
        .each( function() {
            var menu = $( this ),
                item = menu.prev(),
                submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );

            that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
            item
                .attr( "aria-haspopup", "true" )
                .prepend( submenuCaret );
            menu.attr( "aria-labelledby", item.attr( "id" ) );
        } );

    this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );

    menus = submenus.add( this.element );
    items = menus.find( this.options.items );

    // Initialize menu-items containing spaces and/or dashes only as dividers
    items.not( ".ui-menu-item" ).each( function() {
        var item = $( this );
        if ( that._isDivider( item ) ) {
            that._addClass( item, "ui-menu-divider", "ui-widget-content" );
        }
    } );

    // Don't refresh list items that are already adapted
    newItems = items.not( ".ui-menu-item, .ui-menu-divider" ).not(".categoryItem");
    newWrappers = newItems.children()
        .not( ".ui-menu" )
            .uniqueId()
            .attr( {
                tabIndex: -1,
                role: this._itemRole()
            } );
    this._addClass( newItems, "ui-menu-item" )
        ._addClass( newWrappers, "ui-menu-item-wrapper" );

    // Add aria-disabled attribute to any disabled menu item
    items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );

    // If the active item has been removed, blur the menu
    if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
        this.blur();
    }

},
    _move: function( direction, filter, event ) {
    var next;
    if ( this.active ) {
        if ( direction === "first" || direction === "last" ) {
            next = this.active
                [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
                .eq( -1 );
        } else {
            next = this.active
                [ direction + "All" ]( ".ui-menu-item" )
                .eq( 0 );
        }
    }
    if ( !next || !next.length || !this.active ) {
        next = this.activeMenu.find( this.options.items ).not(".categoryItem")[ filter ]();
    }

    this.focus( event, next );
}
}));
like image 42
bakus33 Avatar answered Oct 23 '22 04:10

bakus33