datalist: improvements - jscancer - Javascript crap (relatively small)
 (HTM) git clone git://git.codemadness.org/jscancer
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit d04e4412eb81de4e4f0eb8542a089fd66a03331a
 (DIR) parent 392f4d4e2c582f2132e1d162012157a1d1c655fa
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Thu,  2 Jun 2016 18:50:51 +0200
       
       datalist: improvements
       
       - align dropdown menu better in some cases: for example in a table cell.
       - align dropdown menu width with initial input box width (looks nicer).
       - separate datalist_filter and datalist_match, only update the results if the
         search query has effect (faster).
       - on RETURN key and the datalist is already closed use the default action:
         useful for form submit (default behaviour).
       - improve behaviour on focus and focus lost (onblur).
       
       Diffstat:
         M datalist/datalist.css               |       1 -
         M datalist/datalist.js                |      93 +++++++++++++++++++++----------
       
       2 files changed, 63 insertions(+), 31 deletions(-)
       ---
 (DIR) diff --git a/datalist/datalist.css b/datalist/datalist.css
       @@ -3,7 +3,6 @@
                position: absolute;
                overflow: auto;
                z-index: 999;
       -        width: 600px;
                padding: 0;
                background-color: #fff;
                border: 1px solid #33bbff;
 (DIR) diff --git a/datalist/datalist.js b/datalist/datalist.js
       @@ -7,7 +7,13 @@ function datalist_init(input) {
                var cursel = null, items = [], mouse = true, // enable mouse event handling.
                        dropdown = document.createElement("div");
                dropdown.className = "datalist-dropdown";
       -        dropdown.style.left = String(input.offsetLeft) + "px";
       +        var left = 0;
       +        for (var c = input; c; c = c.offsetParent) {
       +                if (["absolute", "fixed"].indexOf(c.style.position) == -1)
       +                        left += c.offsetLeft;
       +        }
       +        dropdown.style.left = String(left) + "px";
       +        dropdown.style.minWidth = String(input.clientWidth) + "px";
        
                for (var i = 0, ec = ellist.children; i < ec.length; i++) {
                        var div = document.createElement("div");
       @@ -23,37 +29,54 @@ function datalist_init(input) {
                        items.push({ el: div, search: ((div.textContent || div.innerText).toLowerCase() || "").split(" ") });
                }
        
       -        var datalist_match = function(s) {
       +        var datalist_filter = function(data, s) {
                        var matches = [], tok = s.toLowerCase().split(" ");
       -                for (var i = 0; i < items.length; i++) {
       +                for (var i = 0; i < data.length; i++) {
                                var fc = 0;
                                for (var k = 0; k < tok.length && fc < tok.length; k++) {
                                        var f = false;
       -                                for (var j = 0; j < items[i].search.length && fc < tok.length && !f; j++)
       -                                        for (var l = 0; l < items[i].search.length && !f; l++)
       -                                                if (items[i].search[l].indexOf(tok[k]) != -1)
       +                                for (var j = 0; j < data[i].search.length && fc < tok.length && !f; j++)
       +                                        for (var l = 0; l < data[i].search.length && !f; l++)
       +                                                if (data[i].search[l].indexOf(tok[k]) != -1)
                                                                f = true;
                                        if (f)
                                                fc++;
                                }
                                /* all tokens (separated by space) must match. */
                                if (fc == tok.length)
       -                                matches.push(items[i]);
       +                                matches.push(data[i]);
                        }
                        return matches;
       -        },
       -        datalist_render = function(m) {
       -                var p = dropdown.parentNode;
       +        };
       +        var prevmatches = [];
       +        var prevvalue = null;
       +        var datalist_match = function(s) {
       +                s = s.toLowerCase();
       +                if (s === prevvalue)
       +                        return prevmatches;
       +                // if token string is different or string not in previous search: use raw data,
       +                // else filter on existing data and no need to sort.
       +                if (prevvalue === null || (prevvalue.split(" ").length != s.split(" ").length) ||
       +                    s.indexOf(prevvalue) == -1)
       +                        prevmatches = datalist_filter(items, s);
       +                else
       +                        prevmatches = datalist_filter(prevmatches, s);
       +                prevvalue = s;
       +                return prevmatches;
       +        };
       +        var datalist_render = function(m) {
                        var dd = dropdown.cloneNode(false);
                        for (var i = 0; i < m.length; i++)
                                dd.appendChild(m[i].el);
       -                p.replaceChild(dd, dropdown)
       +                dropdown.parentNode.replaceChild(dd, dropdown)
                        dropdown = dd;
       -        },
       -        datalist_show = function(status) {
       +        };
       +        var datalist_visible = false;
       +        var datalist_show = function(status) {
       +                datalist_visible = status;
                        dropdown.className = "datalist-dropdown " + (status ? "visible" : "");
       -        },
       -        datalist_setsel = function(el) {
       +        };
       +        var datalist_setsel = function(el) {
                        if (cursel)
                                cursel.className = "";
                        cursel = el;
       @@ -66,7 +89,11 @@ function datalist_init(input) {
                        case 13: // return
                                if (cursel)
                                        input.value = cursel.textContent || cursel.innerText;
       +                        if (!datalist_visible)
       +                                return datalist_show(false);
                                datalist_show(false);
       +                        e.stopPropagation();
       +                        return !!e.preventDefault();
                        case 27: break; // escape
                        case 33: // page up.
                        case 34: // page down.
       @@ -109,18 +136,8 @@ function datalist_init(input) {
                                }
                        }
                }, false);
       -        input.addEventListener("keyup", function(e) {
       -                mouse = true;
       -                switch (e.which) {
       -                case 13: // return
       -                case 27: // escape
       -                        datalist_show(false);
       -                case 33: // page up.
       -                case 34: // page down.
       -                case 38: // arrow up
       -                case 40: // arrow down
       -                        return;
       -                }
       +
       +        var onchange = function() {
                        var m = datalist_match(input.value);
                        // check if selection is still active in matches.
                        if (cursel) {
       @@ -138,8 +155,7 @@ function datalist_init(input) {
                                datalist_render(m);
                        }
                        datalist_show(!!m.length);
       -        }, false);
       -
       +        };
                var focuschange = function(e) {
                        datalist_setsel(null);
                        if (e.target === input) {
       @@ -152,7 +168,24 @@ function datalist_init(input) {
                                datalist_show(false);
                        }
                };
       -        document.addEventListener("focus", focuschange, false);
       +        input.addEventListener("input", function() {
       +                onchange();
       +        });
       +        input.addEventListener("keyup", function(e) {
       +                mouse = true;
       +                switch (e.which) {
       +                case 13: // return
       +                case 27: // escape
       +                        datalist_show(false);
       +                case 33: // page up.
       +                case 34: // page down.
       +                case 38: // arrow up
       +                case 40: // arrow down
       +                        return;
       +                }
       +                onchange();
       +        }, false);
       +        input.addEventListener("focus", focuschange, false);
                document.addEventListener("click", focuschange, false);
        
                input.parentNode.insertBefore(dropdown, input.nextSibling);