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 9cbdae2212ce9443cee3d20d28d6eeb3d522db6e
 (DIR) parent 3c60e3f53de3ffe7c696d316ae627a30cdaa384e
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Sat, 15 Jul 2017 13:43:24 +0200
       
       datalist improvements
       
       - add function to get the request url dynamically: this can
         be used for example to combine fields for autocompletion.
       - add example for remote url.
       - for a remote request open the complete list immediately if it is
         unchanged.
       - remote url: allow both a list or a dict with { label: "", value: "" }
       
       Diffstat:
         M datalist/README                     |       7 ++++---
         M datalist/datalist.js                |      66 +++++++++++++++++++++----------
         M datalist/example.html               |      10 ++++++++++
       
       3 files changed, 60 insertions(+), 23 deletions(-)
       ---
 (DIR) diff --git a/datalist/README b/datalist/README
       @@ -8,10 +8,11 @@ FEATURES
        --------
        
        - Small:
       -  - Filesize: +- 6.7KB.
       -  - Lines: +- 235, not much code, so hopefully easy to understand.
       +  - Filesize: +- 7.2KB.
       +  - Lines: +- 250, not much code, so hopefully easy to understand.
          - No dependencies on other libraries like jQuery.
       -- (Graceful) fallback to HTML5 datalist if Javascript is disabled.
       +- (Graceful) fallback to HTML5 datalist if Javascript is disabled for inline
       +  datalist.
        - Filtering values: case-insensitively, tokenized (separated by space).
        - Supports querying a remote server for results using a JSON XMLHttpRequest.
        - Permissive ISC license, see LICENSE file, feel free to contact me for
 (DIR) diff --git a/datalist/datalist.js b/datalist/datalist.js
       @@ -9,34 +9,54 @@ function datalist_init(input) {
                        dropdown = document.createElement("div"),
                        prevmatches = [],
                        prevvalue = null,
       -                url = input.getAttribute("data-url") || "";
       +                url = input.getAttribute("data-url") || "",
       +                urlfn = input.getAttribute("data-urlfn") || "";
                dropdown.className = "datalist-dropdown";
        
       +        var getvalue = function(el) {
       +                return el.getAttribute("data-value") ||
       +                        el.textContent || el.innerText;
       +        };
       +
                var createitem = function(s) {
       -                var div = document.createElement("div");
       -                div.innerHTML = s;
       +                var label, value, div = document.createElement("div");
       +                if (typeof(s) === "string") {
       +                        label = value = s;
       +                } else {
       +                        label = s.label;
       +                        value = s.value;
       +                }
       +
       +                div.innerHTML = label;
       +                div.setAttribute("data-value", value);
                        div.addEventListener("mousedown", function() {
       -                        input.value = this.textContent || this.innerText;
       +                        input.value = getvalue(this);
                                datalist_show(false);
                        }, false);
                        div.addEventListener("mousemove", function() {
                                if (mouse)
                                        datalist_setsel(this);
                        }, false);
       -                return { el: div, search: ((div.textContent || div.innerText).toLowerCase() || "").split(" ") };
       +                return { el: div, label: label, value: value };
                };
        
       -        if (url.length) {
       +        if (url.length || urlfn.length) {
       +                urlfn = urlfn.length ? window[urlfn] : function(s) {
       +                        return url + encodeURIComponent(s);
       +                };
       +
                        // "throttled" JSON XMLHttpRequest.
       -                var timer = null;
       -                datalist_match = function(s, fn) {
       +                var timer = null, prevurl = "";
       +                datalist_match = function(s, fn, ev) {
                                clearTimeout(timer);
       +
       +                        url = urlfn(s);
       +                        if (url === prevurl) {
       +                                fn(prevmatches);
       +                                return;
       +                        }
       +
                                timer = setTimeout(function() {
       -                                s = s.toLowerCase();
       -                                if (s === prevvalue) {
       -                                        fn(prevmatches);
       -                                        return;
       -                                }
                                        var x = new(XMLHttpRequest);
                                        x.onreadystatechange = function() {
                                                if (x.readyState != 4 || [ 0, 200 ].indexOf(x.status) == -1)
       @@ -47,21 +67,26 @@ function datalist_init(input) {
                                                for (var i = 0; i < o.length; i++)
                                                        prevmatches.push(createitem(o[i]));
        
       -                                        prevvalue = s;
       +                                        prevurl = url;
                                                fn(prevmatches);
                                        };
       -                                x.open("GET", url + encodeURIComponent(s) + "&t=" + String(new Date().getTime()), true);
       +
       +                                x.open("GET", url + "&t=" + String(new Date().getTime()), true);
                                        x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                                        x.timeout = 10000;
                                        x.send();
       -                        }, 150); // delay in ms.
       +                                // delay in ms: throttle request on change, but on focus/click open fast.
       +                        }, ev == "onchange" ? 150 : 1);
                        };
                } else {
                        // use inline <datalist>.
                        if (attrlist === null || ellist === undefined)
                                return;
       -                for (var i = 0, ec = ellist.children; i < ec.length; i++)
       -                        items.push(createitem(ec[i].innerHTML));
       +                for (var i = 0, ec = ellist.children, o; i < ec.length; i++) {
       +                        var o = createitem(getvalue(ec[i]));
       +                        o.search = o.label.toLowerCase().split(" ");
       +                        items.push(o);
       +                }
        
                        var datalist_filter = function(data, s) {
                                var matches = [], tok = s.toLowerCase().split(" ");
       @@ -89,6 +114,7 @@ function datalist_init(input) {
                                        fn(prevmatches);
                                        return;
                                }
       +
                                // 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) ||
       @@ -131,7 +157,7 @@ function datalist_init(input) {
                        switch (e.which) {
                        case 13: // return
                                if (cursel)
       -                                input.value = cursel.textContent || cursel.innerText;
       +                                input.value = getvalue(cursel);
                                if (!datalist_visible)
                                        return;
                                datalist_show(false);
       @@ -195,7 +221,7 @@ function datalist_init(input) {
                                        datalist_setsel(m[0].el);
                                datalist_render(m);
                                datalist_show(!!m.length);
       -                });
       +                }, "onchange");
                };
                input.addEventListener("input", onchange);
                input.addEventListener("keyup", function(e) {
 (DIR) diff --git a/datalist/example.html b/datalist/example.html
       @@ -32,8 +32,18 @@
        <label for="remote">OS: </label>
        <input type="text" placeholder="Select OS..." value="" data-url="example-data.json?q=" name="remote" id="os" class="datalist" /><br/>
        
       +<p>Using XMLHttpRequest + custom url function + JSON:</p>
       +
       +<label for="remotecustom">OS: </label>
       +<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_urlfn" name="remotecustom" id="os" class="datalist" /><br/>
       +
        </form>
        
       +<script type="text/javascript">
       +function custom_urlfn(s) {
       +        return "example-data.json?q=" + encodeURIComponent(s);
       +};
       +</script>
        <script type="text/javascript" src="datalist.js"></script>
        
        </body>