9

I just found out that the jQueryUI now has it's own built-in auto-complete combo box. Great news!

Unfortunately, the next thing I found is that making it multi-column doesn't seem nearly that simple (at least via documentation).

There is a post here where someone mentions that they've done it (and even gives code), but I'm having trouble understanding what some of their code is doing.

I'm just curious if anyone has ran across this before and could post a quick and easy sample of making a multi-column result set.

Thanks much in advance.

Community
  • 1
  • 1
Lance
  • 5,187
  • 4
  • 27
  • 32

6 Answers6

10

I ended up manually overriding the _renderMenu and _renderItem functions after all. Works like a charm so far, and was actually very easy to do. I was hoping for a "per-instance" solution, but we'll burn that bridge when we come to it. Here's what it came to, and thanks again!

$.ui.autocomplete.prototype._renderMenu = function(ul, items) {
  var self = this;
  ul.append("<table><thead><tr><th>ID#</th><th>Name</th><th>Cool&nbsp;Points</th></tr></thead><tbody></tbody></table>");
  $.each( items, function( index, item ) {
    self._renderItem( ul.find("table tbody"), item );
  });
};

$.ui.autocomplete.prototype._renderItem = function(table, item) {
  return $( "<tr></tr>" )
    .data( "item.autocomplete", item )
    .append( "<td>"+item.id+"</td>"+"<td>"+item.value+"</td>"+"<td>"+item.cp+"</td>" )
    .appendTo( table );
};

$("#search").autocomplete({
  source: [
    {id:1,value:"Thomas",cp:134},
    {id:65,value:"Richard",cp:1743},
    {id:235,value:"Harold",cp:7342},
    {id:982,value:"Nina",cp:21843},
    {id:724,value:"Pinta",cp:35},
    {id:78,value:"Santa Maria",cp:787}],
  minLength: 1
});

Lance
  • 5,187
  • 4
  • 27
  • 32
6

I managed to get the full menu functionality working with a table layout, including selection, highlighting etc. I found that it is impossible to use <table><tr><td> with autocomplete but you can put <div> inside the autocomplete items and use display: table-cell in CSS. This works from IE8 onwards and all the main modern browsers.

$.widget("custom.threecolumnautocomplete", $.ui.autocomplete,
    {
        _renderMenu: function( ul, items )
        {
            ul.addClass("threecolumnautocomplete");
            return this._super(ul, items);
        },

        _renderItem: function (ul, item)
        {
            return $("<li></li>")
                .data("item.autocomplete", item)
                .append("<a><div class='col'>" + item.id + "</div><div class='col'>" + item.value + "</div><div class='col'>" + item.cp + "</div></a>")
                .appendTo(ul);
        }
    });

And then define CSS like this:

.threecolumnautocomplete li { display: table-row-group; }

.threecolumnautocomplete a { display: table-row !important; }

.threecolumnautocomplete .col { display: table-cell; }

.ie7 .threecolumnautocomplete .col { display: block; float: left; width: 15em; overflow: hidden; white-space: nowrap; }

This also shows a basic override with fixed width columns that works in IE7. To use this add the ie7 class higher up the document, e.g. on the body element.

Paul Haley
  • 395
  • 3
  • 12
3

you could also extend the AutoComplete widget and create a custom one, very similarly to what you're doing. Below is sample of how to show a flyout panel with 3 columns using a table and 3 unordered lists:

