Incremental Search //JavaScript Repository

Description

Auto-complete for inputs similar to gmail.
Created: 2005.08.06 - Modified 2013.09.17

Dependencies

Code (Download)

//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/dhtml/incremental-search [rev. #5]

IncrementalSearch = function (input, callback, className) {
    var i, $ = this;
    ($.input = input).autocomplete = "off", $.callback = callback || function () { },
    $.className = className || "", $.hide(), $.visible = 0;
    for(i in { keydown: 0, focus: 0, blur: 0, keyup: 0, keypress: 0 })
        addEvent(input, i, $._handler, $);
};
with({ p: IncrementalSearch.prototype }) {
    p.show = function () {
        for(var $ = this, s = document.body.appendChild($.c).style, o = $.input, x = o.offsetLeft,
            y = o.offsetTop + o.offsetHeight; o = o.offsetParent; x += o.offsetLeft, y += o.offsetTop);
        s.left = x + "px", s.top = y + "px",
        $.l.length ? (s.display = "block", !$.visible && ($._callEvent("onshow"), ++$.visible), $.highlite(0)) : s.display = "none";
    };
    p.hide = function () {
        var $ = this, s = ($.c && $.c.parentNode && $.c.parentNode.removeChild($.c),
        $.c = document.createElement("div")).style;
        $.l = [], $.i = -1, $.c.className = $.className, s.position = "absolute", s.display = "none";
        $._old = null, $.visible && ($._callEvent("onhide"), --$.visible);
    };
    p.add = function (s, x, data) {
        var $ = this, l = 0, d = document, i = $.l.length, v = $.input.value.length,
            o = ($.l[i] = [s, data, $.c.appendChild(d.createElement("div"))])[2];
        if(x instanceof Array || (x = [x]), o.i = i, o.className = "normal", !isNaN(x[0]))
            for(var j = -1, k = x.length; ++j < k; o.appendChild(d.createTextNode(
                s.substring(l, x[j]))).parentNode.appendChild(d.createElement(
                "span")).appendChild(d.createTextNode(s.substring(x[j],
                l = x[j] + v))).parentNode.className = "highlited");
        for(x in o.appendChild(d.createTextNode(s.substr(l))), { click: 0, mouseover: 0 })
            addEvent(o, x, $._handler, $);
    };
    p.highlite = function (i) {
        var $ = this;
        $._invalid(i) || ($._invalid($.i) || ($.l[$.i][2].className = "normal"),
        $.l[$.i = i][2].className += " selected", $._callEvent("onhighlite", $.l[i][0], $.l[i][1]));
    };
    p.select = function (i) {
        var $ = this;
        $._invalid(i = isNaN(i) ? $.i : i) || ($._callEvent("onselect",
            $.input.value = $.l[$.i][0], $.l[i][1]), $.hide());
    };
    p.next = function () {
        var $ = ($ = this, $.highlite(($.i + 1) % $.l.length));
    };
    p.previous = function () {
        var $ = ($ = this, $.highlite((!$.i ? $.l.length : $.i) - 1));
    };
    p._fadeOut = function () {
        var f = (f = function () { arguments.callee.x.hide(); }, f.x = this, setTimeout(f, 200));
    };
    p._handler = function (e) {
        var $ = this, t = e.type, k = e.key;
        t == "focus" || t == "keyup" ? k != 40 && k != 38 && k != 13 && $._old != $.input.value && ($.hide(), $.callback($, $.input.value))
        : t == "keydown" ? k == 40 ? $.next() : k == 38 ? $.previous() : $._old = $.input.value
        : t == "keypress" ? k == 13 && (e.preventDefault(), $.select())
        : t == "blur" ? $._fadeOut() : t == "click" ? $.select()
        : $.highlite((/span/i.test((e = e.target).tagName) ? e.parentNode : e).i);
    };
    p._invalid = function (i) {
        return isNaN(i) || i < 0 || i >= this.l.length;
    }
    p._callEvent = function (e) {
        var $ = this;
        return $[e] instanceof Function ? $[e].apply($, [].slice.call(arguments, 1)) : undefined;
    };
}

Example (Example)

