Skip to content

Instantly share code, notes, and snippets.

@mixonic
Created April 15, 2011 14:16
Show Gist options
  • Save mixonic/921764 to your computer and use it in GitHub Desktop.
Save mixonic/921764 to your computer and use it in GitHub Desktop.
Forked from http://livejs.com/ - modified for Harvest http://www.getharvest.com
<!-- app/views/layouts/application.erb -->
<html><body>
<!-- Include the live.js script, async & only in dev mode please -->
<%= javascript_include_tag('live', :async => :async) if Rails.env.development? %>
</body></html>
# Use guard for watching the filesystem and running
# Compass compilers.
#
group :development do
gem 'guard-compass'
end
#!/usr/bin/env ruby
# script/guard
#
# Guard works best with the rb-fsevent gem for OSX, but
# Bundler doesn't support different gems for different
# operating systems. So we use this script to load rb-fsevent
# before running Guard.
#
require 'rubygems'
if RUBY_PLATFORM.include?('darwin')
require 'rb-fsevent'
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'guard'
require 'guard/cli'
Guard::CLI.start
# More info at https://github.com/guard/guard#readme
# Tell guard to use the guard-compass gem we included in
# our Gemfile. Specify where sass/scss is actually on
# disk.
#
guard 'compass' do
watch(%r{^public/stylesheets/sass/.*?/?([^/]*)\.s[ac]ss})
end
(function () {
/*
public/javascripts/live.js
Live.js - One script closer to Designing in the Browser
Written for Quplo.com by Martin Kool (@mrtnkl).
Modified for Harvest by Matthew Beale (@mixonic).
http://livejs.com
http://livejs.com/license (MIT)
The modified version of this script will only run when
a url has a target of #live. So if you visit:
http://myapp.com/foo
You are using a plain old refresh-the-page workflow. Turn on
live with:
http://myapp.com/foo#live
And your page will update on the fly. Be sure to run `script/guard`
if you are using compass or any other asset compiler.
You can also open a javascript console and run:
live();
To enable live without reloading a page.
*/
var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
Resources = {},
CurrentLinkElements = {},
OldLinkElements = {},
interval = 750,
loaded = false,
active = false;
var Live = {
// performs a cycle per interval
heartbeat: function () {
// check if #live is found in the hash, and activate or deactivate accordingly
active = document.location.hash.indexOf("live") != -1;
if (active) {
// 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("^\\.|^\/|^" + loc.protocol + "//" + loc.host);
return url.match(reg) || !url.match(/:\/\//);
}
// gather all resources
var scripts = document.getElementsByTagName("script"),
links = document.getElementsByTagName("link"),
resources = [];
// track local css urls
for (var i = 0; i < links.length; i++) {
var link = links[i], type = href = link.getAttribute("type"), href = link.getAttribute("href");
if (type && href && type.toLowerCase() == "text/css" && isLocal(href)) {
resources.push(href);
CurrentLinkElements[href] = link;
}
}
// track local js urls
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i], src = script.getAttribute("src");
if (src && isLocal(src)) {
resources.push(src);
}
}
// initialize the resources info
for (var i = 0; i < resources.length; i++) {
var url = resources[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) {
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) {
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) {
// 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) {
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
xhr.open("HEAD", url, true);
xhr.onreadystatechange = function () {
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 == "Etag" && value) value = value.replace(/^W\//, '');
if (h == "Content-Type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
info[h] = value;
}
callback(url, info);
}
}
xhr.send();
}
};
// start listening
window.addEventListener ? window.addEventListener("load", Live.heartbeat, false) :
window.attachEvent("onload", Live.heartbeat, false);
})();
window.live = function(){
if (document.location.hash.indexOf("live") == -1)
document.location = document.location + '#live';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment