How can I custom-format the Autocomplete plug-in results?
Asked Answered
B

13

168

I’m using the jQuery UI Autocomplete plug-in. Is there a way to highlight search character sequence in drop-down results?

For example, if I have “foo bar” as data and I type "foo" I’ll get “foo bar” in the drop-down, like this:

“Breakfast” appears after “Bre” is typed with “Bre” having a bold type and “akfast” having a light one.

Bumpy answered 12/3, 2010 at 21:18 Comment(0)
S
238

Autocomplete with live suggestion

Yes, you can if you monkey-patch autocomplete.

In the autocomplete widget included in v1.8rc3 of jQuery UI, the popup of suggestions is created in the _renderMenu function of the autocomplete widget. This function is defined like this:

_renderMenu: function( ul, items ) {
    var self = this;
    $.each( items, function( index, item ) {
        self._renderItem( ul, item );
    });
},

The _renderItem function is defined like this:

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

So what you need to do is replace that _renderItem fn with your own creation that produces the desired effect. This technique, redefining an internal function in a library, I have come to learn is called monkey-patching. Here's how I did it:

  function monkeyPatchAutocomplete() {

      // don't really need this, but in case I did, I could store it and chain
      var oldFn = $.ui.autocomplete.prototype._renderItem;

      $.ui.autocomplete.prototype._renderItem = function( ul, item) {
          var re = new RegExp("^" + this.term) ;
          var t = item.label.replace(re,"<span style='font-weight:bold;color:Blue;'>" + 
                  this.term + 
                  "</span>");
          return $( "<li></li>" )
              .data( "item.autocomplete", item )
              .append( "<a>" + t + "</a>" )
              .appendTo( ul );
      };
  }

Call that function once in $(document).ready(...) .

Now, this is a hack, because:

  • there's a regexp obj created for every item rendered in the list. That regexp obj ought to be re-used for all items.

  • there's no css class used for the formatting of the completed part. It's an inline style.
    This means if you had multiple autocompletes on the same page, they'd all get the same treatment. A css style would solve that.

...but it illustrates the main technique, and it works for your basic requirements.

alt text

updated working example: http://output.jsbin.com/qixaxinuhe


To preserve the case of the match strings, as opposed to using the case of the typed characters, use this line:

var t = item.label.replace(re,"<span style='font-weight:bold;color:Blue;'>" + 
          "$&" + 
          "</span>");

In other words, starting from the original code above, you just need to replace this.term with "$&".


EDIT
The above changes every autocomplete widget on the page. If you want to change only one, see this question:
How to patch *just one* instance of Autocomplete on a page?

Sarchet answered 12/3, 2010 at 23:13 Comment(12)
Thanks Cheeso. Do you have jsbin link for this?Bumpy
Note that if you do chain things along, it's important to reset context: oldFn.apply(this, [ul, item]);Barbarian
Thank you very much! Would be awesome if this became a part of jQuery UI.Specter
One thing I would say is if you want it to bold the result in any part of the matched string (not just the beginning) modify the RegExp line to this: var re = new RegExp(this.term) ;Specter
Is this answer now out of date? JQ UI have a simple example for custom results here: jqueryui.com/demos/autocomplete/#custom-dataTelescopic
@Telescopic - no, I don't think it is out of date. I didn't see anything on that page regarding custom formatting of the results.Sarchet
Wouldn't it be better to modify item.label and call the built in function passing the modified item object?Robtrobust
If anyone is still looking into this, you don't need to tamper with the widget's original code, you can create a new custom widget that inherits from autocomplete and override the method thereCalvano
the above with a scroll bar added into it....check this url jsbin.com/ezifi/569/editSenega
JQueryUI autocomplete appears to do a case insensitive search by default, so it makes sense to add the "i" flag in the RegExp object. There's also no reason to use the "^" in the regex as @DavidRyder mentioned. Like: var re = new RegExp(this.term, "i"); Great post!Vilhelmina
In order to make it NOT case sensitive, add "gi" argument to Regular Expression: var re = new RegExp("^" + this.term, "gi");Bog
Thanks for this great post. I needed an additional padding:0; in my CSS setup. I also removed the "^" in the regex to allow searching inside the labels.Cowherd
A
65

this also works:

       $.ui.autocomplete.prototype._renderItem = function (ul, item) {
            item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
            return $("<li></li>")
                    .data("item.autocomplete", item)
                    .append("<a>" + item.label + "</a>")
                    .appendTo(ul);
        };

a combination of @Jörn Zaefferer and @Cheeso's responses.

Abdication answered 11/8, 2010 at 11:29 Comment(5)
I liked this one better, as it matches the whole word.Killifish
This works well. The only thing to look out for is that item.label is getting replaced. For me, I was getting "<strong..." in my text box. Just assigning the replace result to another variable cleared that up. You wouldn't have this problem if your data contains a value property that gets put into the text box.Hexagon
this doesn't work if you have an autocomplete that supports multiple values. any suggestions?Orthognathous
The simplest way to get text highlighting in your autocomplete results. Obviously look out for the bugs as pointed out by leora and Kijana aboveIscariot
@RNKushwaha anywhere before you call to $().autocomplete()Ba
P
8

