datalist: add bloat - jscancer - Javascript crap (relatively small) | |
git clone git://git.codemadness.org/jscancer | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit ce985f99424e909b959b7cf2736312074e00d4cf | |
parent f57d63c6ffac03ddf58f78a5fb3958c1caf9a7d2 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Thu, 25 Apr 2024 20:04:43 +0200 | |
datalist: add bloat | |
Diffstat: | |
M datalist/README | 12 +++++++----- | |
M datalist/datalist.css | 23 +++++++++++++++++++++++ | |
M datalist/datalist.js | 190 ++++++++++++++++++++++++++---… | |
A datalist/example-data-cols-small.j… | 28 ++++++++++++++++++++++++++++ | |
A datalist/example-data-cols.json | 79 +++++++++++++++++++++++++++++… | |
A datalist/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.getElemen… | |
- | |
+ 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 attribut… | |
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[… | |
+ tablemode = !!o.columns && !!o.items; | |
+ if (tablemode) { | |
+ // create table header from co… | |
+ thead = document.createElement… | |
+ var tr = document.createElemen… | |
+ var valueidx = 0; // default i… | |
+ | |
+ for (var i = 0; i < o.columns.… | |
+ var th = document.crea… | |
+ th.className = o.colum… | |
+ th.textContent = o.col… | |
+ if (o.columns[i].value) | |
+ valueidx = i; | |
+ tr.appendChild(th); | |
+ } | |
+ thead.appendChild(tr); | |
+ | |
+ // add items as table rows: su… | |
+ for (var i = 0; i < o.items.le… | |
+ var tr = document.crea… | |
+ if (Array.isArray(o.it… | |
+ tr.setAttribut… | |
+ for (var j = 0… | |
+ var td… | |
+ td.tex… | |
+ tr.app… | |
+ } | |
+ } else { | |
+ tr.setAttribut… | |
+ for (var j = 0… | |
+ var co… | |
+ var td… | |
+ var va… | |
+ // cal… | |
+ var fn… | |
+ if (ty… | |
+ … | |
+ if (o.… | |
+ … | |
+ else | |
+ … | |
+ tr.app… | |
+ } | |
+ } | |
+ prevmatches.push(creat… | |
+ } | |
+ } else { | |
+ for (var i = 0; i < o.length; … | |
+ prevmatches.push(creat… | |
+ } | |
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) ? el… | |
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)… | |
dd.style.top = String(r[0].top + input.offsetHeight + … | |
} | |
- 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")… | |
+ 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 disable… | |
+ return; | |
datalist_visible = status; | |
- dropdown.className = "datalist-dropdown " + (status ? "visible… | |
+ dropdown.className = "datalist-dropdown " + (status ? "visible… | |
+ var r = dropdown.getClientRects(); | |
+ // if dropdown popup doesn't fit then adjust x, y scroll posit… | |
+ 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.c… | |
+ 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.childre… | |
+ var sel = cursel, dd = dropdown, dc = tablemode ? drop… | |
// if last and down arrow switch to first item, if fir… | |
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… | |
@@ -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… | |
+<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 <datalist>:</p> | |
+ | |
+<label for="os">OS: </label> | |
+<input type="text" placeholder="Select OS..." value="" list="list" name="os" i… | |
+ | |
+<datalist class="datalist" id="list"> | |
+ <option>DragonflyBSD</option> | |
+ <option>GNU/Hurd</option> | |
+ <option>GNU/Linux</option> | |
+ <option>FreeBSD</option> | |
+ <option>MS-DOS 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.… | |
+ | |
+<p>Using XMLHttpRequest + custom URL function + JSON:</p> | |
+ | |
+<label for="remotecustom">OS: </label> | |
+<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_urlf… | |
+ | |
+<label for="remotecustom2">OS: </label> | |
+<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_urlf… | |
+ | |
+<label for="remotecustom3">OS (multi-table): </label> | |
+<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_mult… | |
+ | |
+<label for="remotecustom4">OS (multi-table, small dataset): </label> | |
+<input type="text" placeholder="Select OS..." value="" data-urlfn="custom_mult… | |
+ | |
+</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> |