<style type="text/css">
/*container da lista | container of the list*/
.autocomplete{
    cursor: pointer;
    border: 1px solid #999;
    border-top: none;
    background: #eee;
}
/*items da listagem | items of the list*/
.autocomplete .normal{border-top: 1px solid #999;}
/*selectedionado item | selected item*/
.autocomplete .selected{background: #ddf;}
/*caracteres que combinaram | characters that matched*/
.autocomplete .highlited{font-weight: bold; color: #008;}
</style>

<form action="">
    <fieldset>
        <legend>Preenchimento din?mico | Dynamic filling</legend>
        <label for="listB">
        Busca case-insensitive em todas as partes da string.
        <br />Case-insensitive search in all the parts of the string.
        </label>
        <input type="text" id="listB" />

        <br /><label for="listA">
        Busca case-sensitive come?ando a partir do inicio da string.
        <br />Case-sensitive search starting from the beginning of the string.
        </label>
        <input type="text" id="listA" />

        <p>Para corrigir o bug do IE ao criar elementos sobre &lt;select&gt;'s, olhe o exemplo dispon?vel aqui: 
        <a href="/geral/hittest#example-header" title="Hit Test">Hit Test</a>.
        <br />To correct the IE bug when creating elements over &lt;select&gt;'s, look at the example available here:
        <a href="/geral/hittest#example-header" title="Hit Test">Hit Test</a>.
</p>
    </fieldset>
</form>
<div id="label" style="clear: both;"></div>

<script type="text/javascript">
//<![CDATA[

var list = [
    "Osama Bin Laden", "Ed?lson Pereira de Carvalho", "Jonas Raoni Soares Silva",
    "Carlos R. L. Rodrigues", "George Bush", "Pedro de Lara", "Britney Spears", "Charles Bronson",
    "Roberto Jefferson", "Silvio Santos", "Tati Quebra Barraco", "William Bonner"
].sort();

document.getElementById("label").innerHTML = "<br /><b>Nomes Dispon?veis | Available names:</b><br />" + list.join("<br />");

//-- Busca simples / Simple search ------------------
new IncrementalSearch(document.forms[0].listA, function(o, search){
    if(!search)
        return;
    for(var i = -1, l = list.length; ++i < l;)
        /*se encontrou "search" no come?o da string (index == 0)
        if "search" was found in the beginning of the string (index == 0)*/
        if(!list[i].indexOf(search))
            /*adiciona o item na listagem, informando que a posi??o onde a palavra foi encontrada ? 0
            adds the item to the list, telling that the position where the word was found is 0*/
            o.add(list[i], 0);
    /*shows the list
    exibe a listagem*/
    o.show();
}, "autocomplete");



//-- Busca m?ltiplas ocorr?ncias / Searches for multiple matches ----
function getNames(o, search){
    if(search = search.toLowerCase())
        for(var i = -1, l = list.length; ++i < l;){
            /*procura todas as ocorr?ncias de "search" e adiciona os ?ndices em um array
            searches all the matches of "search" and adds the indexes in an array */
            for(var j = 0, indices = []; j = list[i].toLowerCase().indexOf(search, j) + 1;
                indices[indices.length] = j - 1);
            /*se alguma ocorr?ncia foi encontrada, adiciona o item e passa a posi??o das ocorr?ncias
            if any ocurrence was found, adds the item and pass the position of the matches*/
            if(indices.length)
                o.add(list[i], indices);
        }
    o.show();
}
new IncrementalSearch(document.forms[0].listB, getNames, "autocomplete");

//]]>
</script>

Help

CSS Classes

.NameOfTheClass
It's applied to the div that contains the list.
.NameOfTheClass .normal
It's applied on every list item.
.NameOfTheClass .selected
It's applied on the selected item.
.NameOfTheClass .highlited
It's applied just on the characters that matched the search.

Constructor

IncrementalSearch(field: HTMLInputElement, searchCallback: Function(IncrementalSearch, String): void, className: String)
Generates an instance of IncrementalSearch.
input
input element that will be linked with the auto-complete
searchCallback
callback function that will be called whenever the user changes the input content, the first parameter is the IncrementalSearch instance itself and the second parameter is the current input text
className
CSS classname that will be used by the auto-complete

Methods

IncrementalSearch.show(void): void
Shows the list.
IncrementalSearch.hide(void): void
Clears and hides the list.
IncrementalSearch.add(caption: String, [matchIndexes: Object = null], [data: Object = null]): void
Adds an item to the list.
caption
text that will be shown in the list
matchIndexes
position where the searched string (the current input text) was found, if there's more than one match of the searched string and you want to highlight them, then just send an array containing all the indexes, if you don't send anything, the text won't be highlighted
data
can be anything, this data will be associated with the item and, you'll have access to it through the IncrementalSearch's events
IncrementalSearch.next(void): void
Focus the next item on the list.
IncrementalSearch.previous(void): void
Focus the previous item on the list.
IncrementalSearch.highlite(index: Integer): void
Focus a determinated item.
index
position of the item that should be focused
IncrementalSearch.select([index: Integer = CURRENT_ITEM]): void
Selects the item specified in index, calls the onselect event and closes the list.
index
position of the item that should be selected

Events

IncrementalSearch.onhighlite: Function(caption: String, data: Object): void
Occurs whenever an item receives focus.
caption
contains the item text
data
contains the data that was previously associated with the item
IncrementalSearch.onselect: Function(caption: String, data: Object): void
Occurs whenever an item is selected.
caption
contains the item text
data
contains the data that was previously associated with the item
IncrementalSearch.onshow: Function(void): void
Occurs when the list is shown.
IncrementalSearch.onhide: Function(void): void
Occurs when the list is hidden.

Properties

IncrementalSearch.input: HTMLInputElement
References to the linked field.
IncrementalSearch.callback: Function(IncrementalSearch, String): void
References to the callback function.
IncrementalSearch.className: String
CSS classname that will be used by the IncrementalSearch.
IncrementalSearch.visible: Boolean
Indicates if the list is visible.
IncrementalSearch.c: HTMLElement
It's the <div> that contains the list (it's recreated whenever the list is hidden).
IncrementalSearch.i: Integer
Keeps the index of the focused item.
IncrementalSearch.l: Array
It's an array containing information about the items, each line is an item, the first column is the item text, the second contains the data that was associated with the item and the third is a reference to the <div> item on the list.

Rank (Votes: 99)

3.24