Super helpful. Thank you. +1.

Here is a light version that sorts on "String must begin with the term":

function hackAutocomplete(){

    $.extend($.ui.autocomplete, {
        filter: function(array, term){
            var matcher = new RegExp("^" + term, "i");

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

hackAutocomplete();
Pasteup answered 20/4, 2011 at 20:43 Comment(2)
Thanks Orolo, I was using autocomplete at multiple locations and wanted a central place where I can make the change to show only result that start with the typed characters and this one is what I exactly needed!Principium
Thanks. This is the best solution of all. It works for case insensitive.Tasty
M
7

Here it goes, a functional full example:

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Autocomplete - jQuery</title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css">
</head>
<body>
<form id="form1" name="form1" method="post" action="">
  <label for="search"></label>
  <input type="text" name="search" id="search" />
</form>

<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<script>
$(function(){

$.ui.autocomplete.prototype._renderItem = function (ul, item) {
    item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
    return $("<li></li>")
            .data("item.autocomplete", item)
            .append("<a>" + item.label + "</a>")
            .appendTo(ul);
};


var availableTags = [
    "JavaScript",
    "ActionScript",
    "C++",
    "Delphi",
    "Cobol",
    "Java",
    "Ruby",
    "Python",
    "Perl",
    "Groove",
    "Lisp",
    "Pascal",
    "Assembly",
    "Cliper",
];

$('#search').autocomplete({
    source: availableTags,
    minLength: 3
});


});
</script>
</body>
</html>

Hope this helps

Mandelbaum answered 22/3, 2013 at 12:58 Comment(0)
T
6

jQueryUI 1.9.0 changes how _renderItem works.

The code below takes this change into consideration and also shows how I was doing highlight matching using Jörn Zaefferer's jQuery Autocomplete plugin. It will highlight all individual terms in the overall search term.

Since moving to using Knockout and jqAuto I found this a much easier way of styling the results.

function monkeyPatchAutocomplete() {
   $.ui.autocomplete.prototype._renderItem = function (ul, item) {

      // Escape any regex syntax inside this.term
      var cleanTerm = this.term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

      // Build pipe separated string of terms to highlight
      var keywords = $.trim(cleanTerm).replace('  ', ' ').split(' ').join('|');

      // Get the new label text to use with matched terms wrapped
      // in a span tag with a class to do the highlighting
      var re = new RegExp("(" + keywords + ")", "gi");
      var output = item.label.replace(re,  
         '<span class="ui-menu-item-highlight">$1</span>');

      return $("<li>")
         .append($("<a>").html(output))
         .appendTo(ul);
   };
};

$(function () {
   monkeyPatchAutocomplete();
});
Territorialism answered 20/10, 2012 at 23:11 Comment(3)
When I search with chars like '(' it causes an error ("Uncaught SyntaxError: Invalid regular expression: /(sam|at|()/: Unterminated group ") anyway to solve this by preventing collission with regex?Shivery
Awesome answer! Love that it highlights the terms regardless of where they appear. Very cool. Thanks for updating the post. One question I did have is about the .ui-menu-item-highlight class you use. Is this expected to be defined by jquery-ui or by consumers? I changed the class name to suite my own means and simply made the font-weight bold. .jqAutocompleteMatch { font-weight: bold; }Vilhelmina
@IdanShechter Great comment. Some logic to escape the this.term for regex should be used before doing any processing. See Escape string for use in Javascript regex as one of many answers to how to do this.Vilhelmina
F
3

for an even easier way, try this:

$('ul: li: a[class=ui-corner-all]').each (function (){      
 //grab each text value 
 var text1 = $(this).text();     
 //grab user input from the search box
 var val = $('#s').val()
     //convert 
 re = new RegExp(val, "ig") 
 //match with the converted value
 matchNew = text1.match(re);
 //Find the reg expression, replace it with blue coloring/
 text = text1.replace(matchNew, ("<span style='font-weight:bold;color:green;'>")  + matchNew +    ("</span>"));

    $(this).html(text)
});
  }
Fbi answered 27/8, 2010 at 20:57 Comment(0)
E
3

Here's a rehash of Ted de Koning's solution. It includes :

  • Case insensitive search
  • Finding many occurrences of the searched string
$.ui.autocomplete.prototype._renderItem = function (ul, item) {

    var sNeedle     = item.label;
    var iTermLength = this.term.length; 
    var tStrPos     = new Array();      //Positions of this.term in string
    var iPointer    = 0;
    var sOutput     = '';

    //Change style here
    var sPrefix     = '<strong style="color:#3399FF">';
    var sSuffix     = '</strong>';

    //Find all occurences positions
    tTemp = item.label.toLowerCase().split(this.term.toLowerCase());
    var CharCount = 0;
    tTemp[-1] = '';
    for(i=0;i<tTemp.length;i++){
        CharCount += tTemp[i-1].length;
        tStrPos[i] = CharCount + (i * iTermLength) + tTemp[i].length
    }

    //Apply style
    i=0;
    if(tStrPos.length > 0){
        while(iPointer < sNeedle.length){
            if(i<=tStrPos.length){
                //Needle
                if(iPointer == tStrPos[i]){
                    sOutput += sPrefix + sNeedle.substring(iPointer, iPointer + iTermLength) + sSuffix;
                    iPointer += iTermLength;
                    i++;
                }
                else{
                    sOutput += sNeedle.substring(iPointer, tStrPos[i]);
                    iPointer = tStrPos[i];
                }
            }
        }
    }


    return $("<li></li>")
        .data("item.autocomplete", item)
        .append("<a>" + sOutput + "</a>")
        .appendTo(ul);
};
Equip answered 24/8, 2012 at 22:31 Comment(1)
I think you are re-inventing the wheel! Why don't you use regular expressions which are faster, easier and more compact than all this code?Mansard
R
2

Here is a version that does not require any regular expressions and matches multiple results in the label.

$.ui.autocomplete.prototype._renderItem = function (ul, item) {
            var highlighted = item.label.split(this.term).join('<strong>' + this.term +  '</strong>');
            return $("<li></li>")
                .data("item.autocomplete", item)
                .append("<a>" + highlighted + "</a>")
                .appendTo(ul);
};
Ramayana answered 8/12, 2011 at 13:55 Comment(2)
This is probably the best solution, but string.split is capable of only case-sensitive matches, I believe.Dangerfield
Do you have any way to match words based on non-case-sensitive ? Because if I have a search "alfa", it will not highlight "Alfa"Weekley
C
1

Take a look at the combobox demo, it includes result highlighting: http://jqueryui.com/demos/autocomplete/#combobox

The regex in use there also deals with html results.

Clavicle answered 4/4, 2010 at 22:56 Comment(1)
doesn't appear to anymore (as far as I can tell)Vilhelmina
N
1

Here is my version:

  • Uses DOM functions instead of RegEx to break strings/inject span tags
  • Only the specified autocomplete is affected, not all
  • Works with UI version 1.9.x
function highlightText(text, $node) {
    var searchText = $.trim(text).toLowerCase(),
        currentNode = $node.get(0).firstChild,
        matchIndex,
        newTextNode,
        newSpanNode;
    while ((matchIndex = currentNode.data.toLowerCase().indexOf(searchText)) >= 0) {
        newTextNode = currentNode.splitText(matchIndex);
        currentNode = newTextNode.splitText(searchText.length);
        newSpanNode = document.createElement("span");
        newSpanNode.className = "highlight";
        currentNode.parentNode.insertBefore(newSpanNode, currentNode);
        newSpanNode.appendChild(newTextNode);
    }
}
$("#autocomplete").autocomplete({
    source: data
}).data("ui-autocomplete")._renderItem = function (ul, item) {
    var $a = $("<a></a>").text(item.label);
    highlightText(this.term, $a);
    return $("<li></li>").append($a).appendTo(ul);
};

Highlight matched text example

Neva answered 28/1, 2014 at 8:13 Comment(0)
A
1

you can use folowing code:

lib:

$.widget("custom.highlightedautocomplete", $.ui.autocomplete, {
    _renderItem: function (ul, item) {
        var $li = $.ui.autocomplete.prototype._renderItem.call(this,ul,item);
        //any manipulation with li
        return $li;
    }
});

and logic:

$('selector').highlightedautocomplete({...});

it creates custom widget that can override _renderItem without overwriting _renderItem of original plugin prototype.

in my example also used original render function to some simplify code

it's important thing if you want to use plugin in different places with different view of autocomplete and don't want to break your code.

Aloe answered 16/5, 2016 at 18:39 Comment(1)
Generally it is better to spend time answering newer questions, or ones without answers rather than answering a question 6 years old that has already been answered.Gutshall
S
0

If you instead use the 3rd party plugin, it has a highlight option: http://docs.jquery.com/Plugins/Autocomplete/autocomplete#url_or_dataoptions

(see the Options tab)

Sydneysydnor answered 12/3, 2010 at 21:26 Comment(2)
yeah I'm aware of this plug in. however, we are using jQueryUI in our app so it would be nice to get this working with jQueryUI Autocomplete plug-inBumpy
As of 2010-06-23, the jQuery Autocomplete plugin has been deprecated in favor of the jQuery UI Autocomplete plugin. See bassistance.de/jquery-plugins/jquery-plugin-autocomplete for more informationDuodenitis
W
0

To support multiple values, just simply add following function:

function getLastTerm( term ) {
  return split( term ).pop();
}

var t = String(item.value).replace(new RegExp(getLastTerm(this.term), "gi"), "<span class='ui-state-highlight'>$&</span>");
Woadwaxen answered 9/12, 2013 at 3:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.