12

I'm using the jquery ui autocomplete combobox, and it's working great but now i'm getting a bit greedy. I would like to be able to add categories to it. The combobox is generated off of a menu so if I added categories see example below the tag would show up like the categories are in the jquery ui autocomplete categories version

<select>
<optgroup name="Cat 1">    
<option value="1a">One A</option>
<option value="1b">One B</option>
<option value="1c">One C</option>
</optgroup>
<optgroup name="Cat 2">    
<option value="2a">Two A</option>
<option value="2b">Two B</option>
<option value="2c">Two C</option>
</optgroup>
</select>

I created a http://jsfiddle.net/nH3b6/11/.

Thanks for any help or direction.

4 Answers 4

11

Expanding on @Jarry's suggestion, I would update your code to determine what optgroup the option belongs to. From there, you can use similar code as found on the jQueryUI website:

(function($) {
    $.widget("ui.combobox", {
        _create: function() {
            var input, self = this,
                select = this.element.hide(),
                selected = select.children(":selected"),
                value = selected.val() ? selected.text() : "",
                wrapper = this.wrapper = $("<span>").addClass("ui-combobox").insertAfter(select);

            input = $("<input>").appendTo(wrapper).val(value).addClass("ui-state-default ui-combobox-input").autocomplete({
                delay: 0,
                minLength: 0,
                source: function(request, response) {
                    var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");

                    response(select.find("option").map(function() {
                        var text = $(this).text();
                        if (this.value && (!request.term || matcher.test(text))) return {
                            label: text.replace(
                            new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(request.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"),
                            value: text,
                            option: this,
                            category: $(this).closest("optgroup").attr("label")
                        };
                        //MK
                        $('#test').attr('style', 'display: none;');
                    }).get());
                },
                select: function(event, ui) {
                    ui.item.option.selected = true;
                    self._trigger("selected", event, {
                        item: ui.item.option
                    });
                },
                change: function(event, ui) {
                    if (!ui.item) {
                        var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex($(this).val()) + "$", "i"),
                            valid = false;
                        select.children("option").each(function() {
                            if ($(this).text().match(matcher)) {
                                this.selected = valid = true;
                                return false;
                            }
                        });
                        if (!valid) {
                            $('#test').attr('style', 'display: block;');
                            // remove invalid value, as it didn't match anything
                            //$( this ).val( "" );
                            //select.val( "" );
                            //input.data( "autocomplete" ).term = "";
                            //return false;                    
                        }
                    }
                }
            }).addClass("ui-widget ui-widget-content ui-corner-left");

            input.data("autocomplete")._renderItem = function(ul, item) {
                return $("<li></li>").data("item.autocomplete", item).append("<a>" + item.label + "</a>").appendTo(ul);
            };

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

            $("<a>").attr("tabIndex", -1).attr("title", "Show All Items").appendTo(wrapper).button({
                icons: {
                    primary: "ui-icon-triangle-1-s"
                },
                text: false
            }).removeClass("ui-corner-all").addClass("ui-corner-right ui-combobox-toggle").click(function() {
                // close if already visible
                if (input.autocomplete("widget").is(":visible")) {
                    input.autocomplete("close");
                    return;
                }

                // work around a bug (likely same cause as #5265)
                $(this).blur();

                // pass empty string as value to search for, displaying all results
                input.autocomplete("search", "");
                input.focus();
            });
        },

        destroy: function() {
            this.wrapper.remove();
            this.element.show();
            $.Widget.prototype.destroy.call(this);
        }
    });
})(jQuery);

$(function() {
    $("#combobox").combobox();
    $("#toggle").click(function() {
        $("#combobox").toggle();
    });
});

Example: http://jsfiddle.net/gB32r/

Sign up to request clarification or add additional context in comments.

10 Comments

Hey so I found a little bug as a by-product of the adjustment you made. If I drop down the list and select one of the options from a category it displays "No matches". If I select an option that is not under a category it works great. I tried to figure it out to no avail.
Nevermind figured it out... select.children("optgroup").children("option").each(function() {
@MarkK: Sorry about that, glad you figured it out :)
You might want to change all calls to children to find. This way, it works after postbacks (in case of ASP.NET) and other cases. I wanted to change the answer, but I think the company network is blocking ajax or something. Also, in my experience, you don't have to change the _renderItem method.
@FernandoSilva: Hey Fernando -- the following update should work: jsfiddle.net/K5q3U/2 The later versions of jQueryUI use different data names uiAutocomplete and ui-autocomplete-item
|
3

There is a few features with jquery 10. I take autocomplete combobox from website of jquery ui: http://jqueryui.com/autocomplete/#combobox and join it with Andrew Whitaker answer.

(function( $ ) {
$.widget( "custom.combobox_with_optgroup", {
    _create: function() {
        this.wrapper = $( "<span>" )
            .addClass( "custom-combobox" )
            .insertAfter( this.element );
        this.element.hide();
        this._createAutocomplete();
        this._createShowAllButton();
    },
    _createAutocomplete: function() {
        var selected = this.element.find( ":selected" ),
            value = selected.val() ? selected.text() : "";
        this.input = $( "<input>" )
            .appendTo( this.wrapper )
            .val( value )
            .attr( "title", "" )
            .addClass( "custom-combobox-input ui-widget ui-widget-content ui-state-default ui-corner-left" )
            .autocomplete({
                delay: 0,
                minLength: 0,
                source: $.proxy( this, "_source" )
            })
            .tooltip({
                tooltipClass: "ui-state-highlight"
            });
        this._on( this.input, {
            autocompleteselect: function( event, ui ) {
                ui.item.option.selected = true;
                this._trigger( "select", event, {
                    item: ui.item.option
                });
            },
            autocompletechange: "_removeIfInvalid"
        });

        this.input.data("uiAutocomplete")._renderMenu = function(ul, items) {
            var self = this,
                currentCategory = "";
            $.each(items, function(index, item) {
                if (item.category != currentCategory) {
                    if (item.category) {
                        ul.append("<li class='custom-autocomplete-category'>" + item.category + "</li>");
                    }
                    currentCategory = item.category;
                }
                self._renderItemData(ul, item);
            });
        };
    },
    _createShowAllButton: function() {
        var input = this.input,
            wasOpen = false;
        $( "<a>" )
            .attr( "tabIndex", -1 )
            .attr( "title", "Показать все" )
            .tooltip()
            .appendTo( this.wrapper )
            .button({
                icons: {
                    primary: "ui-icon-triangle-1-s"
                },
                text: false
            })
            .removeClass( "ui-corner-all" )
            .addClass( "custom-combobox-toggle ui-corner-right" )
            .mousedown(function() {
                wasOpen = input.autocomplete( "widget" ).is( ":visible" );
            })
            .click(function() {
                input.focus();

                if ( wasOpen ) {
                    return;
                }

                input.autocomplete( "search", "" );
            });
    },
    _source: function( request, response ) {
        var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
        response( this.element.find( "option" ).map(function() {
            var text = $( this ).text();
            if ( this.value && ( !request.term || matcher.test(text) ) )
                return {
                    label: text,
                    value: text,
                    option: this,
                    category: $(this).closest("optgroup").attr("label")
                };
        }) );
    },
    _removeIfInvalid: function( event, ui ) {

        if ( ui.item ) {
            return;
        }

        var value = this.input.val(),
            valueLowerCase = value.toLowerCase(),
            valid = false;
        this.element.find( "option" ).each(function() {
            if ( $( this ).text().toLowerCase() === valueLowerCase ) {
                this.selected = valid = true;
                return false;
            }
        });

        if ( valid ) {
            return;
        }

        this.input
            .val( "" )
            .attr( "title", value + " не существует" )
            .tooltip( "open" );
        this.element.val( "" );
        this._delay(function() {
            this.input.tooltip( "close" ).attr( "title", "" );
        }, 2500 );
        this.input.data( "ui-autocomplete" ).term = "";
    },
    _destroy: function() {
        this.wrapper.remove();
        this.element.show();
    }
});
})( jQuery );

Comments

2

as you can see in the jQueryUI docs, you have to customize the widget to do that

_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 );
            });
        }

