jQuery UI Cookbook
上QQ阅读APP看书,第一时间看更新

Custom data and categories

One approach to separating two categories of autocomplete data might be to have two distinct fields, each with their own autocomplete widgets. Another would be to introduce the notion of a category into the widget itself. When the drop-down menu appears to suggest items for the user, they will also see the category the item belongs to. To do this in the autocomplete widget, we need to change both how the widget understands the source data, and how the menu items are rendered.

How to do it...

We're going to extend the autocomplete widget in order to change how the menu items are rendered. We also need to consider the data passed into the widget as the source.

( function( $, undefined ) {

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

    _renderMenu: function( ul, items ) {

        var that = this,
            currentCategory = "";

        items.sort(function( a, b ) {
            return a.cat > b.cat 
        });

        $.each( items, function( index, item ) {

            if ( item.cat != currentCategory ) {
                that._renderCategory( ul, item );
                currentCategory = item.cat;
            }

            that._renderItemData( ul, item );

        });

    },

    _renderCategory: function( ul, item ) {
        return $( "<li>" ).addClass( "ui-autocomplete-category" )
                          .html( item.cat )                          
                          .appendTo( ul );
    },

    _renderItem: function( ul, item ) {
        return $( "<li>" ).addClass( "ui-autocomplete-item" )
                          .append( $( "<a>" )
                          .append( $( "<span>" ).html( item.label ) )
                          .append( $( "<span>" ).html( item.desc ) ) )
                          .appendTo( ul );
    }

});

})( jQuery );

$( function() {

    var items = [
        {
            value: "First Item",
            label: "First Item",
            desc: "A description of the first item goes here",
            cat: "Completed"
        },
        {
            value: "Second Item",
            label: "Second Item",
            desc: "A description of the second item goes here",
            cat: "In Progress"
        },
        {
            value: "Third Item",
            label: "Third Item",
            desc: "A description of the third item goes here",
            cat: "Completed"
        }
    ];

    $( "#autocomplete" ).autocomplete( {source: items} );

});

We're almost done. The changes we've made to the menu will not just magically work out, we need to apply some styles. The following CSS code should be included on the page:

.ui-autocomplete-category {
    font-weight: bold;
    padding: .2em .4em;
    margin: .8em 0 .2em;
    line-height: 1.5;
}

.ui-autocomplete-item > a > span {
    display: block;
}

.ui-autocomplete-item > a > span + span {
    font-size: .9em;
}

Now, if you start typing in the autocomplete, you'll notice a drop-down menu drastically different from what we're used to as it contains both category and description information.

How it works...

The goal of this widget extension is to accept custom source data and to use that data in the display of the drop-down menu. Specifically, the new data we're working with is the category and the description. The category is a one-to-many relationship with the items insofar, as several items we pass to the widget may have the same category string. Our job is to figure out which items fall under any given category and to represent this structure in the drop-down menu. Additionally, the description of the item is a one-to-one relationship, so less work is required here but we nonetheless would like to include the description in the drop-down menu.

The first method from the original implementation that we're overriding is _renderMenu(). The job of _renderMenu() is to alter the underlying HTML structure each time a suggestion is made to the user. We keep track of the current category with currentCategory. We then render each item with _renderItem().

The _renderCategory() function renders the category text as an <li>. It also adds the ui-autocomplete-category class. Likewise, our _renderItem() function renders the item text, and it is here that we also make use of the desc attribute. The item also has the ui-autocomplete-item class.

The new CSS styles we've included in our UI are a necessary component of the new version of autocomplete that we've created. Without them, the description would be of the same font size and would display on the same line as the item label. Likewise, the category needs the newly-added styles to stand out as a category that groups other items instead of just another item.

There's more...

Whenever we extend the data used by the autocomplete widget, we have to tell the widget how to work with it. Here, we've told autocomplete how to display the new data in the drop-down menu. Alternatively, we could tell the widget to perform filtering on some data fields that the user never actually sees in the drop down-menu. Or we could combine the two.

Here is how we would go about using both the category and the description, both non-standard fields, in the filtering when the user starts typing.

$.ui.autocomplete.filter = function( array, term ) {

    var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );

    return $.grep( array, function( value ) {
        return matcher.test( value.cat ) || 
               matcher.test( value.desc ) ||
               matcher.test( value.label )
    });

};

Here we're replacing the filter() function that autocomplete uses with our own implementation. The two are similar, we're just adapting the RegExp.test() calls to the desc and cat field. We would place this code just beneath the custom widget declaration of autocomplete. The reason this is done externally to the customization specification is because autocomplete.filter() is kind of like a static method. Where with other methods, we're overriding on a per-instance basis.