script.js - jscancer - Javascript crap (relatively small) | |
git clone git://git.codemadness.org/jscancer | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
script.js (17957B) | |
--- | |
1 // run atleast once, then every n milliseconds. | |
2 // if n is 0 just run once. | |
3 function updateevery(n, fn) { | |
4 // schedule in parallel now. | |
5 setTimeout(function() { | |
6 fn(); | |
7 }, 0); | |
8 if (!n) | |
9 return null; | |
10 return setInterval(fn, n); | |
11 } | |
12 | |
13 function pad0(n) { | |
14 return (parseInt(n) < 10 ? "0" : "") + n.toString(); | |
15 } | |
16 | |
17 function padspace(n) { | |
18 return (parseInt(n) < 10 ? " " : "") + n.toString(); | |
19 } | |
20 | |
21 // dutch weekdays and months. | |
22 //var strftime_weekdays = [ "Zondag", "Maandag", "Dinsdag", "Woensdag", … | |
23 //var strftime_months = [ "Januari", "Februari", "Maart", "April", "Mei… | |
24 | |
25 // subset of strftime() | |
26 function strftime(fmt, d) { | |
27 //var locale = locale || navigator.language || "en"; | |
28 //var locale = "nl-NL"; // dutch | |
29 var locale = "en-US"; // english US | |
30 d = d || new Date(); | |
31 | |
32 var mon = d.getMonth(); // starts at 0 | |
33 var day = d.getDay(); // weekday, starts at 0 (sunday). | |
34 | |
35 var s = fmt; | |
36 s = s.replace(/%Y/g, d.getFullYear().toString()); | |
37 s = s.replace(/%m/g, pad0(mon + 1)); | |
38 s = s.replace(/%d/g, pad0(d.getDate())); | |
39 s = s.replace(/%e/g, padspace(d.getDate())); | |
40 s = s.replace(/%H/g, pad0(d.getHours())); | |
41 s = s.replace(/%M/g, pad0(d.getMinutes())); | |
42 s = s.replace(/%S/g, pad0(d.getSeconds())); | |
43 | |
44 s = s.replace(/%A/g, (new Intl.DateTimeFormat(locale, { weekday:… | |
45 s = s.replace(/%a/g, (new Intl.DateTimeFormat(locale, { weekday:… | |
46 s = s.replace(/%B/g, (new Intl.DateTimeFormat(locale, { month: "… | |
47 s = s.replace(/%b/g, (new Intl.DateTimeFormat(locale, { month: "… | |
48 | |
49 //s = s.replace(/%A/g, strftime_weekdays[day]); // full weekday … | |
50 //s = s.replace(/%a/g, strftime_weekdays[day].substring(0, 3)); … | |
51 //s = s.replace(/%B/g, strftime_months[mon]); // full month name | |
52 //s = s.replace(/%b/g, strftime_months[mon].substring(0, 3)); //… | |
53 | |
54 return s; | |
55 } | |
56 | |
57 // XMLHttpRequest helper function. | |
58 function xhr(url, fn, timeout) { | |
59 var x = new(XMLHttpRequest); | |
60 x.open("get", url, true); // async | |
61 x.setRequestHeader("X-Requested-With", "XMLHttpRequest"); | |
62 x.timeout = parseInt(timeout || 10000); // default: 10 seconds | |
63 x.onreadystatechange = function () { | |
64 if (x.readyState != 4) | |
65 return; | |
66 fn(x); | |
67 }; | |
68 var data = ""; | |
69 x.send(data); | |
70 } | |
71 | |
72 // find direct descendent child element with a certain classname. | |
73 function getdirectchilds(parent, classname) { | |
74 var els = []; | |
75 | |
76 for (var i = 0; i < parent.children.length; i++) { | |
77 var el = parent.children[i]; | |
78 if (el.classList.contains(classname)) | |
79 els.push(el); | |
80 } | |
81 | |
82 return els; | |
83 } | |
84 | |
85 // news tickers. | |
86 var tickerels = document.getElementsByClassName("news-ticker"); | |
87 var tickers = []; | |
88 for (var i = 0; i < tickerels.length; i++) { | |
89 var url = tickerels[i].getAttribute("data-url") || ""; | |
90 if (url == "") | |
91 continue; | |
92 | |
93 // bind element to context | |
94 var t = (function(tickerel, delay) { | |
95 var displayfn = function(ticker) { | |
96 var parent = ticker.el; | |
97 var items = ticker.items || []; | |
98 var counter = ticker.counter || 0; | |
99 | |
100 var node = document.createElement("div"); | |
101 node.innerText = items[counter] || ""; | |
102 parent.appendChild(node); // add new. | |
103 counter = (counter + 1) % items.length; | |
104 | |
105 ticker.counter = counter; // update counter | |
106 | |
107 if (parent.children.length) { | |
108 // schedule to be removed. | |
109 setTimeout(function() { | |
110 if (parent.children.length) | |
111 parent.children[0].class… | |
112 setTimeout(function() { | |
113 if (parent.children.leng… | |
114 parent.removeChi… | |
115 }, ticker.fadein); | |
116 }, ticker.display); | |
117 } | |
118 }; | |
119 | |
120 var processfn = function(x) { | |
121 var feed = JSON.parse(x.responseText || ""); | |
122 var items = []; | |
123 var feeditems = feed.items || []; | |
124 | |
125 for (var j = 0; j < feeditems.length; j++) { | |
126 var title = feeditems[j].title; | |
127 var ts = feeditems[j].date_published || … | |
128 // if there is a timestamp prefix it in … | |
129 if (ts.length) { | |
130 var d = new Date(Date.parse(ts)); | |
131 title = strftime("%H:%M", d) + "… | |
132 } | |
133 items.push(title); | |
134 } | |
135 ticker.items = items; | |
136 }; | |
137 | |
138 // ticker config / context. | |
139 var ticker = { | |
140 el: tickerel, | |
141 url: (tickerel.getAttribute("data-url") || ""), | |
142 display: parseInt(tickerel.getAttribute("data-di… | |
143 fadein: parseInt(tickerel.getAttribute("data-fad… | |
144 update: parseInt(tickerel.getAttribute("data-upd… | |
145 }; | |
146 | |
147 ticker.processfn = processfn; | |
148 ticker.displayfn = displayfn; | |
149 | |
150 // reload RSS/Atom data. | |
151 var updater = function() { | |
152 xhr(ticker.url, function(x) { | |
153 ticker.processfn(x); | |
154 | |
155 // reset and stop previous timer for cyc… | |
156 if (ticker.displaytimer) | |
157 clearInterval(ticker.displaytime… | |
158 | |
159 ticker.counter = 0; // reset item counter | |
160 ticker.el.innerHTML = ""; // clear conte… | |
161 ticker.displayfn(ticker); // immediately… | |
162 | |
163 // display / cycle existing items. | |
164 ticker.displaytimer = setInterval(functi… | |
165 }); | |
166 }; | |
167 | |
168 ticker.updater = updater; | |
169 ticker.updatetimer = setInterval(updater, ticker.update)… | |
170 setTimeout(updater, delay); // small delay so the newsti… | |
171 | |
172 return ticker; | |
173 })(tickerels[i], 1000 * i); | |
174 | |
175 tickers.push(t); | |
176 } | |
177 | |
178 // elements showing simple HTML content in a placeholder, refresh using … | |
179 var xhrcontentels = document.getElementsByClassName("xhr-content"); | |
180 for (var i = 0; i < xhrcontentels.length; i++) { | |
181 var xhrcontentel = xhrcontentels[i]; | |
182 var url = xhrcontentel.getAttribute("data-url") || ""; | |
183 if (url == "") | |
184 continue; | |
185 | |
186 // bind element to context. | |
187 (function(bindel) { | |
188 var updatefn = function(config) { | |
189 var parent = config.el; | |
190 var curcontent = parent.childNodes.length ? pare… | |
191 var newcontent = config.data || ""; | |
192 // content changed. | |
193 if (curcontent === newcontent) | |
194 return; | |
195 | |
196 // remove previous nodes. | |
197 while (parent.childNodes.length > 0) | |
198 parent.removeChild(parent.childNodes[0]); | |
199 | |
200 // add nodes to force a transition. | |
201 var node = document.createElement("div"); | |
202 node.innerHTML = newcontent; | |
203 if (config.animate !== "") { | |
204 node.classList.add("animate-once"); | |
205 node.classList.add(config.animateclass); | |
206 } | |
207 parent.appendChild(node); // add new. | |
208 }; | |
209 | |
210 var processfn = function(x) { | |
211 var data = ""; | |
212 if (x.status >= 200 && x.status < 400) | |
213 data = x.responseText || ""; | |
214 else | |
215 data = ""; // "ERROR"; | |
216 config.data = data; | |
217 }; | |
218 | |
219 // config / context. | |
220 var config = { | |
221 animateclass: bindel.getAttribute("data-animate-… | |
222 el: bindel, | |
223 processfn: processfn, | |
224 timeout: parseInt(bindel.getAttribute("data-time… | |
225 update: parseInt(bindel.getAttribute("data-updat… | |
226 updatefn: updatefn, | |
227 url: (bindel.getAttribute("data-url") || "") | |
228 }; | |
229 | |
230 // reload data. | |
231 var updater = function() { | |
232 xhr(config.url, function(x) { | |
233 // process data. | |
234 config.processfn(x); | |
235 // update and display data. | |
236 config.updatefn(config); | |
237 }, config.timeout); | |
238 }; | |
239 | |
240 setInterval(updater, config.update); | |
241 // run almost immediately once the first time. | |
242 setTimeout(updater, 1); | |
243 })(xhrcontentel); | |
244 } | |
245 | |
246 // object or iframe elements that simply show some HTML and refresh in a… | |
247 var embedcontentels = document.getElementsByClassName("embed-content"); | |
248 for (var i = 0; i < embedcontentels.length; i++) { | |
249 var embedcontentel = embedcontentels[i]; | |
250 | |
251 // must have one child node of type "object" or "iframe". | |
252 if (embedcontentel.children.length <= 0) | |
253 continue; | |
254 | |
255 var child = embedcontentel.children[0]; | |
256 | |
257 // bind element to context. | |
258 (function(bindel, child) { | |
259 var url = ""; | |
260 var type = ""; | |
261 var tagname = child.tagName; | |
262 if (tagname === "OBJECT") { | |
263 url = child.data || ""; | |
264 type = child.getAttribute("data-type") || "text/… | |
265 } else if (tagname === "IFRAME") { | |
266 url = child.getAttribute("src") || ""; | |
267 } | |
268 | |
269 // config / context. | |
270 var config = { | |
271 el: bindel, | |
272 update: parseInt(bindel.getAttribute("data-updat… | |
273 url: url, | |
274 type: type | |
275 }; | |
276 | |
277 var updater; | |
278 if (tagname === "OBJECT") { | |
279 // reload data. | |
280 updater = function() { | |
281 var objects = Array.from(bindel.childNod… | |
282 | |
283 var object = document.createElement("obj… | |
284 object.data = config.url; // reload | |
285 object.type = config.type; | |
286 object.style.visibility = "hidden"; | |
287 object.onload = function() { | |
288 // quickly swap out the old obje… | |
289 // this prevents some flickering. | |
290 for (var j = 0; j < objects.leng… | |
291 bindel.removeChild(objec… | |
292 this.style.visibility = "visible… | |
293 }; | |
294 bindel.appendChild(object); | |
295 }; | |
296 } else if (tagname === "IFRAME") { | |
297 // reload data. | |
298 updater = function() { | |
299 child.contentWindow.location.reload(); | |
300 }; | |
301 } | |
302 | |
303 setInterval(updater, config.update); | |
304 // run almost immediately once the first time. | |
305 setTimeout(updater, 1); | |
306 })(embedcontentel, child); | |
307 } | |
308 | |
309 // pause videos inside element. | |
310 function pausevideos(el) { | |
311 var videos = el.getElementsByTagName("video"); | |
312 for (var i = 0; i < videos.length; i++) { | |
313 videos[i].pause(); | |
314 if ((videos[i].getAttribute("data-reset-on-pause") || ""… | |
315 videos[i].currentTime = 0; // reset time / seek … | |
316 } | |
317 } | |
318 | |
319 // play / resume videos inside element. | |
320 function playvideos(el) { | |
321 var videos = el.getElementsByTagName("video"); | |
322 for (var i = 0; i < videos.length; i++) { | |
323 videos[i].muted = true; // mute audio by default. | |
324 if ((videos[i].getAttribute("data-reset-on-play") || "")… | |
325 videos[i].currentTime = 0; // reset time / seek … | |
326 videos[i].play(); // NOTE: auto-play must be enabled by … | |
327 } | |
328 } | |
329 | |
330 // date / clocks. | |
331 var dates = document.getElementsByClassName("datetime"); | |
332 for (var i = 0; i < dates.length; i++) { | |
333 var format = dates[i].getAttribute("data-format") || ""; | |
334 var freq = dates[i].getAttribute("data-update") || 1000; // defa… | |
335 | |
336 var fn = (function(el, freq, format) { | |
337 return function() { | |
338 el.innerHTML = strftime(format); // allow HTML i… | |
339 }; | |
340 })(dates[i], freq, format); | |
341 updateevery(freq, fn); | |
342 } | |
343 | |
344 // init slides and timings. | |
345 function slides_init(rootel) { | |
346 rootel = rootel || document; | |
347 var slides = { | |
348 current: 0, | |
349 prev: -1, | |
350 prevprev: -1, | |
351 slides: [] | |
352 }; | |
353 | |
354 // find direct descendent element with class "slide". | |
355 var slideels = getdirectchilds(rootel, "slide"); | |
356 for (var i = 0; i < slideels.length; i++) { | |
357 var attrtiming = slideels[i].getAttribute("data-displayn… | |
358 var timing = parseInt(attrtiming); | |
359 if (timing <= 0) | |
360 timing = 5000; | |
361 | |
362 var slide = { | |
363 timing: timing, | |
364 el: slideels[i] | |
365 }; | |
366 slides.slides.push(slide); | |
367 } | |
368 return slides; | |
369 } | |
370 | |
371 function slides_showcurrent(slides) { | |
372 var el = slides.slides[slides.current].el; | |
373 if (el === null) | |
374 return; | |
375 el.classList.add("visible"); | |
376 playvideos(el); | |
377 } | |
378 | |
379 function slides_change(slides) { | |
380 if (typeof(slides.prev) !== "undefined") | |
381 slides.slides[slides.prev].elapsed = 0; | |
382 slides.slides[slides.current].elapsed = 0; | |
383 | |
384 slides_showcurrent(slides); | |
385 if (slides.onchange) | |
386 slides.onchange(slides.prev, slides.prevprev); // curren… | |
387 | |
388 // completely hidden. | |
389 if (slides.prevprev !== -1) { | |
390 var el = slides.slides[slides.prevprev].el; | |
391 el.classList.remove("pause"); | |
392 } | |
393 | |
394 // pause / fade out. | |
395 if (slides.prev !== -1) { | |
396 var el = slides.slides[slides.prev].el; | |
397 el.classList.remove("visible"); | |
398 el.classList.add("pause"); | |
399 pausevideos(el); | |
400 } | |
401 } | |
402 | |
403 function slides_go(slides, n) { | |
404 // out-of-bounds or the same: do nothing. | |
405 if (n < 0 || n >= slides.slides.length || n == slides.current) | |
406 return; | |
407 slides.prevprev = slides.prev; | |
408 slides.prev = slides.current; | |
409 slides.current = n; | |
410 slides_change(slides); | |
411 } | |
412 | |
413 function slides_prev(slides) { | |
414 var n = slides.current - 1; | |
415 if (n < 0 && slides.slides.length) | |
416 n = slides.slides.length - 1; | |
417 slides_go(slides, n); | |
418 slides_change(slides); | |
419 } | |
420 | |
421 function slides_next(slides) { | |
422 var n = slides.current + 1; | |
423 if (n >= slides.slides.length) | |
424 n = 0; | |
425 slides_go(slides, n); | |
426 slides_change(slides); | |
427 } | |
428 | |
429 function slides_pause(slides) { | |
430 slides.paused = true; | |
431 clearTimeout(slides.timernextslide); | |
432 clearTimeout(slides.timercurrentslide); | |
433 pausevideos(slides.slides[slides.current].el); | |
434 } | |
435 | |
436 function slides_play(slides) { | |
437 slides_continue(slides); | |
438 } | |
439 | |
440 function slides_continue(slides) { | |
441 slides.paused = false; | |
442 slides_updater(slides); | |
443 slides_showcurrent(slides); | |
444 } | |
445 | |
446 function slides_updater(slides) { | |
447 // need more than 1 slide to change it. | |
448 if (slides.slides.length <= 1) | |
449 return; | |
450 var fn = function() { | |
451 slides_next(slides); | |
452 var currentslide = slides.slides[slides.current]; | |
453 var timing = currentslide.timing || 5000; // default: 5 … | |
454 timing -= (currentslide.elapsed || 0); // minus played t… | |
455 slides.timernextslide = setTimeout(fn, timing); | |
456 }; | |
457 var currentslide = slides.slides[slides.current]; | |
458 var timing = currentslide.timing || 5000; // default: 5 seconds | |
459 timing -= (currentslide.elapsed || 0); // minus played time for … | |
460 slides.timercurrentslide = setTimeout(fn, timing); | |
461 } | |
462 | |
463 function progressbar_init(slides, progressbar) { | |
464 if (slides.slides.length <= 1) | |
465 return; | |
466 | |
467 // config: show total progress or progress per slide. | |
468 var usetotalprogress = parseInt(progressbar.getAttribute("data-t… | |
469 | |
470 function progressbar_update(slides) { | |
471 var currentslide = slides.slides[slides.current]; | |
472 var total = currentslide.timing || 5000; // default: 5 s… | |
473 | |
474 if (usetotalprogress) { | |
475 total = 0; | |
476 for (var i = 0; i < slides.slides.length; i++) { | |
477 total += parseInt(slides.slides[i].timin… | |
478 } | |
479 } | |
480 | |
481 currentslide.elapsed = currentslide.elapsed || 0; | |
482 | |
483 var perfnow = window.performance.now() | |
484 slides.prevprogress = slides.prevprogress || 0; | |
485 | |
486 if (!slides.paused) { | |
487 currentslide.elapsed += perfnow - slides.prevpro… | |
488 } | |
489 slides.prevprogress = perfnow; | |
490 | |
491 var elapsed = currentslide.elapsed; | |
492 if (usetotalprogress) { | |
493 // current slide progress + all finished previou… | |
494 for (var i = 0; i < slides.current; i++) | |
495 elapsed += parseInt(slides.slides[i].tim… | |
496 } | |
497 | |
498 // generate linear gradient with ticks for the total pro… | |
499 if (usetotalprogress) { | |
500 var total = 0; | |
501 for (var i = 0; i < slides.slides.length; i++) { | |
502 total += parseInt(slides.slides[i].timin… | |
503 } | |
504 | |
505 var pattern = []; | |
506 var timing = 0; | |
507 for (var i = 0; i < slides.slides.length; i++) { | |
508 timing += parseInt(slides.slides[i].timi… | |
509 var percent = (timing / total) * 100.0; | |
510 | |
511 pattern.push("rgba(255, 255, 255, 0.0) "… | |
512 | |
513 // don't show tick for last. | |
514 if (i !== slides.slides.length - 1) { | |
515 // tick color | |
516 pattern.push("rgba(255,255,255, … | |
517 pattern.push("rgba(255,255,255, … | |
518 } | |
519 } | |
520 | |
521 // ticks gradient; | |
522 var gradient = "linear-gradient(90deg, " + patte… | |
523 // progress gradient. | |
524 var percent = (elapsed / total) * 100.0; | |
525 gradient += ", linear-gradient(90deg, rgba(255, … | |
526 "%, rgba(255, 255, 255, 0) " + (percent … | |
527 "%, rgba(255, 255, 255, 0) 100%)" // act… | |
528 progressbar.style.background = gradient; | |
529 progressbar.style.width = "100%"; | |
530 } else { | |
531 var percent = (elapsed / total) * 100.0; | |
532 progressbar.style.width = percent + "%"; | |
533 } | |
534 } | |
535 | |
536 function progressbar_updater() { | |
537 var fn = function() { | |
538 progressbar_update(slides); | |
539 }; | |
540 setInterval(fn, 16); // 16ms, update at ~60fps | |
541 } | |
542 | |
543 progressbar_updater(slides); | |
544 } | |
545 | |
546 // initialize slides, can be nested. | |
547 var rootels = document.getElementsByClassName("slides") || []; | |
548 for (var i = 0; i < rootels.length; i++) { | |
549 var rootel = rootels[i]; | |
550 | |
551 var slides = slides_init(rootel); | |
552 slides_play(slides); | |
553 | |
554 /* progressbar: shows current slide progress */ | |
555 var progressbars = getdirectchilds(rootel, "progressbar"); | |
556 if (progressbars.length) // use first progressbar. | |
557 progressbar_init(slides, progressbars[0]); | |
558 } | |
559 | |
560 /* mechanism to poll if screen data is updated and needs a full refresh | |
561 ignoring the cache. */ | |
562 var lasthash = null; | |
563 var els = document.getElementsByClassName("check-updates"); | |
564 for (var i = 0; i < els.length; i++) { | |
565 var el = els[i]; | |
566 var interval = parseInt(el.getAttribute("data-update")); | |
567 if (interval <= 0) | |
568 continue; | |
569 var url = el.getAttribute("data-url") || ""; | |
570 if (url === "") | |
571 continue; | |
572 | |
573 var fn = function(x) { | |
574 if (x.status >= 200 && x.status < 400) { | |
575 var newhash = x.responseText || ""; | |
576 if (lasthash === null) { | |
577 lasthash = newhash; | |
578 } else if (newhash !== lasthash) { | |
579 // reload, ignore cache: works in Chrome… | |
580 window.location.reload(true); | |
581 } | |
582 } | |
583 }; | |
584 | |
585 setInterval(function() { | |
586 xhr(url, fn, 10000); | |
587 }, interval); | |
588 xhr(url, fn, 10000); | |
589 | |
590 break; | |
591 } | |
592 | |
593 window.addEventListener("keyup", function(e) { | |
594 switch (e.keyCode) { | |
595 case 32: // space: toggle pause/play. | |
596 if (slides.paused) | |
597 slides_continue(slides); | |
598 else | |
599 slides_pause(slides); | |
600 break; | |
601 case 37: // left arrow: previous slide. | |
602 slides_prev(slides); | |
603 break; | |
604 case 13: // return or right arrow next slide. | |
605 case 39: | |
606 slides_next(slides); | |
607 break; | |
608 case 49: // '1': go to slide 1 | |
609 case 50: // '2': go to slide 2 | |
610 case 51: // '3': go to slide 3 | |
611 case 52: // '4': go to slide 4 | |
612 case 53: // '5': go to slide 5 | |
613 case 54: // '6': go to slide 6 | |
614 case 55: // '7': go to slide 7 | |
615 case 56: // '8': go to slide 8 | |
616 case 57: // '9': go to slide 9 | |
617 slides_go(slides, e.keyCode - 49); | |
618 break; | |
619 } | |
620 }, false); |