123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- /*
- 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 (Live.removeArgsId(url) != Live.removeArgsId(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();
- }
- },
- // remove args and id in url
- removeArgsId: function(url){
- var idx = url.indexOf("?");
- if(idx >= 0){
- url = url.substr(0, idx);
- }
- idx = url.indexOf("#");
- if(idx >= 0){
- url = url.substr(0, idx);
- }
- return url;
- },
-
- // 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.");
- })();
-
|