datalist.js - jscancer - Javascript crap (relatively small)
 (HTM) git clone git://git.codemadness.org/jscancer
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       datalist.js (12086B)
       ---
            1 function datalist_init(input) {
            2         var tablemode = false;
            3         var attrlist = input.getAttribute("data-table") || "";
            4         if (attrlist !== "")
            5                 tablemode = true;
            6         else
            7                 attrlist = input.getAttribute("list")
            8         var ellist = document.getElementById(attrlist);
            9         var thead = null;
           10         if (tablemode) {
           11                 if (ellist.tHead)
           12                         thead = ellist.tHead;
           13         }
           14         input.removeAttribute("list");
           15         input.autocomplete = "off";
           16 
           17         var cursel = null, items = [], mouse = true, // enable mouse event handling.
           18                 datalist_match,
           19                 dropdown = document.createElement("div"),
           20                 prevmatches = [],
           21                 prevvalue = null,
           22                 url = input.getAttribute("data-url") || "",
           23                 urlfn = input.getAttribute("data-urlfn") || "",
           24                 indom = false;
           25         dropdown.className = "datalist-dropdown";
           26 
           27         var ctx = {input: input, dropdown: dropdown};
           28 
           29         var getlabel = function(el) {
           30                 return el.textContent || el.innerText || "";
           31         };
           32 
           33         // set new input value.
           34         var setvalue = function(el, value) {
           35                 var oldvalue = el.value;
           36                 if (input.oldvalue === value)
           37                         return;
           38                 el.value = el.oldvalue = value; // set new value.
           39                 el.dispatchEvent(new Event("value-changed"));
           40         };
           41         input.oldvalue = input.defaultValue; // store previous value.
           42 
           43         // get last selection value (can be different from input value).
           44         var getselvalue = function(el) {
           45                 var value = el.getAttribute("data-value");
           46                 if (value !== null)
           47                         return value;
           48                 value = el.value;
           49                 if (value !== null)
           50                         return value;
           51                 return el.textContent || el.innerText || "";
           52         };
           53 
           54         // set selection value (can be different from input value).
           55         var setselvalue = function(el, value) {
           56                 el.dispatchEvent(new Event("selection"));
           57                 var oldvalue = el.value;
           58                 setvalue(el, value);
           59                 if (oldvalue !== value)
           60                         el.dispatchEvent(new Event("selection-changed"));
           61         };
           62 
           63         var datalist_enabled = function() {
           64                 return !input.readOnly && !input.disabled && !input.hidden;
           65         };
           66 
           67         // create item: expects string, or object with name and label attribute.
           68         var createitem = function(o) {
           69                 var label, row, value;
           70                 if (!tablemode)
           71                         row = document.createElement("div");
           72                 if (typeof(o) === "string") {
           73                         label = value = o;
           74                         row.textContent = label;
           75                 } else if (typeof(o.getAttribute) == "function") {
           76                         // element
           77                         label = getlabel(o);
           78                         value = getselvalue(o);
           79                         if (tablemode)
           80                                 row = o.cloneNode(true);
           81                         else
           82                                 row.textContent = label;
           83                 } else {
           84                         // (JSON) object
           85                         label = o.label;
           86                         value = o.value;
           87                         row.textContent = label;
           88                 }
           89 
           90                 row.setAttribute("data-value", value);
           91                 row.addEventListener("mousedown", function() {
           92                         if (!datalist_enabled())
           93                                 return;
           94                         setselvalue(input, getselvalue(this));
           95                         datalist_show(false);
           96                 }, false);
           97                 row.addEventListener("mousemove", function() {
           98                         if (mouse)
           99                                 datalist_setsel(this);
          100                 }, false);
          101                 return { el: row, label: label, value: value };
          102         };
          103 
          104         if (url.length || urlfn.length) {
          105                 urlfn = urlfn.length ? window[urlfn] : function(s, input) {
          106                         return url + encodeURIComponent(s);
          107                 };
          108 
          109                 // "throttled" JSON XMLHttpRequest.
          110                 var timer = null, prevurl = "";
          111                 datalist_match = function(s, fn, ev) {
          112                         clearTimeout(timer);
          113 
          114                         if (!datalist_enabled())
          115                                 return;
          116 
          117                         var requrl = urlfn(s, input);
          118                         if (requrl === prevurl) {
          119                                 fn(prevmatches);
          120                                 return;
          121                         }
          122 
          123                         timer = setTimeout(function() {
          124                                 // set class for loading indicator styling.
          125                                 input.classList.add("loading");
          126 
          127                                 var x = new(XMLHttpRequest);
          128                                 x.onreadystatechange = function() {
          129                                         // remove loading indicator when "DONE".
          130                                         if (x.readyState == 4)
          131                                                 input.classList.remove("loading");
          132 
          133                                         if (x.readyState != 4 || [ 0, 200 ].indexOf(x.status) == -1)
          134                                                 return;
          135 
          136                                         prevmatches = [];
          137                                         var o = JSON.parse(x.responseText);
          138                                         tablemode = !!o.columns && !!o.items;
          139                                         if (tablemode) {
          140                                                 // create table header from columns, set name as class.
          141                                                 thead = document.createElement("thead");
          142                                                 var tr = document.createElement("tr");
          143                                                 var valueidx = 0; // default is first column.
          144 
          145                                                 for (var i = 0; i < o.columns.length; i++) {
          146                                                         var th = document.createElement("th");
          147                                                         th.className = o.columns[i].name;
          148                                                         th.textContent = o.columns[i].label;
          149                                                         if (o.columns[i].value)
          150                                                                 valueidx = i;
          151                                                         tr.appendChild(th);
          152                                                 }
          153                                                 thead.appendChild(tr);
          154 
          155                                                 // add items as table rows: support simple array or object.
          156                                                 for (var i = 0; i < o.items.length; i++) {
          157                                                         var tr = document.createElement("tr");
          158                                                         if (Array.isArray(o.items[i])) {
          159                                                                 tr.setAttribute("data-value", o.items[i][valueidx]);
          160                                                                 for (var j = 0; j < o.items[i].length; j++) {
          161                                                                         var td = document.createElement("td");
          162                                                                         td.textContent = o.items[i][j];
          163                                                                         tr.appendChild(td);
          164                                                                 }
          165                                                         } else {
          166                                                                 tr.setAttribute("data-value", o.items[i].value);
          167                                                                 for (var j = 0; j < o.columns.length; j++) {
          168                                                                         var col = o.columns[j];
          169                                                                         var td = document.createElement("td");
          170                                                                         var value = o.items[i][col.name];
          171                                                                         // callback function for value.
          172                                                                         var fnn = "datalist_format_" + col.fn;
          173                                                                         if (typeof(window[fnn]) == "function")
          174                                                                                 value = window[fnn](value, td, tr, i, j);
          175                                                                         if (o.columns[j].html)
          176                                                                                 td.innerHTML = value; /* allow HTML */
          177                                                                         else
          178                                                                                 td.textContent = value;
          179                                                                         tr.appendChild(td);
          180                                                                 }
          181                                                         }
          182                                                         prevmatches.push(createitem(tr));
          183                                                 }
          184                                         } else {
          185                                                 for (var i = 0; i < o.length; i++)
          186                                                         prevmatches.push(createitem(o[i]));
          187                                         }
          188 
          189                                         prevurl = requrl;
          190                                         fn(prevmatches);
          191                                 };
          192 
          193                                 x.open("GET", requrl + "&t=" + String(new Date().getTime()), true);
          194                                 x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
          195                                 x.timeout = 10000;
          196                                 x.send();
          197                                 // delay in ms: throttle request on change, but on focus/click open fast.
          198                         }, ev == "onchange" ? 150 : 1);
          199                 };
          200         } else {
          201                 // use inline <datalist> or table.
          202                 if (attrlist === null || ellist === null)
          203                         return;
          204 
          205                 for (var i = 0, ec = (tablemode && ellist.tBodies.length) ? ellist.tBodies[0].children : ellist.children; i < ec.length; i++) {
          206                         var o = createitem(ec[i]);
          207                         o.search = o.label.toLowerCase().split(" ");
          208                         items.push(o);
          209                 }
          210 
          211                 var datalist_filter = function(data, s) {
          212                         var matches = [], tok = s.toLowerCase().split(" ");
          213                         for (var i = 0; i < data.length; i++) {
          214                                 var fc = 0;
          215                                 for (var k = 0; k < tok.length && fc < tok.length; k++) {
          216                                         var f = false;
          217                                         for (var j = 0; j < data[i].search.length && fc < tok.length && !f; j++)
          218                                                 for (var l = 0; l < data[i].search.length && !f; l++)
          219                                                         if (data[i].search[l].indexOf(tok[k]) != -1)
          220                                                                 f = true;
          221                                         if (f)
          222                                                 fc++;
          223                                 }
          224                                 // all tokens (separated by space) must match.
          225                                 if (fc == tok.length)
          226                                         matches.push(data[i]);
          227                         }
          228                         return matches;
          229                 };
          230 
          231                 datalist_match = function(s, fn) {
          232                         if (!datalist_enabled())
          233                                 return;
          234 
          235                         s = s.toLowerCase();
          236                         if (s === prevvalue) {
          237                                 fn(prevmatches);
          238                                 return;
          239                         }
          240 
          241                         // if token string is different or string not in previous search: use raw data,
          242                         // else filter on existing data and no need to sort.
          243                         if (prevvalue === null || (prevvalue.split(" ").length != s.split(" ").length) ||
          244                             s.indexOf(prevvalue) == -1)
          245                                 prevmatches = datalist_filter(items, s);
          246                         else
          247                                 prevmatches = datalist_filter(prevmatches, s);
          248                         prevvalue = s;
          249                         fn(prevmatches);
          250                 };
          251         }
          252 
          253         var datalist_render = function(m) {
          254                 if (!indom) { // add to DOM on first use when needed.
          255                         document.body.appendChild(dropdown);
          256                         indom = true;
          257                 }
          258 
          259                 var dd = dropdown.cloneNode(false), table, tbody;
          260                 if (tablemode) {
          261                         table = document.createElement("table");
          262                         if (thead)
          263                                 table.tHead = thead.cloneNode(true);
          264                         tbody = document.createElement("tbody");
          265                 }
          266 
          267                 var r = input.getClientRects() || [];
          268                 if (r.length) {
          269                         dd.style.left = String(r[0].left + window.pageXOffset) + "px";
          270                         dd.style.top = String(r[0].top + input.offsetHeight + window.pageYOffset) + "px";
          271                 }
          272                 dd.style.minWidth = String(input.getAttribute("data-minwidth") || input.clientWidth) + "px";
          273                 if (tablemode) {
          274                         for (var i = 0; i < m.length; i++)
          275                                 tbody.appendChild(m[i].el);
          276                         table.appendChild(tbody);
          277                         dd.appendChild(table);
          278                 } else {
          279                         for (var i = 0; i < m.length; i++)
          280                                 dd.appendChild(m[i].el);
          281                 }
          282                 dropdown.parentNode.replaceChild(dd, dropdown)
          283                 dropdown = dd;
          284         };
          285 
          286         var datalist_visible = false;
          287         var datalist_show = function(status) {
          288                 if (status && !datalist_enabled()) // do not show when disabled, but it can be hidden.
          289                         return;
          290                 datalist_visible = status;
          291                 dropdown.className = "datalist-dropdown " + (status ? "visible" : "") + " " + (input.getAttribute("data-class") || "");
          292                 var r = dropdown.getClientRects();
          293                 // if dropdown popup doesn't fit then adjust x, y scroll position so it fits on the screen.
          294                 if (!r.length)
          295                         return;
          296                 if (r[0].left + r[0].width >= window.innerWidth) {
          297                         dropdown.style.left = "auto";
          298                         dropdown.style.right = "0px";
          299                 }
          300                 var ri = input.getClientRects() || [];
          301                 if (input.scrollIntoView && ri.length && r[0].top + dropdown.clientHeight + ri[0].height >= window.innerHeight)
          302                         input.scrollIntoView();
          303         };
          304         var datalist_setsel = function(el) {
          305                 if (cursel)
          306                         cursel.className = "";
          307                 cursel = el;
          308                 if (el)
          309                         el.className = "sel";
          310         };
          311 
          312         input.addEventListener("keydown", function(e) {
          313                 mouse = false;
          314                 switch (e.which) {
          315                 case 13: // return
          316                         if (cursel)
          317                                 setselvalue(input, getselvalue(cursel));
          318                         datalist_show(false);
          319                         e.stopPropagation();
          320                         return !!e.preventDefault();
          321                 case 27: break; // escape
          322                 case 33: // page up.
          323                 case 34: // page down.
          324                 case 38: // arrow up
          325                 case 40: // arrow down
          326                         var sel = cursel, dd = dropdown, dc = tablemode ? dropdown.children[0].tBodies[0].children : dropdown.children;
          327 
          328                         // if last and down arrow switch to first item, if first and up arrow switch to last item.
          329                         if (dc.length) {
          330                                 if (e.which == 38) { // up
          331                                         if (!sel || !(sel = sel.previousSibling))
          332                                                 sel = dc[dc.length - 1];
          333                                 } else if (e.which == 40) { // down
          334                                         if (!sel || !(sel = sel.nextSibling))
          335                                                 sel = dc[0];
          336                                 } else if (!sel) {
          337                                         sel = dc[0];
          338                                 }
          339                         }
          340                         if (cursel && (e.which == 33 || e.which == 34)) {
          341                                 var n = sel.offsetHeight ? (dd.clientHeight / sel.offsetHeight) : 0;
          342                                 if (e.which == 33) { // page up.
          343                                         for (; n > 0 && sel && sel.previousSibling;
          344                                                 n--, sel = sel.previousSibling)
          345                                                 ;
          346                                 } else { // page down.
          347                                         for (; n > 0 && sel && sel.nextSibling;
          348                                                 n--, sel = sel.nextSibling)
          349                                                 ;
          350                                 }
          351                         }
          352                         if (sel) {
          353                                 datalist_setsel(sel);
          354 
          355                                 // only update scroll if needed.
          356                                 if (sel.offsetTop < dd.scrollTop)
          357                                         dd.scrollTop = sel.offsetTop;
          358                                 else if (sel.offsetTop + sel.offsetHeight > dd.scrollTop + dd.offsetHeight)
          359                                         dd.scrollTop = sel.offsetTop;
          360                         }
          361                 }
          362         }, false);
          363 
          364         var onchange = function() {
          365                 setvalue(input, input.value);
          366                 datalist_match(input.value, function(m) {
          367                         // check if selection is still active in matches.
          368                         if (cursel) {
          369                                 var hassel = false;
          370                                 for (var i = 0; i < m.length && !(hassel = (m[i].el === cursel)); i++)
          371                                         ;
          372                                 if (!hassel)
          373                                         datalist_setsel(null);
          374                         }
          375                         // only one match? select it.
          376                         if (m.length == 1)
          377                                 datalist_setsel(m[0].el);
          378                         datalist_render(m);
          379                         datalist_show(!!m.length);
          380                 }, "onchange");
          381         };
          382 
          383         input.addEventListener("input", onchange);
          384         input.addEventListener("keyup", function(e) {
          385                 mouse = true;
          386                 switch (e.which) {
          387                 case 13: // return
          388                 case 27: // escape
          389                         datalist_show(false);
          390                 case 33: // page up.
          391                 case 34: // page down.
          392                 case 38: // arrow up
          393                 case 40: // arrow down
          394                         return;
          395                 }
          396                 onchange();
          397         }, false);
          398         input.addEventListener("focus", function() {
          399                 datalist_setsel(null);
          400                 datalist_match(input.value, function(m) {
          401                         datalist_render(m);
          402                         datalist_show(!!m.length);
          403                         dropdown.scrollTop = 0; // reset scroll.
          404                 });
          405         }, false);
          406         input.addEventListener("blur", function() {
          407                 mouse = true;
          408                 datalist_setsel(null);
          409                 datalist_show(false);
          410         }, false);
          411 
          412         return ctx;
          413 }
          414 
          415 var els = document.getElementsByClassName("datalist");
          416 if (els !== null)
          417         for (var i = 0; i < els.length; i++)
          418                 datalist_init(els[i]);