datalist.js - jscancer - Javascript crap (relatively small) | |
git clone git://git.codemadness.org/jscancer | |
Log | |
Files | |
Refs | |
README | |
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 eve… | |
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.hidd… | |
65 }; | |
66 | |
67 // create item: expects string, or object with name and label at… | |
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 styli… | |
125 input.classList.add("loading"); | |
126 | |
127 var x = new(XMLHttpRequest); | |
128 x.onreadystatechange = function() { | |
129 // remove loading indicator when… | |
130 if (x.readyState == 4) | |
131 input.classList.remove("… | |
132 | |
133 if (x.readyState != 4 || [ 0, 20… | |
134 return; | |
135 | |
136 prevmatches = []; | |
137 var o = JSON.parse(x.responseTex… | |
138 tablemode = !!o.columns && !!o.i… | |
139 if (tablemode) { | |
140 // create table header f… | |
141 thead = document.createE… | |
142 var tr = document.create… | |
143 var valueidx = 0; // def… | |
144 | |
145 for (var i = 0; i < o.co… | |
146 var th = documen… | |
147 th.className = o… | |
148 th.textContent =… | |
149 if (o.columns[i]… | |
150 valueidx… | |
151 tr.appendChild(t… | |
152 } | |
153 thead.appendChild(tr); | |
154 | |
155 // add items as table ro… | |
156 for (var i = 0; i < o.it… | |
157 var tr = documen… | |
158 if (Array.isArra… | |
159 tr.setAt… | |
160 for (var… | |
161 … | |
162 … | |
163 … | |
164 } | |
165 } else { | |
166 tr.setAt… | |
167 for (var… | |
168 … | |
169 … | |
170 … | |
171 … | |
172 … | |
173 … | |
174 … | |
175 … | |
176 … | |
177 … | |
178 … | |
179 … | |
180 } | |
181 } | |
182 prevmatches.push… | |
183 } | |
184 } else { | |
185 for (var i = 0; i < o.le… | |
186 prevmatches.push… | |
187 } | |
188 | |
189 prevurl = requrl; | |
190 fn(prevmatches); | |
191 }; | |
192 | |
193 x.open("GET", requrl + "&t=" + String(ne… | |
194 x.setRequestHeader("X-Requested-With", "… | |
195 x.timeout = 10000; | |
196 x.send(); | |
197 // delay in ms: throttle request on chan… | |
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… | |
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 < t… | |
216 var f = false; | |
217 for (var j = 0; j < data[i].sear… | |
218 for (var l = 0; l < data… | |
219 if (data[i].sear… | |
220 f = true; | |
221 if (f) | |
222 fc++; | |
223 } | |
224 // all tokens (separated by space) must … | |
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… | |
242 // else filter on existing data and no need to s… | |
243 if (prevvalue === null || (prevvalue.split(" ").… | |
244 s.indexOf(prevvalue) == -1) | |
245 prevmatches = datalist_filter(items, s); | |
246 else | |
247 prevmatches = datalist_filter(prevmatche… | |
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.pageXO… | |
270 dd.style.top = String(r[0].top + input.offsetHei… | |
271 } | |
272 dd.style.minWidth = String(input.getAttribute("data-minw… | |
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 d… | |
289 return; | |
290 datalist_visible = status; | |
291 dropdown.className = "datalist-dropdown " + (status ? "v… | |
292 var r = dropdown.getClientRects(); | |
293 // if dropdown popup doesn't fit then adjust x, y scroll… | |
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 + drop… | |
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 … | |
327 | |
328 // if last and down arrow switch to first item, … | |
329 if (dc.length) { | |
330 if (e.which == 38) { // up | |
331 if (!sel || !(sel = sel.previous… | |
332 sel = dc[dc.length - 1]; | |
333 } else if (e.which == 40) { // down | |
334 if (!sel || !(sel = sel.nextSibl… | |
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.clientHei… | |
342 if (e.which == 33) { // page up. | |
343 for (; n > 0 && sel && sel.previ… | |
344 n--, sel = sel.previousS… | |
345 ; | |
346 } else { // page down. | |
347 for (; n > 0 && sel && sel.nextS… | |
348 n--, sel = sel.nextSibli… | |
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.offsetHeigh… | |
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… | |
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]); |