this its not tested, but should be a good start:

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

if this doesnt work, maybe you should debug to see whats into the items array that comes as a parameter of _renderMenu.

a side note: this is called MonkeyPatching, i wouldnt recommend doing this a lot, but since the docs show it, id say do it.

2 Comments

Thanks for the reply. Correct me if i'm wrong, but you are starting with the code from the "category" version of the autocomplete I am starting from the "combobox" version. So the code above wouldn't work.
you are not wrong. the code is from the category. im not sure if it would work, and i cant test it right now. i think it will work, you just have to override(AKA monkeypatch) the _renderMenu function. i bet that its being called despite from where the items come
1

I'am using the jqueryui autocomplete widget on my webapp, with the combobox monkey patching, optgroup (categories) and ability to seach also in category names. The seach term is also emphasized inside compatible option and optgroup. I used several answers from stackoverflow and jqueryui website to get to this point, thanks !

I like to keep it working on the last version of jqueryui. Jqueryui 1.9 and 1.11 introduced breaking changes (in autocomplete and menu plugin, the lastest being used by the former) and I finally succeeded in making it work with the lastest version of jqueryui (1.11.0) and jquery ( 2.1.1 )

jsbin here

Important part : change menu widget options to not consider categories as normal menu link via new items option (so new that not inside the doc but in the jqueryui upgrade guide to 1.11

$.extend($.ui.menu.prototype.options, {
    items: "> :not(.aureltime-autocomplete-category)"
});

1 Comment

This is perfect! I was hesitant at first to use this, but this actually worked and solved my issue, thanks a lot!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.