jquery ui autocomplete combobox with categories
Asked Answered
T

4

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.

Transform answered 14/6, 2012 at 19:12 Comment(0)
O
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/

Onofredo answered 14/6, 2012 at 23:22 Comment(10)
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.Transform
Nevermind figured it out... select.children("optgroup").children("option").each(function() {Transform
@MarkK: Sorry about that, glad you figured it out :)Onofredo
+1 I always find it funny when I'm looking into something and there's been a very recent question/answer on the same subject!Ema
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.Stroganoff
@AndrewWhitaker any suggestions on how to get the same functionality to work on the latest version of jQuery? jsfiddle.net/ZeNz0r/K5q3U/1 Added the libraries to the resource panel instead of the HTML head. jQuery 1.10.2, jQueryUI 1.10.4Hannan
@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-itemOnofredo
@AndrewWhitaker Perfect! How did I spend an entire afternoon looking at documentation and trying to rewrite this and missed that. Thanks, now I just need to adjust css and I'm ready to go.Hannan
@Andrew Whitaker this stopped working for me when trying to upgrade to the jQuery 1.11 and jQuery UI 1.11, any idea? has the menu changed?Lecher
See https://mcmap.net/q/429899/-customize-autocomplete-display-in-jquery-ui-1-8 to fix input.data("autocomplete") with input.data("ui-autocomplete") with jQuery 1.9+Lavish
H
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 );
Hale answered 2/9, 2013 at 7:26 Comment(0)
M
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.

Maggoty answered 14/6, 2012 at 19:39 Comment(2)
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.Transform
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 comeMaggoty
T
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)"
});
Trivium answered 5/8, 2014 at 20:13 Comment(1)
This is perfect! I was hesitant at first to use this, but this actually worked and solved my issue, thanks a lot!Lecher

© 2022 - 2024 — McMap. All rights reserved.