$.widget("custom.threecolumnautocomplete", $.ui.autocomplete, {

            //going to extend the AutoComplete widget by customizing renderMenu and renderItems
            _renderMenu: function (ul, items) {
                var self = this;
                //we'll define a table structure with 3 columns, and use UL elements to shove items into.
                ul.append("<table class='customautocomplete' cellpadding='5'>\
                            <thead><tr>\
                                <th>Products</th>\
                                <th class='border'>Accessories</th>\
                                <th class='border'>Categories</th>\
                            </tr></thead>\
                            <tbody><tr>\
                                <td><ul class='products'></ul></td>\
                                <td class='border'><ul class='accessories'></ul></td>\
                                <td class='border'><ul class='categories'></ul></td>\
                            </tr></tbody>\
                        </table>");

                $.each(items, function (index, item) {
                    self._renderItem(ul.find("table tbody"), item);
                });
            },

            _renderItem: function (table, item) {

                if (item.category.toLowerCase() == "product") {
                    return $("<li></li>")
                .data("item.autocomplete", item)
                .append("<a href='ProductDetails.aspx?Id=" + item.value + "'>" + item.label + "</a>") // need the actual URL for a product details page
                .appendTo(table.find("ul.products"));
                }

                if (item.category.toLowerCase() == "accessory") {
                    return $("<li></li>")
                .data("item.autocomplete", item)
                .append("<a href='ProductDetails.aspx?Id=" + item.value + "'>" + item.label + "</a>") // need the actual URL for a product details page
                .appendTo(table.find("ul.accessories"));
                }

                if (item.category.toLowerCase() == "category") {
                    return $("<li></li>")
                .data("item.autocomplete", item)
                .append("<a href='ProductSearch.aspx?q=" + item.value + "'>" + item.label + "</a>") // need the actual URL for a product search page
                .appendTo(table.find("ul.categories"));
                }

                // default if a category was not matched, just append a row to the containing table
                return $("<tr></tr>")
                .data("item.autocomplete", item)
                .append("<td colspan='3'>" + item.label + "</td>")
                .appendTo(table);
            }
        });

    $(function () {
        $("#tbSearchBox").threecolumnautocomplete({
           .
           .
           .
Thiago Silva
  • 6,354
  • 2
  • 26
  • 38
2

Thanks Lance for putting me on the right track. This answer is selectable and works in jquery-ui-1.11.4.

$.ui.autocomplete.prototype._renderMenu = function (ul, items) {
    var self = this;
    ul.append("<li class='ui-autocomplete-category' aria-label='header'><div class='listFullName listHeader'>Name</div><div class='listEmployeeID listHeader'>Employee ID</div><div class='listJobTitle listHeader'>Job Title</div></li>");
    $.each(items, function (index, item) {
        self._renderItemData(ul, item);
    });
};

$.ui.autocomplete.prototype._renderItem = function (table, item) {
    return $("<li>")
      .data("item.autocomplete", item)
      .append("<div class='listFullName'>" + item.label + "</div>" + "<div class='listEmployeeID'>" + item.value + "</div>"  + "<div class='listJobTitle'>" + item.JobTitle + "</div>")
      .appendTo(table);
};
$("#employeeLookup").autocomplete({
    source: [
        { value: 1, label: "Bob Smith", JobTitle: "President" },
        { value: 2, label: "Bob Washington", JobTitle: "Vice-President" },
        { value: 3, label: "Bobby Fischer", JobTitle: "Secretary" },
        { value: 4, label: "Bobby Brady", JobTitle: "Treasurer" },
        { value: 5, label: "Bobby Socks", JobTitle: "Senior Vice-President" },
        { value: 6, label: "Barney Rubble", JobTitle: "Sidekick" },
        { value: 7, label: "Brenda Stevens", JobTitle: "Assistant Senior Vice-President" }
    ],
    minLength:1
});

Here's the CSS I then add to format the columns:

.listFullName{
    width:200px;
    display:inline-block;
}
.listJobTitle{
    width:150px;
    display:inline-block;
}
.listEmployeeID{
    width:100px;
    display:inline-block;
}

Note that I add the 'ui-autocomplete-category' class to the header row to prevent it from being selectable in the results. I add the aria-label attribute to avoid a jQueryUI runtime exception apparently caused by encountering a list item that wasn't rendered using _renderItemData, although I did not do a deep dive into that.

Now, if you are pulling data via ajax, such as in the following example:

$("#employeeLookup").autocomplete({
    source: function (request, response) {
       // var id = $(this);
       // alert(id);
        $.ajax({
            url: "Search.asmx/FindEmployee",
            data: "{ 'partialName': '" + request.term + "'}",
            dataType: "json",
            type: "POST",
            contentType: "application/json; charset=utf-8",
            dataFilter: function (data) { return data; },
            success: function (data) {
                response($.map(data.d, function (item) {
                    return {
                        FullName: item.FullName,
                        EmployeeID: item.Person_ID,
                        JobTitle: item.JobTitle,
                        label: item.FullName,
                        value:item.FullName
                    }
                }))
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                alert(XMLHttpRequest);
            }
        });
    },
    minLength: 3,
    delay: 500,
    select: function (event, ui) {
         alert("select" + ui.item.FullName);
    }
});

You could change the _renderItem override to this:

$.ui.autocomplete.prototype._renderItem = function (table, item) {
    return $("<li>")
      .data("item.autocomplete", item)
      .append("<div class='listFullName'>" + item.FullName + "</div>" + "<div class='listEmployeeID'>" + item.EmployeeID + "</div>"  + "<div class='listJobTitle'>" + item.JobTitle + "</div>")
      .appendTo(table);
};

In the Success event, jQueryUI is looking for "label" and "value" elements in the resulting array. You can map other elements as well for your own coding clarity, but "label" is the array element that jQueryUI Autocomplete will search/filter by, and "value" is the array element that JQueryUI Autocomplete will put into the html input once you select a value from the list.

1

The post you reference is using a callback for the source instead of a url. The important part of it is the success callback on the ajax function. It takes response from the server and maps it to an object that the autocomplete expects to recieve:{label: '', value: ''}. In that example they are setting the label (which shows in the menu) to the html they want displayed.

If you look at the autocomplete source the actual rendering of each item is handled by _renderItem, each label is wrapped by an <a> and then appended to an <li> element.

If what you want to do cannot be handled by setting the item label to whatever html you want to display, you could try and monkeypatch as described here.

Post some of your code and I might be able to help with a more concrete example.

Community
  • 1
  • 1
Brian Cauthon
  • 5,484
  • 2
  • 21
  • 26
  • I wrote a custom auto-complete and am hoping to replace it with this new feature, so unfortunately, my code wouldn't do much good. Essentially, I'm just trying to squeeze my returned data into a tabular format and am having trouble seeing how I could do this. Anything I've found about "multi-column" seems to be more about "multi-item" than tabular columns. – Lance Apr 30 '10 at 14:25
  • The example you linked to is pretty straight forward so if you are still having trouble with it I'd guess that either your suggesturl isn't accepting the 'term' param in the queryString, or the response isn't coming back as a json array of objects with label/value props. Both problems are faily easy to fix in the callback. – Brian Cauthon Apr 30 '10 at 15:22
0

I know this is an old thread but this widget supports multi column autocomplete

Here is a demo page using multi column autocomplete

The code below shows how to create a multi column autocomplete input:

$('input#starttime').menuoptions({
                                    "Data": $("body").data("alltimes"),

                                    "ClearBtn": true,
                                    "onSelect": function(e, data) { 
                                        ResetEndTimeData(data.newVal); 
                                    }, 
                                    "ColumnCount": 4,
                                    "Width": 300,
                                    "Height": 200,
                                    "Sort": []
                                    });
MIkee
  • 700
  • 7
  • 11