Permalink
Browse files

Full rewrite of the client code, also support @import CSS commands.

  • Loading branch information...
1 parent 297139f commit 3a295509b88b459afef36662bd0a064b2a1fe559 @Poetro Poetro committed Aug 11, 2011
Showing with 230 additions and 106 deletions.
  1. +230 −106 src/client/vogue-client.js
View
@@ -1,132 +1,256 @@
// Vogue - Client
// Copyright (c) 2011 Andrew Davey (andrew@equin.co.uk)
+(function () {
+ var script,
+ hop = Object.prototype.hasOwnProperty,
+ head = document.getElementsByTagName("head")[0];
-(function() {
+ function vogue() {
+ var stylesheets,
+ socket = io.connect(script.rootUrl);
-var script = getScriptInfo();
+ /**
+ * Watch for all available stylesheets.
+ */
+ function watchAllStylesheets() {
+ var href;
+ for (href in stylesheets) {
+ if (hop.call(stylesheets, href)) {
+ socket.emit("watch", {
+ href: href
+ });
+ }
+ }
+ }
-loadScripts({
- io: script.rootUrl + 'socket.io/socket.io.js'
-}, vogue);
+ /**
+ * Reload a stylesheet.
+ *
+ * @param {String} href The URL of the stylesheet to be reloaded.
+ */
+ function reloadStylesheet(href) {
+ var newHref = stylesheets[href].href +
+ (href.indexOf("?") >= 0 ? "&" : "?") +
+ "_vogue_nocache=" + (new Date).getTime(),
+ stylesheet;
+ // Check if the appropriate DOM Node is there.
+ if (!stylesheets[href].setAttribute) {
+ // Create the link.
+ stylesheet = document.createElement("link");
+ stylesheet.setAttribute("rel", "stylesheet");
+ stylesheet.setAttribute("href", newHref);
+ head.appendChild(stylesheet);
+
+ // Update the reference to the newly created link.
+ stylesheets[href] = stylesheet;
+ } else {
+ // Update the href to the new URL.
+ stylesheets[href].href = newHref;
+ }
+ }
-function vogue() {
- var stylesheets = getLocalStylesheets();
- var socket = io.connect(script.rootUrl);
- socket.on('connect', watchAllStylesheets);
- socket.on('update', handleMessage);
- function watchAllStylesheets() {
- for (var href in stylesheets) {
- socket.emit('watch', { href: href });
+ /**
+ * Handle messages from socket.io, and load the appropriate stylesheet.
+ *
+ * @param message Socket.io message object.
+ * @param message.href The url of the stylesheet to be loaded.
+ */
+ function handleMessage(message) {
+ reloadStylesheet(message.href);
}
- }
- function handleMessage(message) {
- reloadStylesheet(message.href);
- }
+ /**
+ * Fetch all the local stylesheets from the page.
+ *
+ * @returns {Object} The list of local stylesheets keyed by their base URL.
+ */
+ function getLocalStylesheets() {
- function reloadStylesheet(href) {
- var newHref = stylesheets[href].href
- + (href.indexOf('?') >= 0 ? '&' : '?')
- + '_vogue_nocache='
- + (new Date()).getTime();
- stylesheets[href].href = newHref;
- }
-
- function getLocalStylesheets() {
-
- function isLocalStylesheet(link) {
- if (link.getAttribute('rel') !== 'stylesheet') {
- return false;
+ /**
+ * Checks if the stylesheet is local.
+ *
+ * @param {Object} link The link to check for.
+ * @returns {Boolean}
+ */
+ function isLocalStylesheet(link) {
+ var href, i, isExternal = true;
+ if (link.getAttribute("rel") !== "stylesheet") {
+ return false;
+ }
+ href = link.href;
+
+ for (i = 0; i < script.bases.length; i += 1) {
+ if (href.indexOf(script.bases[i]) > -1) {
+ isExternal = false;
+ break;
+ }
+ }
+
+ return !(isExternal && href.match(/^https?:/));
}
- var href = link.href;
- var isExternal = true;
- for (var i=0; i<script.bases.length; i++) {
- if (href.indexOf(script.bases[i]) > -1) {
- isExternal = false;
- break;
+
+ /**
+ * Get the link's base URL.
+ *
+ * @param {String} href The URL to check.
+ * @returns {String|Boolean} The base URL, or false if no matches found.
+ */
+ function getBase(href) {
+ var base, j;
+ for (j = 0; j < script.bases.length; j += 1) {
+ base = script.bases[j];
+ if (href.indexOf(base) > -1) {
+ return href.substr(base.length);
+ }
}
+ return false;
}
- return !(isExternal && href.match(/^https?:/));
- }
-
- var links = document.getElementsByTagName('link');
- var stylesheets = {};
- for (var i = 0, m = links.length; i < m; i++) {
- if (!isLocalStylesheet(links[i])) {
- continue;
+
+ function getProperty(property) {
+ return this[property];
}
- // Match hrefs against stylesheet bases we know of
- for (var j=0; j<script.bases.length; j++) {
- if (links[i].href.indexOf(script.bases[j]) > -1) {
- var href = links[i].href.substr(script.bases[j].length);
- stylesheets[href] = links[i];
- break;
+
+ var stylesheets = {},
+ reImport = /@import\s+url\(["']?([^"'\)]+)["']?\)/g,
+ links = document.getElementsByTagName("link"),
+ link, href, matches, content, i, m;
+
+ // Go through all the links in the page, looking for stylesheets.
+ for (i = 0, m = links.length; i < m; i += 1) {
+ link = links[i];
+ if (isLocalStylesheet(link)) {
+ // Link is local, get the base URL.
+ href = getBase(link.href);
+ if (href !== false) {
+ stylesheets[href] = link;
+ }
}
- }
+ }
+
+ // Go through all the style tags, looking for @import tags.
+ links = document.getElementsByTagName("style");
+ for (i = 0, m = links.length; i < m; i += 1) {
+ content = links[i].text || links[i].textContent;
+ while ((matches = reImport.exec(content))) {
+ link = {
+ rel: "stylesheet",
+ href: matches[1],
+ getAttribute: getProperty
+ };
+ if (isLocalStylesheet(link)) {
+ // Link is local, get the base URL.
+ href = getBase(link.href);
+ if (href !== false) {
+ stylesheets[href] = link;
+ }
+ }
+ }
+ }
+ return stylesheets;
}
- return stylesheets;
+ stylesheets = getLocalStylesheets();
+ socket.on("connect", watchAllStylesheets);
+ socket.on("update", handleMessage);
}
-}
-function loadScripts(scripts, loadedCallback) {
- var srcs = [];
- for (var property in scripts) {
- if (!(property in window)) srcs.push(scripts[property]);
- }
+ /**
+ * Load a script into the page, and call a callback when it is loaded.
+ *
+ * @param {String} src The URL of the script to be loaded.
+ * @param {Function} loadedCallback The function to be called when the script is loaded.
+ */
+ function loadScript(src, loadedCallback) {
+ var script = document.createElement("script");
+ script.setAttribute("type", "text/javascript");
+ script.setAttribute("src", src);
+
+ // Call the callback when the script is loaded.
+ script.onload = loadedCallback;
+ script.onreadystatechange = function () {
+ if (this.readyState === "complete" || this.readyState === "loaded") {
+ loadedCallback();
+ }
+ };
- var count = srcs.length;
- if (count == 0) loadedCallback();
- for (var i = 0; i < srcs.length; i++) {
- var src = srcs[i];
- loadScript(src, function() {
- count--;
- if (count == 0) loadedCallback();
- });
+ head.appendChild(script);
}
-}
-
-function loadScript(src, loadedCallback) {
- var script = document.createElement('script');
- script.setAttribute('type', 'text/javascript');
- script.setAttribute('src', src);
- script.onload = loadedCallback; // Chrome
- script.onreadystatechange = function () { // IE?
- if (this.readyState == 'complete' || this.readyState == 'loaded') loadedCallback();
- };
- document.getElementsByTagName('head')[0].appendChild(script);
-}
-
-function getScriptInfo() {
- var bases = [document.location.protocol + '//' + document.location.host];
- if (typeof window.__vogue__ === "undefined") {
- var scripts = document.getElementsByTagName("script");
- var src = scripts[scripts.length - 1].getAttribute("src");
-
- var rootUrl = src.match(/^https?\:\/\/(.*?)\//)[0];
-
- var baseMatch = src.match(/\bbase=(.*)(&|$)/);
- if (baseMatch) {
- bases = bases.concat(baseMatch[1].split(','));
+
+ /**
+ * Load scripts into the page, and call a callback when they are loaded.
+ *
+ * @param {Array} scripts The scripts to be loaded.
+ * @param {Function} loadedCallback The function to be called when all the scripts have loaded.
+ */
+ function loadScripts(scripts, loadedCallback) {
+ var srcs = [], property, count, i, src,
+ countDown = function () {
+ count -= 1;
+ if (!count) {
+ loadedCallback();
+ }
+ };
+
+ for (property in scripts) {
+ if (!(property in window)) {
+ srcs.push(scripts[property]);
+ }
}
- return {
- rootUrl: rootUrl,
- bases: bases
- };
- } else {
- window.__vogue__.bases = bases;
- return window.__vogue__;
+ count = srcs.length;
+ if (!count) {
+ loadedCallback();
+ }
+
+ for (i = 0; i < srcs.length; i += 1) {
+ src = srcs[i];
+ loadScript(src, countDown);
+ }
+ }
+
+ /**
+ * Fetches the info for the vogue client.
+ */
+ function getScriptInfo() {
+ var bases = [ document.location.protocol + "//" + document.location.host ],
+ scripts, src, rootUrl, baseMatch;
+ if (typeof window.__vogue__ === "undefined") {
+ scripts = document.getElementsByTagName("script");
+ // The last parsed script will be our script.
+ src = scripts[scripts.length - 1].getAttribute("src");
+ rootUrl = src.match(/^https?\:\/\/(.*?)\//)[0];
+ // There is an optional base argument, that can be used.
+ baseMatch = src.match(/\bbase=(.*)(&|$)/);
+
+ if (baseMatch) {
+ bases = bases.concat(baseMatch[1].split(","));
+ }
+ return {
+ rootUrl: rootUrl,
+ bases: bases
+ };
+ } else {
+ window.__vogue__.bases = bases;
+ return window.__vogue__;
+ }
}
-}
-function getPort(url) {
- // URL may contain the port number after the second colon.
- // http://domain:1234/
- var index = url.indexOf(':', 6); // skipping 6 characters to ignore first colon
- if (index < 0) return 80; // default to port 80 if none found.
- return parseInt(url.substr(index+1), 10);
-}
+ /**
+ * Fetches the port from the URL.
+ *
+ * @param {String} url URL to get the port from
+ * @returns {Number} The port number, or 80 if no port number found or is invalid.
+ */
+ function getPort(url) {
+ // URL may contain the port number after the second colon.
+ // http://domain:1234/
+ var index = url.indexOf(":", 6); // skipping 6 characters to ignore first colon
+ return index < 0 ? 80 : parseInt(url.substr(index + 1), 10);
+ }
-})();
+ script = getScriptInfo();
+ loadScripts({
+ io: script.rootUrl + "socket.io/socket.io.js"
+ }, vogue);
+}());

0 comments on commit 3a29550

Please sign in to comment.