JAVASCRIPT   128

Live css

Guest on 19th July 2022 02:38:26 AM

  1. /*
  2.  * Live CSS will monitor <link> tags on the page and poll the server for changes to the CSS. This enables you
  3.  * to refresh styles without disrupting the state of the view, and the page updates itself without you
  4.  * having to switch from your editor to the browser and hit refresh.
  5.  *
  6.  * Usage:
  7.  * livecss.watchAll() - starts polling all <link> tags in the current page for changes.
  8.  *
  9.  * If you want more fine grained control over which CSS is being autoreloaded:
  10.  * livecss.watch(linkElement) - start watching a single <link> element for changes.
  11.  * livecss.unwatchAll()
  12.  * livecss.unwatch(linkElement)
  13.  *
  14.  * For convenience, livecss will call watchAll() right away if the page has "startlivecss=true" in the URL's
  15.  * query string.
  16.  */
  17. var livecss = {
  18.   // How often to poll for changes to the CSS.
  19.   pollFrequency: 1000,
  20.   outstandingRequests: {}, // stylesheet url => boolean
  21.   filesLastModified: {}, // stylesheet url => last modified timestamp
  22.   watchTimers: {}, // stylesheet url => timer ID
  23.  
  24.   /*
  25.    * Begins polling all link elements on the current page for changes.
  26.    */
  27.   watchAll: function() {
  28.     this.unwatchAll();
  29.     var timerId = setInterval(this.proxy(function() {
  30.       var linkElements = document.getElementsByTagName("link");
  31.       var validMediaTypes = ["screen", "handheld", "all", ""];
  32.       for (var i = 0; i < linkElements.length; i++) {
  33.         var media = (linkElements[i].getAttribute("media") || "").toLowerCase();
  34.         if (linkElements[i].getAttribute("rel") == "stylesheet"
  35.             && livecss.indexOf(validMediaTypes, media) >= 0
  36.             && this.isLocalLink(linkElements[i])) {
  37.           this.refreshLinkElement(linkElements[i]);
  38.         }
  39.       }
  40.     }), this.pollFrequency);
  41.     this.watchTimers["all"] = timerId;
  42.   },
  43.  
  44.   watch: function(linkElement) {
  45.     var url = linkElement.getAttribute("href");
  46.     this.unwatch(url);
  47.     this.watchTimers[url] = setInterval(this.proxy(function() {
  48.       var linkElement = this.linkElementWithHref(url);
  49.       this.refreshLinkElement(linkElement);
  50.     }), this.pollFrequency);
  51.   },
  52.  
  53.   unwatchAll: function() {
  54.     for (var url in this.watchTimers)
  55.       this.unwatch(url);
  56.   },
  57.  
  58.   unwatch: function(url) {
  59.     if (this.watchTimers[url] != null) {
  60.       clearInterval(this.watchTimers[url]);
  61.       delete this.watchTimers[url];
  62.       delete this.outstandingRequests[url];
  63.     }
  64.   },
  65.  
  66.   linkElementWithHref: function(url) {
  67.     var linkElements = document.getElementsByTagName("link");
  68.     for (var i = 0; i < linkElements.length; i++)
  69.       if (linkElements[i].href == url)
  70.         return linkElements[i]
  71.   },
  72.  
  73.   /*
  74.    * Replaces a link element with a new one for the given URL. This has to wait for the new <link> to fully
  75.    * load, because simply changing the href on an existing <link> causes the page to flicker.
  76.    */
  77.   replaceLinkElement: function(linkElement, stylesheetUrl) {
  78.     var parent = linkElement.parentNode;
  79.     var sibling = linkElement.nextSibling;
  80.     var url = this.addCacheBust(linkElement.href);
  81.  
  82.     var newLinkElement = document.createElement("link");
  83.     newLinkElement.href = url;
  84.     newLinkElement.setAttribute("rel", "stylesheet");
  85.  
  86.     if (sibling)
  87.       parent.insertBefore(newLinkElement, sibling);
  88.     else
  89.       parent.appendChild(newLinkElement);
  90.  
  91.     // We're polling to check whether the CSS is loaded, because firefox doesn't support an onload event
  92.     // for <link> elements.
  93.     var loadingTimer = setInterval(this.proxy(function() {
  94.       if (!this.isCssElementLoaded(newLinkElement)) return;
  95.       if (typeof(console) != "undefined")
  96.         console.log("CSS refreshed:", this.removeCacheBust(url));
  97.       clearInterval(loadingTimer);
  98.       delete this.outstandingRequests[this.removeCacheBust(url)];
  99.       parent.removeChild(linkElement);
  100.     }), 100);
  101.   },
  102.  
  103.   /*
  104.    * Refreshes the provided linkElement if it's changed. We issue a HEAD request for the CSS. If its
  105.    * last-modified header is changed, we remove and re-add the <link> element to the DOM which trigger a
  106.    * re-render from the browser. This uses a cache-bust querystring parameter to ensure we always bust through
  107.    * the browser's cache.
  108.    */
  109.   refreshLinkElement: function(linkElement) {
  110.     var url = this.removeCacheBust(linkElement.getAttribute("href"));
  111.     if (this.outstandingRequests[url]) return;
  112.     var request = new XMLHttpRequest();
  113.     this.outstandingRequests[url] = request;
  114.     var cacheBustUrl = this.addCacheBust(url);
  115.  
  116.     request.onreadystatechange = this.proxy(function(event) {
  117.       if (request.readyState != 4) return;
  118.       delete this.outstandingRequests[url];
  119.       if (request.status != 200 && request.status != 304) return;
  120.       var lastModified = Date.parse(request.getResponseHeader("Last-Modified"));
  121.       if (!this.filesLastModified[url] || this.filesLastModified[url] < lastModified) {
  122.         this.filesLastModified[url] = lastModified;
  123.         this.replaceLinkElement(linkElement, cacheBustUrl);
  124.       }
  125.     });
  126.     request.open("HEAD", cacheBustUrl);
  127.     request.send(null);
  128.   },
  129.  
  130.   isCssElementLoaded: function(cssElement) {
  131.     // cssElement.sheet.cssRules will throw an error in firefox when the css file is not yet loaded.
  132.     try { return (cssElement.sheet && cssElement.sheet.cssRules.length > 0); } catch(error) { }
  133.     return false;
  134.   },
  135.  
  136.   /* returns true for local urls such as: '/screen.css', 'http://mydomain.com/screen.css', 'css/screen.css'
  137.   */
  138.   isLocalLink: function(linkElement) {
  139.         //On all tested browsers, this javascript property returns a normalized URL
  140.         var url = linkElement.href;
  141.     var regexp = new RegExp("^\/|^" +
  142.       document.location.protocol + "//" + document.location.host);
  143.     return (url.search(regexp) == 0);
  144.   },
  145.  
  146.   /*
  147.    * Adds and removes a "cache_bust" querystring parameter to the given URLs. This is so we always bust
  148.    * through the browser's cache when checking for updated CSS.
  149.    */
  150.   addCacheBust: function(url) { return this.removeCacheBust(url) + "?cache_bust=" + (new Date()).getTime(); },
  151.   removeCacheBust: function(url) { return url.replace(/\?cache_bust=[^&]+/, ""); },
  152.  
  153.   /* A utility method to bind the value of "this". Equivalent to jQuery's proxy() function. */
  154.   proxy: function(fn) {
  155.     var self = this;
  156.     return function() { return fn.apply(self, []); };
  157.   },
  158.  
  159.   /* Unfortunately IE7 doesn't have this built-in. */
  160.   indexOf: function(array, item) {
  161.     for (var i = 0; i < array.length; i++) { if (array[i] == item) return i; }
  162.     return -1;
  163.   },
  164.  
  165.   /* A utility function for abstracting the difference between event listening in IE and other browsers. */
  166.   addEventListener: function(object, event, fn) {
  167.     object.attachEvent ? object.attachEvent("on" + event, fn) : object.addEventListener(event, fn, false);
  168.   }
  169. };
  170.  
  171. if (window.location.search.toString().indexOf("startlivecss=true") >= 0)
  172.   livecss.addEventListener(window, "load", function() { livecss.watchAll(); });

Raw Paste


Login or Register to edit or fork this paste. It's free.