jscancer

Javascript crap (relatively small)
git clone git://git.codemadness.org/jscancer
Log | Files | Refs | README | LICENSE

commit ce985f99424e909b959b7cf2736312074e00d4cf
parent f57d63c6ffac03ddf58f78a5fb3958c1caf9a7d2
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Thu, 25 Apr 2024 20:04:43 +0200

datalist: add bloat

Diffstat:
Mdatalist/README | 12+++++++-----
Mdatalist/datalist.css | 23+++++++++++++++++++++++
Mdatalist/datalist.js | 190++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Adatalist/example-data-cols-small.json | 28++++++++++++++++++++++++++++
Adatalist/example-data-cols.json | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adatalist/example-json.html | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 377 insertions(+), 36 deletions(-)

diff --git a/datalist/README b/datalist/README @@ -1,25 +1,27 @@ datalist ======== -small dropdown filter / autocomplete script. +relatively small dropdown filter / autocomplete script. FEATURES -------- - Small: - - Filesize: +- 7.2KB. - - Lines: +- 275, not much code, so hopefully easy to understand. + - Filesize: +- 12KB. + - Lines: +- 420, not much code, so hopefully easy to understand. - No dependencies on other libraries like jQuery. - (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. +- Show a table with multiple columns in the list, with nice alignment of items. + - Support HTML, for example for thumbnail images. + - Support a callback function per cell, row or to reformat the value. - Permissive ISC license, see LICENSE file. -- Officially supported browsers are: +- Supported browsers are: - Firefox and Firefox ESR. - Chrome and most recent webkit-based browsers. - - IE10+. EXAMPLES diff --git a/datalist/datalist.css b/datalist/datalist.css @@ -23,3 +23,26 @@ datalist { display: none; } + +/* multi-column table in dropdown */ +.datalist-dropdown table { + border-collapse: collapse; + width: 100%; +} +.datalist-dropdown thead { + position: sticky; + top: 0; +} +.datalist-dropdown table th { + text-align: left; + background-color: #eee; + border-bottom: 2px solid #ccc; +} +.datalist-dropdown table th, +.datalist-dropdown table td { + padding: 3px; +} +.datalist-dropdown table tr.sel td { + background-color: #33bbff; + color: #fff; +} diff --git a/datalist/datalist.js b/datalist/datalist.js @@ -1,6 +1,16 @@ function datalist_init(input) { - var attrlist = input.getAttribute("list"), ellist = document.getElementById(attrlist); - + var tablemode = false; + var attrlist = input.getAttribute("data-table") || ""; + if (attrlist !== "") + tablemode = true; + else + attrlist = input.getAttribute("list") + var ellist = document.getElementById(attrlist); + var thead = null; + if (tablemode) { + if (ellist.tHead) + thead = ellist.tHead; + } input.removeAttribute("list"); input.autocomplete = "off"; @@ -10,14 +20,28 @@ function datalist_init(input) { prevmatches = [], prevvalue = null, url = input.getAttribute("data-url") || "", - urlfn = input.getAttribute("data-urlfn") || ""; + urlfn = input.getAttribute("data-urlfn") || "", + indom = false; dropdown.className = "datalist-dropdown"; + var ctx = {input: input, dropdown: dropdown}; + var getlabel = function(el) { return el.textContent || el.innerText || ""; }; - var getvalue = function(el) { + // set new input value. + var setvalue = function(el, value) { + var oldvalue = el.value; + if (input.oldvalue === value) + return; + el.value = el.oldvalue = value; // set new value. + el.dispatchEvent(new Event("value-changed")); + }; + input.oldvalue = input.defaultValue; // store previous value. + + // get last selection value (can be different from input value). + var getselvalue = function(el) { var value = el.getAttribute("data-value"); if (value !== null) return value; @@ -27,39 +51,54 @@ function datalist_init(input) { return el.textContent || el.innerText || ""; }; - var setvalue = function(el, value) { - if (el.value !== value) { - el.value = value; + // set selection value (can be different from input value). + var setselvalue = function(el, value) { + el.dispatchEvent(new Event("selection")); + var oldvalue = el.value; + setvalue(el, value); + if (oldvalue !== value) el.dispatchEvent(new Event("selection-changed")); - } + }; + + var datalist_enabled = function() { + return !input.readOnly && !input.disabled && !input.hidden; }; // create item: expects string, or object with name and label attribute. var createitem = function(o) { - var label, value, div = document.createElement("div"); + var label, row, value; + if (!tablemode) + row = document.createElement("div"); if (typeof(o) === "string") { label = value = o; + row.textContent = label; } else if (typeof(o.getAttribute) == "function") { // element label = getlabel(o); - value = getvalue(o); + value = getselvalue(o); + if (tablemode) + row = o.cloneNode(true); + else + row.textContent = label; } else { // (JSON) object label = o.label; value = o.value; + row.textContent = label; } - div.innerHTML = label; - div.setAttribute("data-value", value); - div.addEventListener("mousedown", function() { - setvalue(input, getvalue(this)); + row.setAttribute("data-value", value); + row.addEventListener("mousedown", function() { + if (!datalist_enabled()) + return; + setselvalue(input, getselvalue(this)); datalist_show(false); }, false); - div.addEventListener("mousemove", function() { + row.addEventListener("mousemove", function() { if (mouse) datalist_setsel(this); }, false); - return { el: div, label: label, value: value }; + return { el: row, label: label, value: value }; }; if (url.length || urlfn.length) { @@ -72,6 +111,9 @@ function datalist_init(input) { datalist_match = function(s, fn, ev) { clearTimeout(timer); + if (!datalist_enabled()) + return; + var requrl = urlfn(s, input); if (requrl === prevurl) { fn(prevmatches); @@ -93,8 +135,56 @@ function datalist_init(input) { prevmatches = []; var o = JSON.parse(x.responseText); - for (var i = 0; i < o.length; i++) - prevmatches.push(createitem(o[i])); + tablemode = !!o.columns && !!o.items; + if (tablemode) { + // create table header from columns, set name as class. + thead = document.createElement("thead"); + var tr = document.createElement("tr"); + var valueidx = 0; // default is first column. + + for (var i = 0; i < o.columns.length; i++) { + var th = document.createElement("th"); + th.className = o.columns[i].name; + th.textContent = o.columns[i].label; + if (o.columns[i].value) + valueidx = i; + tr.appendChild(th); + } + thead.appendChild(tr); + + // add items as table rows: support simple array or object. + for (var i = 0; i < o.items.length; i++) { + var tr = document.createElement("tr"); + if (Array.isArray(o.items[i])) { + tr.setAttribute("data-value", o.items[i][valueidx]); + for (var j = 0; j < o.items[i].length; j++) { + var td = document.createElement("td"); + td.textContent = o.items[i][j]; + tr.appendChild(td); + } + } else { + tr.setAttribute("data-value", o.items[i].value); + for (var j = 0; j < o.columns.length; j++) { + var col = o.columns[j]; + var td = document.createElement("td"); + var value = o.items[i][col.name]; + // callback function for value. + var fnn = "datalist_format_" + col.fn; + if (typeof(window[fnn]) == "function") + value = window[fnn](value, td, tr, i, j); + if (o.columns[j].html) + td.innerHTML = value; /* allow HTML */ + else + td.textContent = value; + tr.appendChild(td); + } + } + prevmatches.push(createitem(tr)); + } + } else { + for (var i = 0; i < o.length; i++) + prevmatches.push(createitem(o[i])); + } prevurl = requrl; fn(prevmatches); @@ -108,10 +198,11 @@ function datalist_init(input) { }, ev == "onchange" ? 150 : 1); }; } else { - // use inline <datalist>. - if (attrlist === null || ellist === undefined) + // use inline <datalist> or table. + if (attrlist === null || ellist === null) return; - for (var i = 0, ec = ellist.children, o; i < ec.length; i++) { + + for (var i = 0, ec = (tablemode && ellist.tBodies.length) ? ellist.tBodies[0].children : ellist.children; i < ec.length; i++) { var o = createitem(ec[i]); o.search = o.label.toLowerCase().split(" "); items.push(o); @@ -138,6 +229,9 @@ function datalist_init(input) { }; datalist_match = function(s, fn) { + if (!datalist_enabled()) + return; + s = s.toLowerCase(); if (s === prevvalue) { fn(prevmatches); @@ -157,22 +251,55 @@ function datalist_init(input) { } var datalist_render = function(m) { - var dd = dropdown.cloneNode(false); + if (!indom) { // add to DOM on first use when needed. + document.body.appendChild(dropdown); + indom = true; + } + + var dd = dropdown.cloneNode(false), table, tbody; + if (tablemode) { + table = document.createElement("table"); + if (thead) + table.tHead = thead.cloneNode(true); + tbody = document.createElement("tbody"); + } + var r = input.getClientRects() || []; if (r.length) { dd.style.left = String(r[0].left + window.pageXOffset) + "px"; dd.style.top = String(r[0].top + input.offsetHeight + window.pageYOffset) + "px"; } - dd.style.minWidth = String(input.clientWidth) + "px"; - for (var i = 0; i < m.length; i++) - dd.appendChild(m[i].el); + dd.style.minWidth = String(input.getAttribute("data-minwidth") || input.clientWidth) + "px"; + if (tablemode) { + for (var i = 0; i < m.length; i++) + tbody.appendChild(m[i].el); + table.appendChild(tbody); + dd.appendChild(table); + } else { + for (var i = 0; i < m.length; i++) + dd.appendChild(m[i].el); + } dropdown.parentNode.replaceChild(dd, dropdown) dropdown = dd; }; + var datalist_visible = false; var datalist_show = function(status) { + if (status && !datalist_enabled()) // do not show when disabled, but it can be hidden. + return; datalist_visible = status; - dropdown.className = "datalist-dropdown " + (status ? "visible" : ""); + dropdown.className = "datalist-dropdown " + (status ? "visible" : "") + " " + (input.getAttribute("data-class") || ""); + var r = dropdown.getClientRects(); + // if dropdown popup doesn't fit then adjust x, y scroll position so it fits on the screen. + if (!r.length) + return; + if (r[0].left + r[0].width >= window.innerWidth) { + dropdown.style.left = "auto"; + dropdown.style.right = "0px"; + } + var ri = input.getClientRects() || []; + if (input.scrollIntoView && ri.length && r[0].top + dropdown.clientHeight + ri[0].height >= window.innerHeight) + input.scrollIntoView(); }; var datalist_setsel = function(el) { if (cursel) @@ -187,9 +314,7 @@ function datalist_init(input) { switch (e.which) { case 13: // return if (cursel) - setvalue(input, getvalue(cursel)); - if (!datalist_visible) - return; + setselvalue(input, getselvalue(cursel)); datalist_show(false); e.stopPropagation(); return !!e.preventDefault(); @@ -198,7 +323,7 @@ function datalist_init(input) { case 34: // page down. case 38: // arrow up case 40: // arrow down - var sel = cursel, dd = dropdown, dc = dropdown.children; + var sel = cursel, dd = dropdown, dc = tablemode ? dropdown.children[0].tBodies[0].children : dropdown.children; // if last and down arrow switch to first item, if first and up arrow switch to last item. if (dc.length) { @@ -237,6 +362,7 @@ function datalist_init(input) { }, false); var onchange = function() { + setvalue(input, input.value); datalist_match(input.value, function(m) { // check if selection is still active in matches. if (cursel) { @@ -253,6 +379,7 @@ function datalist_init(input) { datalist_show(!!m.length); }, "onchange"); }; + input.addEventListener("input", onchange); input.addEventListener("keyup", function(e) { mouse = true; @@ -281,7 +408,8 @@ function datalist_init(input) { datalist_setsel(null); datalist_show(false); }, false); - document.body.appendChild(dropdown); + + return ctx; } var els = document.getElementsByClassName("datalist"); diff --git a/datalist/example-data-cols-small.json b/datalist/example-data-cols-small.json @@ -0,0 +1,28 @@ +{ +"columns" : + [ + { + "name" : "name", + "label" : "Name", + "value" : true + }, { + "name" : "type", + "label" : "Type" + }, { + "name" : "proprietary", + "label" : "Proprietary?" + } + ], +"items" : [ + ["DragonflyBSD", "BSD", "No"], + ["GNU/Hurd", "BSD-like", "No"], + ["GNU/Linux", "Linux", "No"], + ["FreeBSD", "BSD", "No"], + ["MS-DOS 6.11", "DOS", "Yes"], + ["OpenBSD", "BSD", "No"], + ["OpenSolaris", "BSD-like", "No"], + ["NetBSD", "BSD", "No"], + ["Plan9", "Plan9", "No"], + ["Windows", "Windows", "Yes"] +] +} diff --git a/datalist/example-data-cols.json b/datalist/example-data-cols.json @@ -0,0 +1,79 @@ +{ +"columns" : + [ + { + "name" : "name", + "label" : "Name" + }, { + "name" : "type", + "label" : "Type" + }, { + "name" : "proprietary", + "label" : "Proprietary?", + "fn" : "item_bool" + } + ], +"items" : [ + { + "label" : "DragonflyBSD", + "value" : "DragonflyBSD", + "name" : "DragonflyBSD", + "type" : "BSD", + "proprietary" : "No" + }, { + "label" : "GNU/Hurd", + "value" : "GNU/Hurd", + "name" : "GNU/Hurd", + "type" : "BSD-like", + "proprietary" : "No" + }, { + "label" : "GNU/Linux", + "value" : "GNU/Linux", + "name" : "GNU/Linux", + "type" : "Linux", + "proprietary" : "No" + }, { + "label" : "FreeBSD", + "value" : "FreeBSD", + "name" : "FreeBSD", + "type" : "BSD", + "proprietary" : "No" + }, { + "label" : "MS-DOS 6.11", + "value" : "MS-DOS 6.11", + "name" : "MS-DOS 6.11", + "type" : "DOS", + "proprietary" : "Yes" + }, { + "label" : "OpenBSD", + "value" : "OpenBSD", + "name" : "OpenBSD", + "type" : "BSD", + "proprietary" : "No" + }, { + "label" : "OpenSolaris", + "value" : "OpenSolaris", + "name" : "OpenSolaris", + "type" : "BSD-like", + "proprietary" : "No" + }, { + "label" : "NetBSD", + "value" : "NetBSD", + "name" : "NetBSD", + "type" : "BSD", + "proprietary" : "No" + }, { + "label" : "Plan9", + "value" : "Plan9", + "name" : "Plan9", + "type" : "Plan9", + "proprietary" : "No" + }, { + "label" : "Windows", + "value" : "Windows", + "name" : "Windows", + "type" : "Windows", + "proprietary" : "Yes" + } +] +} diff --git a/datalist/example-json.html b/datalist/example-json.html @@ -0,0 +1,81 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<title>jsdatalist</title> +<link rel="stylesheet" type="text/css" href="datalist.css" /> +<style type="text/css"> +.yes { + background-color: #92d543; +} +.no { + background-color: #d54343; +} +</style> +</head> +<body> + +<form method="post" action=""> + +<p>Inline &lt;datalist&gt;:</p> + +<label for="os">OS: </label> +<input type="text" placeholder="Select OS..." value="" list="list" name="os" id="os" class="datalist" /><br/> + +<datalist class="datalist" id="list"> + <option>DragonflyBSD</option> + <option>GNU/Hurd</option> + <option>GNU/Linux</option> + <option>FreeBSD</option> + <option>MS-DOS&nbsp;6.11</option> + <option>OpenBSD</option> + <option>OpenSolaris</option> + <option>NetBSD</option> + <option>Plan9</option> + <option>Windows</option> +</datalist> + +<p>Using XMLHttpRequest + JSON:</p> + +<label for="remote">OS: </label> +<input type="text" placeholder="Select OS..." value="" data-url="example-data.json?q=" name="remote" id="remote" 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="remotecustom" class="datalist" /><br/> + +<label for="remotecustom2">OS: </label> +<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_urlfn" name="remotecustom" id="remotecustom2" class="datalist" /><br/> + +<label for="remotecustom3">OS (multi-table): </label> +<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_multi_urlfn" name="remotecustom3" id="remotecustom3" class="datalist" /><br/> + +<label for="remotecustom4">OS (multi-table, small dataset): </label> +<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_multi_small_urlfn" name="remotecustom4" id="remotecustom4" class="datalist" autofocus /><br/> + +</form> + +<script type="text/javascript"> +function custom_urlfn(s, input) { + return "example-data.json?q=" + encodeURIComponent(s); +} + +function custom_multi_urlfn(s, input) { + return "example-data-cols.json?q=" + encodeURIComponent(s); +} + +function custom_multi_small_urlfn(s, input) { + return "example-data-cols-small.json?q=" + encodeURIComponent(s); +} + +function datalist_format_item_bool(value, td) { + td.classList[value === "Yes" ? "add" : "remove"]("yes"); + td.classList[value !== "Yes" ? "add" : "remove"]("no"); + return value; +} +</script> +<script type="text/javascript" src="datalist.js"></script> + +</body> +</html>