Created
April 15, 2011 14:16
-
-
Save mixonic/921764 to your computer and use it in GitHub Desktop.
Forked from http://livejs.com/ - modified for Harvest http://www.getharvest.com
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Use guard for watching the filesystem and running | |
# Compass compilers. | |
# | |
group :development do | |
gem 'guard-compass' | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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