mirror of
https://git.mills.io/prologic/zs-starter-template.git
synced 2024-11-23 22:06:06 +03:00
233 lines
8.1 KiB
JavaScript
233 lines
8.1 KiB
JavaScript
/*
|
|
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.");
|
|
})(); |