autocomplete/datalist: batch of improvements - jscancer - Javascript crap (relatively small)
HTML git clone git://git.codemadness.org/jscancer
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
DIR commit 2967ba6942ad55041437a01f8603cc43e5e91c0a
DIR parent 15738be7e39f50783051f26e06d2d7f193653261
HTML Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Sat, 28 May 2016 20:30:30 +0200
autocomplete/datalist: batch of improvements
rename from autocomplete to datalist
Diffstat:
D autocomplete/README | 47 -------------------------------
D autocomplete/TODO | 1 -
D autocomplete/complete.js | 161 -------------------------------
D autocomplete/example.html | 28 ----------------------------
D autocomplete/style.css | 26 --------------------------
A datalist/README | 47 +++++++++++++++++++++++++++++++
A datalist/TODO | 5 +++++
A datalist/datalist.css | 27 +++++++++++++++++++++++++++
A datalist/datalist.js | 164 +++++++++++++++++++++++++++++++
A datalist/example.html | 35 +++++++++++++++++++++++++++++++
10 files changed, 278 insertions(+), 263 deletions(-)
---
DIR diff --git a/autocomplete/README b/autocomplete/README
@@ -1,47 +0,0 @@
-autocomplete
-============
-
-small Javascript autocomplete script.
-
-
-FEATURES
---------
-
-- Small:
- - Filesize: +- 4.7KB, +- 2.3KB minified, +- 1KB GZIP'd.
- - Lines: +- 162, not much code, so hopefully easy to understand.
- - No dependencies on other libraries like jQuery.
-- (Graceful) fallback to HTML5 datalist if Javascript is disabled.
-- Filtering values: case-insensitively, tokenized (separated by space).
-- Permissive ISC license, see LICENSE file, feel free to contact me for
- questions or other terms.
-- No support for legacy browsers, officially supported are:
- - Firefox and Firefox ESR.
- - Chrome and most recent webkit-based browsers.
- - IE Edge.
-
-
-USAGE
------
-
-
-EXAMPLES
---------
-
-See example.html and style.css for an example. A stylesheet file style.css
-is also included.
-
-An input should have the classname "autocomplete" set with the attribute
-list="idoflist". The id should be of the datalist:
-
-<datalist name="nameoflist" id="idoflist">
- <option>Item 1</option>
- <option>Item 2</option>
- <option>Item 3</option>
-</datalist>
-
-
-Author
-------
-
-Hiltjo Posthuma <hiltjo@codemadness.org>
DIR diff --git a/autocomplete/TODO b/autocomplete/TODO
@@ -1 +0,0 @@
-- improve README and documentation.
DIR diff --git a/autocomplete/complete.js b/autocomplete/complete.js
@@ -1,161 +0,0 @@
-function autocomplete_init(input) {
- var attrlist = input.getAttribute("list"), ellist = document.getElementById(attrlist);
- if (attrlist === null || ellist === undefined)
- return;
- input.removeAttribute("list");
- input.autocomplete = "off";
- var dropdown = document.createElement("div"),
- items = [],
- mouse = true, // enable mouse event handling.
- cursel = null;
- dropdown.className = "autocomplete-dropdown";
- dropdown.style.left = String(input.offsetLeft) + "px";
-
- var autocomplete_match = function(s) {
- var matches = [], tok = s.toLowerCase().split(" ");
- for (var i = 0; i < items.length; i++) {
- var fc = 0;
- for (var k = 0; k < tok.length && fc < tok.length; k++) {
- var f = false;
- for (var j = 0; j < items[i].search.length && fc < tok.length && !f; j++)
- for (var l = 0; l < items[i].search.length && !f; l++)
- if (items[i].search[l].indexOf(tok[k]) != -1)
- f = true;
- if (f)
- fc++;
- }
- /* all tokens (separated by space) must match. */
- if (fc == tok.length)
- matches.push(items[i]);
- }
- return matches;
- };
- var autocomplete_render = function(m) {
- var p = dropdown.parentNode;
- var dd = dropdown.cloneNode(false);
- for (var i = 0; i < m.length; i++)
- dd.appendChild(m[i].el);
- p.replaceChild(dd, dropdown)
- dropdown = dd;
- };
- var autocomplete_show = function(status) {
- dropdown.className = "autocomplete-dropdown " + (status ? "visible" : "");
- };
- var autocomplete_setsel = function(el) {
- if (cursel)
- cursel.className = "";
- cursel = el;
- if (el)
- el.className = "sel";
- };
-
- for (var i = 0, ec = ellist.children; i < ec.length; i++) {
- var div = document.createElement("div");
- div.innerHTML = ec[i].innerHTML;
- div.onmousedown = function() {
- input.value = this.textContent;
- autocomplete_show(false);
- };
- div.onmousemove = function() {
- if (mouse)
- autocomplete_setsel(this);
- };
- items.push({ el: div, search: (div.textContent.toLowerCase() || "").split(" ") });
- }
- input.onkeydown = function(e) {
- mouse = false;
- switch (e.which) {
- case 13: // return
- if (cursel)
- this.value = cursel.textContent;
- autocomplete_show(false);
- case 27: break; // escape
- case 33: // page up.
- case 34: // page down.
- case 38: // arrow up
- case 40: // arrow down
- var sel = cursel, dd = dropdown, dc = dropdown.children;
-
- // if last and down arrow switch to first item, if first and up arrow switch to last item.
- if (dc.length) {
- if (e.which == 38) { // up
- if (!sel || !(sel = sel.previousSibling))
- sel = dc[dc.length - 1];
- } else if (e.which == 40) { // down
- if (!sel || !(sel = sel.nextSibling))
- sel = dc[0];
- } else if (!sel) {
- sel = dc[0];
- }
- }
- if (cursel && (e.which == 33 || e.which == 34)) {
- var n = sel.offsetHeight ? (dd.clientHeight / sel.offsetHeight) : 0;
- if (e.which == 33) { // page up.
- for (; n > 0 && sel && sel.previousSibling;
- n--, sel = sel.previousSibling)
- ;
- } else { // page down.
- for (; n > 0 && sel && sel.nextSibling;
- n--, sel = sel.nextSibling)
- ;
- }
- }
- if (sel) {
- autocomplete_setsel(sel);
-
- // only update scroll if needed.
- if (sel.offsetTop < dd.scrollTop)
- dd.scrollTop = sel.offsetTop;
- else if (sel.offsetTop + sel.offsetHeight > dd.scrollTop + dd.offsetHeight)
- dd.scrollTop = sel.offsetTop;
- }
- }
- };
- input.oninput = function() {
- var m = autocomplete_match(this.value);
- // check if selection is still active in matches.
- if (cursel) {
- var hassel = false;
- for (var i = 0; i < m.length && !(hassel = (m[i].el === cursel)); i++)
- ;
- if (!hassel)
- autocomplete_setsel(null);
- }
- // show list if it has matches.
- if (m.length) {
- // only one match? select it.
- if (m.length == 1)
- autocomplete_setsel(m[0].el);
- autocomplete_render(m);
- }
- autocomplete_show(!!m.length);
- };
- input.onkeyup = function(e) {
- mouse = true;
- // return key or escape was not pressed.
- if (e.which == 13 || e.which == 27)
- autocomplete_show(false);
- };
-
- var focuschange = function(e) {
- autocomplete_setsel(null);
- if (e.target === input) {
- var m = autocomplete_match(input.value);
- if (m.length)
- autocomplete_render(m);
- autocomplete_show(!!m.length);
- dropdown.scrollTop = 0; // reset scroll.
- } else {
- autocomplete_show(false);
- }
- };
- document.addEventListener("focus", focuschange, false);
- document.addEventListener("click", focuschange, false);
-
- input.parentNode.insertBefore(dropdown, input.nextSibling);
-}
-
-var els = document.getElementsByClassName("autocomplete");
-if (els !== null)
- for (var i = 0; i < els.length; i++)
- autocomplete_init(els[i]);
DIR diff --git a/autocomplete/example.html b/autocomplete/example.html
@@ -1,28 +0,0 @@
-<!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>jsautocomplete</title>
- <link rel="stylesheet" type="text/css" href="style.css" />
-</head>
-<body>
-
-<label for="os">OS: </label>
-<input type="text" value="" list="list" name="os" id="os" class="autocomplete" /><br/>
-
-<datalist name="list" 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>NetBSD</option>
- <option>Plan9</option>
- <option>Windows</option>
-</datalist>
-
-<script type="text/javascript" src="complete.js"></script>
-
-</body>
-</html>
DIR diff --git a/autocomplete/style.css b/autocomplete/style.css
@@ -1,26 +0,0 @@
-.autocomplete-dropdown {
- max-height: 300px;
- position: absolute;
- overflow: auto;
- z-index: 999;
- width: 600px;
- padding: 0;
- background-color: #fff;
- border: 1px solid #33bbff;
- display: none;
-}
-.autocomplete-dropdown.visible {
- display: block;
-}
-.autocomplete-dropdown div {
- padding: 0px 0px 0px 10px;
- cursor: default;
-}
-.autocomplete-dropdown div.sel {
- background-color: #33bbff;
- color: #fff;
-}
-/* hide datalist for browsers that don't support it */
-datalist {
- display: none;
-}
DIR diff --git a/datalist/README b/datalist/README
@@ -0,0 +1,47 @@
+autocomplete
+============
+
+small dropdown / autocomplete script.
+
+
+FEATURES
+--------
+
+- Small:
+ - Filesize: +- 4.6KB.
+ - Lines: +- 164, not much code, so hopefully easy to understand.
+ - No dependencies on other libraries like jQuery.
+- (Graceful) fallback to HTML5 datalist if Javascript is disabled.
+- Filtering values: case-insensitively, tokenized (separated by space).
+- Permissive ISC license, see LICENSE file, feel free to contact me for
+ questions or other terms.
+- Officially supported browsers are:
+ - Firefox and Firefox ESR.
+ - Chrome and most recent webkit-based browsers.
+ - IE8+ (use compat.js).
+
+
+USAGE
+-----
+
+
+EXAMPLES
+--------
+
+See example.html and style.css for an example. A stylesheet file style.css
+is also included.
+
+An input should have the classname "autocomplete" set with the attribute
+list="idoflist". The id should be of the datalist:
+
+<datalist name="nameoflist" id="idoflist">
+ <option>Item 1</option>
+ <option>Item 2</option>
+ <option>Item 3</option>
+</datalist>
+
+
+Author
+------
+
+Hiltjo Posthuma <hiltjo@codemadness.org>
DIR diff --git a/datalist/TODO b/datalist/TODO
@@ -0,0 +1,5 @@
+- test oninput clear?
+- update examples.
+- when autocomplete is closed RETURN should allow the normal event (for example for form submits).
+ prevent default otherwise.
+- improve README and documentation.
DIR diff --git a/datalist/datalist.css b/datalist/datalist.css
@@ -0,0 +1,27 @@
+.datalist-dropdown {
+ max-height: 300px;
+ position: absolute;
+ overflow: auto;
+ z-index: 999;
+ width: 600px;
+ padding: 0;
+ background-color: #fff;
+ border: 1px solid #33bbff;
+ display: none;
+}
+.datalist-dropdown.visible {
+ display: block;
+}
+.datalist-dropdown div {
+ padding: 0px 0px 0px 10px;
+ cursor: default;
+}
+.datalist-dropdown div.sel {
+ background-color: #33bbff;
+ color: #fff;
+}
+/* hide datalist for browsers that don't support it */
+select.datalist,
+datalist {
+ display: none;
+}
DIR diff --git a/datalist/datalist.js b/datalist/datalist.js
@@ -0,0 +1,164 @@
+function datalist_init(input) {
+ var attrlist = input.getAttribute("list"), ellist = document.getElementById(attrlist);
+ if (attrlist === null || ellist === undefined)
+ return;
+ input.removeAttribute("list");
+ input.autocomplete = "off";
+ var cursel = null, items = [], mouse = true, // enable mouse event handling.
+ dropdown = document.createElement("div");
+ dropdown.className = "datalist-dropdown";
+ dropdown.style.left = String(input.offsetLeft) + "px";
+
+ for (var i = 0, ec = ellist.children; i < ec.length; i++) {
+ var div = document.createElement("div");
+ div.innerHTML = ec[i].innerHTML;
+ div.addEventListener("mousedown", function() {
+ input.value = this.textContent || this.innerText;
+ datalist_show(false);
+ }, false);
+ div.addEventListener("mousemove", function() {
+ if (mouse)
+ datalist_setsel(this);
+ }, false);
+ items.push({ el: div, search: ((div.textContent || div.innerText).toLowerCase() || "").split(" ") });
+ }
+
+ var datalist_match = function(s) {
+ var matches = [], tok = s.toLowerCase().split(" ");
+ for (var i = 0; i < items.length; i++) {
+ var fc = 0;
+ for (var k = 0; k < tok.length && fc < tok.length; k++) {
+ var f = false;
+ for (var j = 0; j < items[i].search.length && fc < tok.length && !f; j++)
+ for (var l = 0; l < items[i].search.length && !f; l++)
+ if (items[i].search[l].indexOf(tok[k]) != -1)
+ f = true;
+ if (f)
+ fc++;
+ }
+ /* all tokens (separated by space) must match. */
+ if (fc == tok.length)
+ matches.push(items[i]);
+ }
+ return matches;
+ },
+ datalist_render = function(m) {
+ var p = dropdown.parentNode;
+ var dd = dropdown.cloneNode(false);
+ for (var i = 0; i < m.length; i++)
+ dd.appendChild(m[i].el);
+ p.replaceChild(dd, dropdown)
+ dropdown = dd;
+ },
+ datalist_show = function(status) {
+ dropdown.className = "datalist-dropdown " + (status ? "visible" : "");
+ },
+ datalist_setsel = function(el) {
+ if (cursel)
+ cursel.className = "";
+ cursel = el;
+ if (el)
+ el.className = "sel";
+ };
+ input.addEventListener("keydown", function(e) {
+ mouse = false;
+ switch (e.which) {
+ case 13: // return
+ if (cursel)
+ input.value = cursel.textContent || cursel.innerText;
+ datalist_show(false);
+ case 27: break; // escape
+ case 33: // page up.
+ case 34: // page down.
+ case 38: // arrow up
+ case 40: // arrow down
+ var sel = cursel, dd = dropdown, dc = dropdown.children;
+
+ // if last and down arrow switch to first item, if first and up arrow switch to last item.
+ if (dc.length) {
+ if (e.which == 38) { // up
+ if (!sel || !(sel = sel.previousSibling))
+ sel = dc[dc.length - 1];
+ } else if (e.which == 40) { // down
+ if (!sel || !(sel = sel.nextSibling))
+ sel = dc[0];
+ } else if (!sel) {
+ sel = dc[0];
+ }
+ }
+ if (cursel && (e.which == 33 || e.which == 34)) {
+ var n = sel.offsetHeight ? (dd.clientHeight / sel.offsetHeight) : 0;
+ if (e.which == 33) { // page up.
+ for (; n > 0 && sel && sel.previousSibling;
+ n--, sel = sel.previousSibling)
+ ;
+ } else { // page down.
+ for (; n > 0 && sel && sel.nextSibling;
+ n--, sel = sel.nextSibling)
+ ;
+ }
+ }
+ if (sel) {
+ datalist_setsel(sel);
+
+ // only update scroll if needed.
+ if (sel.offsetTop < dd.scrollTop)
+ dd.scrollTop = sel.offsetTop;
+ else if (sel.offsetTop + sel.offsetHeight > dd.scrollTop + dd.offsetHeight)
+ dd.scrollTop = sel.offsetTop;
+ }
+ }
+ }, false);
+ input.addEventListener("keyup", function(e) {
+ mouse = true;
+ switch (e.which) {
+ case 13: // return
+ case 27: // escape
+ datalist_show(false);
+ case 33: // page up.
+ case 34: // page down.
+ case 38: // arrow up
+ case 40: // arrow down
+ return;
+ }
+ var m = datalist_match(input.value);
+ // check if selection is still active in matches.
+ if (cursel) {
+ var hassel = false;
+ for (var i = 0; i < m.length && !(hassel = (m[i].el === cursel)); i++)
+ ;
+ if (!hassel)
+ datalist_setsel(null);
+ }
+ // show list if it has matches.
+ if (m.length) {
+ // only one match? select it.
+ if (m.length == 1)
+ datalist_setsel(m[0].el);
+ datalist_render(m);
+ }
+ datalist_show(!!m.length);
+ }, false);
+
+ var focuschange = function(e) {
+ datalist_setsel(null);
+ if (e.target === input) {
+ var m = datalist_match(input.value);
+ if (m.length)
+ datalist_render(m);
+ datalist_show(!!m.length);
+ dropdown.scrollTop = 0; // reset scroll.
+ } else {
+ datalist_show(false);
+ }
+ };
+ document.addEventListener("focus", focuschange, false);
+ document.addEventListener("click", focuschange, false);
+
+ input.parentNode.insertBefore(dropdown, input.nextSibling);
+}
+
+var els = document.getElementsByClassName("datalist");
+if (els !== null)
+ for (var i = 0; i < els.length; i++)
+ datalist_init(els[i]);
DIR diff --git a/datalist/example.html b/datalist/example.html
@@ -0,0 +1,35 @@
+<!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" />
+</head>
+<body>
+
+<form method="post" action="">
+
+<label for="os">OS: </label>
+<input type="text" value="" list="list" name="os" id="os" class="datalist" /><br/>
+
+<!--[if lte IE 9]><select class="datalist" id="list"><![endif]-->
+<!--[if !IE]>--><datalist class="datalist" id="list"><!--<![endif]-->
+ <option>DragonflyBSD</option>
+ <option>GNU/Hurd</option>
+ <option>GNU/Linux</option>
+ <option>FreeBSD</option>
+ <option>MS-DOS 6.11</option>
+ <option>OpenBSD</option>
+ <option>NetBSD</option>
+ <option>Plan9</option>
+ <option>Windows</option>
+<!--[if !IE]>--></datalist><!--<![endif]-->
+<!--[if lte IE 9]></select><![endif]-->
+
+</form>
+
+<!--[if lte IE 8]><script type="text/javascript" src="../compat.js"></script><![endif]-->
+<script type="text/javascript" src="datalist.js"></script>
+
+</body>
+</html>