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);