datalist: add bloat - 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 ce985f99424e909b959b7cf2736312074e00d4cf
(DIR) parent f57d63c6ffac03ddf58f78a5fb3958c1caf9a7d2
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
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(-)
---
(DIR) 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
(DIR) 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;
+}
(DIR) 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");
(DIR) 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"]
+]
+}
(DIR) 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"
+ }
+]
+}
(DIR) 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 <datalist>:</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 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>