Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jquery UI Autocomplete Custom HTML (item is undefined)

I've been banging my head off my desk trying to get the jQuery UI Autocomplete to output custom HTML. Here is my code.

        $(document).ready(function(){

        $.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-autocomplete-category'>" + item.category + "<ul class='autocomplete-category'></ul></li>" );
                        currentCategory = item.category;
                    }
                    self._renderItem( ul, item);
                });
            }
        });

        var data = [
            { label: "anders", category: "Antigen" },
            { label: "andreas", category: "Antigen" },
            { label: "antal", category: "Antigen" },
            { label: "annhhx10", category: "Products" },
            { 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" }
        ];

        $( "#typeAhead" ).catcomplete({
            delay: 0,
            source: data,
        })
        .data( "catcomplete" )._renderItem = function( ul, item ) {
            return $( "<li></li>" )
                .data( "item.catcomplete", item )
                .append( $( "<a class='ui-menu-item'></a>" ).text( item.label ) )
                .appendTo( $('ul').last('.autocomplete-category'));
        };
    });

I seem to be running into trouble by nesting my lists in the renderItem function. The HTML output is exactly how I want it. However when I "keydown" the I get a javascript error (item is undefined).

Any ideas or suggestions? I've tried just about everything.

like image 396
Nick Avatar asked Feb 05 '11 17:02

Nick


1 Answers

You were almost there! I only made two changes to get this to work (Updated after comments from OP):

After digging into the jQueryUI autocomplete source, it looks like the underlying menu that's used by the autocomplete widget isn't too friendly toward nested elements.

I could be wrong about this, but I think the menu expects a simple <ul> with just children <li>s containing an anchor tag.

Edit: This line confirms my suspicion about the menu (found in jqueryUI 1.8.9's menu widget):

    var items = this.element.children("li:not(.ui-menu-item):has(a)")
        .addClass("ui-menu-item")
        .attr("role", "menuitem");

    items.children("a")
        .addClass("ui-corner-all")
        .attr("tabindex", -1)
        // mouseenter doesn't work with event delegation
        .mouseenter(function( event ) {
            self.activate( event, $(this).parent() );
        })
        .mouseleave(function() {
            self.deactivate();
        });

Basically, since your a tags were buried inside a nested list, they weren't getting recognized by the menu.

I bet you noticed in your original code that your autocomplete menu items were not highlighting when you moused over them. This highlighting actually coincides with which menu item the widget thinks is active, which was causing your widget to fail when the user selected an item.

Since there's nothing semantically wrong or visually wrong with just giving the category lis a different class, I would restructure the widget's menu like this:

JavaScript:

_renderItem function:

.data( "catcomplete" )._renderItem = function( ul, item ) {
    return $( "<li></li>" )
        .data( "item.autocomplete", item )
        .append( $( "<a class='ui-menu-item'></a>" )
                 .text( item.label ) )
        .appendTo(ul);
};

Your _renderMenu function:

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

Generated HTML for AutoComplete menu:

<ul class="ui-autocomplete>
    <li class="ui-autocomplete-category">Antigen</li>
    <li class="ui-menu-item" role="menuitem">
         <a class="ui-menu-item ui-corner-all" tabindex="-1">anders</a>
    </li>
    <li class="ui-menu-item" role="menuitem">
        <a class="ui-menu-item ui-corner-all" tabindex="-1">andreas</a>
    </li>
    <li class="ui-menu-item" role="menuitem">
        <a class="ui-menu-item ui-corner-all" tabindex="-1">antal</a>
    </li>
    <!-- More categories, items, etc.-->
</ul>

Judging by your comments, it seemed like you wanted the menu's HTML to be nested uls inside of lis for each category. Let me know if changing the HTML of the generated menu isn't an option for you for some reason.

I've updated the example: http://jsfiddle.net/andrewwhitaker/pjs7a/2/

Hope that helps.

like image 112
Andrew Whitaker Avatar answered Nov 18 '22 21:11

Andrew Whitaker