233 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|  /*
 | |
|   Live.js - One script closer to Designing in the Browser
 | |
|   Written for Handcraft.com by Martin Kool (@mrtnkl).
 | |
| 
 | |
|   Version 4.
 | |
|   Recent change: Made stylesheet and mimetype checks case insensitive.
 | |
| 
 | |
|   http://livejs.com
 | |
|   http://livejs.com/license (MIT)  
 | |
|   @livejs
 | |
| 
 | |
|   Include live.js#css to monitor css changes only.
 | |
|   Include live.js#js to monitor js changes only.
 | |
|   Include live.js#html to monitor html changes only.
 | |
|   Mix and match to monitor a preferred combination such as live.js#html,css  
 | |
| 
 | |
|   By default, just include live.js to monitor all css, js and html changes.
 | |
|   
 | |
|   Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
 | |
|   as a page reload due to a change in html or css would not re-include the bookmarklet.
 | |
|   To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
 | |
| */
 | |
| (function () {
 | |
| 
 | |
|   var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
 | |
|       resources = {},
 | |
|       pendingRequests = {},
 | |
|       currentLinkElements = {},
 | |
|       oldLinkElements = {},
 | |
|       interval = 1000,
 | |
|       loaded = false,
 | |
|       active = { "html": 1, "css": 1, "js": 1 };
 | |
| 
 | |
|   var Live = {
 | |
| 
 | |
|     // performs a cycle per interval
 | |
|     heartbeat: function () {      
 | |
|       if (document.body) {        
 | |
|         // make sure all resources are loaded on first activation
 | |
|         if (!loaded) Live.loadresources();
 | |
|         Live.checkForChanges();
 | |
|       }
 | |
|       setTimeout(Live.heartbeat, interval);
 | |
|     },
 | |
| 
 | |
|     // loads all local css and js resources upon first activation
 | |
|     loadresources: function () {
 | |
| 
 | |
|       // helper method to assert if a given url is local
 | |
|       function isLocal(url) {
 | |
|         var loc = document.location,
 | |
|             reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
 | |
|         return url.match(reg);
 | |
|       }
 | |
| 
 | |
|       // gather all resources
 | |
|       var scripts = document.getElementsByTagName("script"),
 | |
|           links = document.getElementsByTagName("link"),
 | |
|           uris = [];
 | |
| 
 | |
|       // track local js urls
 | |
|       for (var i = 0; i < scripts.length; i++) {
 | |
|         var script = scripts[i], src = script.getAttribute("src");
 | |
|         if (src && isLocal(src))
 | |
|           uris.push(src);
 | |
|         if (src && src.match(/\blive.js#/)) {
 | |
|           for (var type in active)
 | |
|             active[type] = src.match("[#,|]" + type) != null
 | |
|           if (src.match("notify")) 
 | |
|             alert("Live.js is loaded.");
 | |
|         }
 | |
|       }
 | |
|       if (!active.js) uris = [];
 | |
|       if (active.html) uris.push(document.location.href);
 | |
| 
 | |
|       // track local css urls
 | |
|       for (var i = 0; i < links.length && active.css; i++) {
 | |
|         var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
 | |
|         if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
 | |
|           uris.push(href);
 | |
|           currentLinkElements[href] = link;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // initialize the resources info
 | |
|       for (var i = 0; i < uris.length; i++) {
 | |
|         var url = uris[i];
 | |
|         Live.getHead(url, function (url, info) {
 | |
|           resources[url] = info;
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       // add rule for morphing between old and new css files
 | |
|       var head = document.getElementsByTagName("head")[0],
 | |
|           style = document.createElement("style"),
 | |
|           rule = "transition: all .3s ease-out;"
 | |
|       css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
 | |
|       style.setAttribute("type", "text/css");
 | |
|       head.appendChild(style);
 | |
|       style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
 | |
| 
 | |
|       // yep
 | |
|       loaded = true;
 | |
|     },
 | |
| 
 | |
|     // check all tracking resources for changes
 | |
|     checkForChanges: function () {
 | |
|       for (var url in resources) {
 | |
|         if (pendingRequests[url])
 | |
|           continue;
 | |
| 
 | |
|         Live.getHead(url, function (url, newInfo) {
 | |
|           var oldInfo = resources[url],
 | |
|               hasChanged = false;
 | |
|           resources[url] = newInfo;
 | |
|           for (var header in oldInfo) {
 | |
|             // do verification based on the header type
 | |
|             var oldValue = oldInfo[header],
 | |
|                 newValue = newInfo[header],
 | |
|                 contentType = newInfo["Content-Type"];
 | |
|             switch (header.toLowerCase()) {
 | |
|               case "etag":
 | |
|                 if (!newValue) break;
 | |
|                 // fall through to default
 | |
|               default:
 | |
|                 hasChanged = oldValue != newValue;
 | |
|                 break;
 | |
|             }
 | |
|             // if changed, act
 | |
|             if (hasChanged) {
 | |
|               Live.refreshResource(url, contentType);
 | |
|               break;
 | |
|             }
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     // act upon a changed url of certain content type
 | |
|     refreshResource: function (url, type) {
 | |
|       switch (type.toLowerCase()) {
 | |
|         // css files can be reloaded dynamically by replacing the link element                               
 | |
|         case "text/css":
 | |
|           var link = currentLinkElements[url],
 | |
|               html = document.body.parentNode,
 | |
|               head = link.parentNode,
 | |
|               next = link.nextSibling,
 | |
|               newLink = document.createElement("link");
 | |
| 
 | |
|           html.className = html.className.replace(/\s*livejs\-loading/gi, '') + ' livejs-loading';
 | |
|           newLink.setAttribute("type", "text/css");
 | |
|           newLink.setAttribute("rel", "stylesheet");
 | |
|           newLink.setAttribute("href", url + "?now=" + new Date() * 1);
 | |
|           next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
 | |
|           currentLinkElements[url] = newLink;
 | |
|           oldLinkElements[url] = link;
 | |
| 
 | |
|           // schedule removal of the old link
 | |
|           Live.removeoldLinkElements();
 | |
|           break;
 | |
| 
 | |
|         // check if an html resource is our current url, then reload                               
 | |
|         case "text/html":
 | |
|           if (url != document.location.href)
 | |
|             return;
 | |
| 
 | |
|           // local javascript changes cause a reload as well
 | |
|         case "text/javascript":
 | |
|         case "application/javascript":
 | |
|         case "application/x-javascript":
 | |
|           document.location.reload();
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     // removes the old stylesheet rules only once the new one has finished loading
 | |
|     removeoldLinkElements: function () {
 | |
|       var pending = 0;
 | |
|       for (var url in oldLinkElements) {
 | |
|         // if this sheet has any cssRules, delete the old link
 | |
|         try {
 | |
|           var link = currentLinkElements[url],
 | |
|               oldLink = oldLinkElements[url],
 | |
|               html = document.body.parentNode,
 | |
|               sheet = link.sheet || link.styleSheet,
 | |
|               rules = sheet.rules || sheet.cssRules;
 | |
|           if (rules.length >= 0) {
 | |
|             oldLink.parentNode.removeChild(oldLink);
 | |
|             delete oldLinkElements[url];
 | |
|             setTimeout(function () {
 | |
|               html.className = html.className.replace(/\s*livejs\-loading/gi, '');
 | |
|             }, 100);
 | |
|           }
 | |
|         } catch (e) {
 | |
|           pending++;
 | |
|         }
 | |
|         if (pending) setTimeout(Live.removeoldLinkElements, 50);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     // performs a HEAD request and passes the header info to the given callback
 | |
|     getHead: function (url, callback) {
 | |
|       pendingRequests[url] = true;
 | |
|       var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
 | |
|       xhr.open("HEAD", url, true);
 | |
|       xhr.onreadystatechange = function () {
 | |
|         delete pendingRequests[url];
 | |
|         if (xhr.readyState == 4 && xhr.status != 304) {
 | |
|           xhr.getAllResponseHeaders();
 | |
|           var info = {};
 | |
|           for (var h in headers) {
 | |
|             var value = xhr.getResponseHeader(h);
 | |
|             // adjust the simple Etag variant to match on its significant part
 | |
|             if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
 | |
|             if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
 | |
|             info[h] = value;
 | |
|           }
 | |
|           callback(url, info);
 | |
|         }
 | |
|       }
 | |
|       xhr.send();
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   // start listening
 | |
|   if (document.location.protocol != "file:") {
 | |
|     if (!window.liveJsLoaded)
 | |
|       Live.heartbeat();
 | |
| 
 | |
|     window.liveJsLoaded = true;
 | |
|   }
 | |
|   else if (window.console)
 | |
|     console.log("Live.js doesn't support the file protocol. It needs http.");    
 | |
| })(); |