script.js - jscancer - Javascript crap (relatively small)
(HTM) git clone git://git.codemadness.org/jscancer
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
script.js (17968B)
---
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", "Donderdag", "Vrijdag", "Zaterdag" ];
23 //var strftime_months = [ "Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December" ];
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: "long" })).format(d)); // full weekday name
45 s = s.replace(/%a/g, (new Intl.DateTimeFormat(locale, { weekday: "short" })).format(d)); // abbreviated weekday name
46 s = s.replace(/%B/g, (new Intl.DateTimeFormat(locale, { month: "long" })).format(d)); // full month name
47 s = s.replace(/%b/g, (new Intl.DateTimeFormat(locale, { month: "short" })).format(d)); // abbreviated month name
48
49 //s = s.replace(/%A/g, strftime_weekdays[day]); // full weekday name
50 //s = s.replace(/%a/g, strftime_weekdays[day].substring(0, 3)); // abbreviated weekday name
51 //s = s.replace(/%B/g, strftime_months[mon]); // full month name
52 //s = s.replace(/%b/g, strftime_months[mon].substring(0, 3)); // abbreviated month name
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].className = "out";
112 setTimeout(function() {
113 if (parent.children.length > 1)
114 parent.removeChild(parent.children[0]);
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 the title.
129 if (ts.length) {
130 var d = new Date(Date.parse(ts));
131 title = strftime("%H:%M", d) + " " + title;
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-displaynext")) || 10000, // default: 10 seconds
143 fadein: parseInt(tickerel.getAttribute("data-fadein")) || 1000, // default: 1 second
144 update: parseInt(tickerel.getAttribute("data-update")) || (15 * 60 * 1000) // default: 15 minutes
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 cycling items.
156 if (ticker.displaytimer)
157 clearInterval(ticker.displaytimer);
158
159 ticker.counter = 0; // reset item counter
160 ticker.el.innerHTML = ""; // clear contents
161 ticker.displayfn(ticker); // immediately update the first time
162
163 // display / cycle existing items.
164 ticker.displaytimer = setInterval(function() { ticker.displayfn(ticker); }, ticker.display);
165 });
166 };
167
168 ticker.updater = updater;
169 ticker.updatetimer = setInterval(updater, ticker.update); // update every 15 minutes.
170 setTimeout(updater, delay); // small delay so the newstickers don't align up.
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 XMLHTTPRequest (AJAX).
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 ? parent.childNodes[0].innerHTML : "";
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.animateclass !== "") {
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-class") || "",
222 el: bindel,
223 processfn: processfn,
224 timeout: parseInt(bindel.getAttribute("data-timeout")) || 10000, // default: 10 seconds
225 update: parseInt(bindel.getAttribute("data-update")) || (15 * 60 * 1000), // default: 15 minutes
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 an interval.
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/html";
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-update")) || (15 * 60 * 1000), // default: 15 minutes
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.childNodes); // copy nodes.
282
283 var object = document.createElement("object");
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 object(s) and show the new object.
289 // this prevents some flickering.
290 for (var j = 0; j < objects.length; j++)
291 bindel.removeChild(objects[j]);
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") || "") === "1")
315 videos[i].currentTime = 0; // reset time / seek to start.
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") || "") === "1")
325 videos[i].currentTime = 0; // reset time / seek to start.
326 videos[i].play(); // NOTE: auto-play must be enabled by the client.
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; // default: 1 second
335
336 var fn = (function(el, freq, format) {
337 return function() {
338 el.innerHTML = strftime(format); // allow HTML in format.
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-displaynext") || "";
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); // current, prev
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 seconds
454 timing -= (currentslide.elapsed || 0); // minus played time for this slide.
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 this slide.
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-total-progress")) || 0;
469
470 function progressbar_update(slides) {
471 var currentslide = slides.slides[slides.current];
472 var total = currentslide.timing || 5000; // default: 5 seconds
473
474 if (usetotalprogress) {
475 total = 0;
476 for (var i = 0; i < slides.slides.length; i++) {
477 total += parseInt(slides.slides[i].timing) || 0;
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.prevprogress;
488 }
489 slides.prevprogress = perfnow;
490
491 var elapsed = currentslide.elapsed;
492 if (usetotalprogress) {
493 // current slide progress + all finished previous ones.
494 for (var i = 0; i < slides.current; i++)
495 elapsed += parseInt(slides.slides[i].timing) || 0;
496 }
497
498 // generate linear gradient with ticks for the total progress.
499 if (usetotalprogress) {
500 var total = 0;
501 for (var i = 0; i < slides.slides.length; i++) {
502 total += parseInt(slides.slides[i].timing) || 0;
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].timing) || 0;
509 var percent = (timing / total) * 100.0;
510
511 pattern.push("rgba(255, 255, 255, 0.0) " + (Math.floor((percent - 0.2) * 100) / 100.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, 1.0) " + (Math.floor((percent) * 100) / 100.0) + "%");
517 pattern.push("rgba(255,255,255, 0.0) " + (Math.floor((percent + 0.2) * 100) / 100.0) + "%");
518 }
519 }
520
521 // ticks gradient;
522 var gradient = "linear-gradient(90deg, " + pattern.join(",") + ")";
523 // progress gradient.
524 var percent = (elapsed / total) * 100.0;
525 gradient += ", linear-gradient(90deg, rgba(255, 255, 255, 0.5) " + percent +
526 "%, rgba(255, 255, 255, 0) " + (percent + 0.1) +
527 "%, rgba(255, 255, 255, 0) 100%)" // actual progress
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 and Firefox.
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);