File: css_notify.js
/**
@module CSSNotification
*/
////////////////////////////////////////////////////
//
// BrowserID
//
////////////////////////////////////////////////////
/**
Class for identifying the family of javascript engine interpreting the code.
*Typical Usage*
var browser_id = new BrowserID();
// getId() outputs a string identifier such as "gecko", "trident" or "webkit"
console.log("The javascript engine is from the following family: " + browser_id.getId() );
@class BrowserID
@constructor
*/
function BrowserID() {
}
BrowserID.prototype.engine_search_strings = {
gecko: "Gecko/[0-9]",
webkit: "AppleWebKit/[0-9]",
trident: "Trident/[0-9]"
};
BrowserID.prototype.getId = function() {
var id_for_returning = "unknown";
for( engine_id in this.engine_search_strings ) {
if( this.engine_search_strings.hasOwnProperty( engine_id ) &&
this.engine_search_strings[ engine_id ] )
{
var ua_regex = new RegExp( this.engine_search_strings[engine_id].toLowerCase() );
ua_full_string = this.getUserAgentString() || "";
if( ua_regex.test(ua_full_string.toLowerCase()) ) {
id_for_returning = engine_id;
break;
}
}
}
return id_for_returning;
}
BrowserID.prototype.getUserAgentString = function() {
return navigator.userAgent;
}
////////////////////////////////////////////////////
//
// capitalizeString
//
////////////////////////////////////////////////////
function capitalizeString(input_string) {
return input_string.charAt(0).toUpperCase() + input_string.slice(1);
}
////////////////////////////////////////////////////
//
// CSSNotificationStrategyFactory
//
////////////////////////////////////////////////////
/**
Factory class creates objects of the correct strategy class depending on the identifier passed to
the constructor, and the implementation of the getStrategyName() method.
*Typical Usage*
var class_factory = new CSSNotificationStrategyFactory("gecko");
var strategy_class = class_factory.newStrategyObject();
// By default, the type would be CSSNotificationGeckoStrategy
// If this class did not exist, the object type would default to CSSNotificationBasicStrategy
console.log("The returned object is of type: " + strategy_class.constructor );
@class CSSNotificationStrategyFactory
@constructor
*/
function CSSNotificationStrategyFactory(strategy_id) {
this.strategy_id = strategy_id;
}
CSSNotificationStrategyFactory.prototype.getStrategyName = function() {
return "CSSNotification" + capitalizeString(this.strategy_id) + "Strategy";
}
CSSNotificationStrategyFactory.prototype.newStrategyObject = function() {
try {
return new window[this.getStrategyName()];
} catch(caught_error) {
return this.newBaseClassObjectForKnownException(caught_error);
}
}
CSSNotificationStrategyFactory.prototype.newBaseClassObjectForKnownException = function(error) {
if( error.constructor === TypeError ) {
return new CSSNotificationBasicStrategy();
} else {
throw error;
}
}
////////////////////////////////////////////////////
//
// CSSNotificationBasicStrategy
//
////////////////////////////////////////////////////
/**
Default strategy implementation for the CSSNotification class, simply attaches a provided
callback to the onload property of the node that the notification is set on.
*Typical Usage*
var css_node = document.GetElementById("theme-css");
var strategy = new CSSNotificationBasicStrategy();
// The supplied callback will get called asyncronously in IE, unfortunately this strategy
// does not work in some other browsers
strategy.setNotification(css_node, function() { console.log("CSS file has finished loading."); } );
@class CSSNotificationBasicStrategy
@constructor
*/
function CSSNotificationBasicStrategy() {
}
CSSNotificationBasicStrategy.prototype.setNotification = function(node, callback) {
this.throwExceptionForInvalidArgument(node, callback);
node.onload = callback;
}
CSSNotificationBasicStrategy.prototype.throwExceptionForInvalidArgument = function(node, callback) {
var err_prefix = this.constructor + ".setNotification() error: ";
if( node == null ) {
throw new Error(err_prefix + "invalid node passed for setting notification.");
}
if( callback == null || typeof callback !== 'function' ) {
throw new Error(err_prefix + "invalid notification callback passed.");
}
}
////////////////////////////////////////////////////
//
// CSSNotificationPollingStrategy
//
////////////////////////////////////////////////////
/**
A base class for Notification strategies which is further refined for use in cases where the notification
mechanism relies on a polling strategy, for example periodically checking the DOM to see if CSS
styles have been added to it yet by the browser rendering the page.
Usage is the same as for CSSNotificationBasicStrategy.
@class CSSNotificationPollingStrategy
@extends CSSNotificationBasicStrategy
@constructor
*/
function CSSNotificationPollingStrategy() {
this.stop_watch = new StopWatch();
this.stop_polling_timeout_in_s = 10;
this.poll_frequency_in_ms = 50;
this.anotherPollWasRequested = false;
}
CSSNotificationPollingStrategy.prototype.__proto__ = new CSSNotificationBasicStrategy();
CSSNotificationPollingStrategy.prototype.setNotification = function(node, callback) {
this.throwExceptionForInvalidArgument(node, callback);
this.stop_watch.start();
this.poll(node, callback);
}
CSSNotificationPollingStrategy.prototype.poll = function(node, callback) {
this.invokeNotificationIfResourceHasLoaded(node, callback);
if( this.isAnotherPollRequired() ) {
this.scheduleAnotherPoll();
}
}
CSSNotificationPollingStrategy.prototype.invokeNotificationIfResourceHasLoaded = function(node, callback) {
throw new Error("Abstract base class method must be overridden.");
}
CSSNotificationPollingStrategy.prototype.isAnotherPollRequired = function() {
return ( this.anotherPollWasRequested &&
this.stop_watch.secondsElapsed < this.stop_polling_timeout );
}
CSSNotificationPollingStrategy.prototype.scheduleAnotherPoll = function(node, callback) {
setTimeout(
function () {
this.poll(node, callback);
},
this.poll_frequency_in_ms
);
this.anotherPollWasRequested = false;
}
CSSNotificationPollingStrategy.prototype.continuePolling = function() {
this.anotherPollWasRequested = true;
}
////////////////////////////////////////////////////
//
// CSSNotificationGeckoStrategy
//
////////////////////////////////////////////////////
/**
@class CSSNotificationGeckoStrategy
@extends CSSNotificationPollingStrategy
@constructor
*/
function CSSNotificationGeckoStrategy() {
}
CSSNotificationGeckoStrategy.prototype.__proto__ = new CSSNotificationPollingStrategy();
/**
This method was borrowed and adapted from lazyload, Copyright (c) 2011 Ryan Grove <ryan@wonko.com>
See original source and license information at this link:
https://github.com/rgrove/lazyload/blob/master/lazyload.js
Begins polling to determine when the specified stylesheet has finished loading
in Gecko. Polling stops when all pending stylesheets have loaded or after
a timeout period (to prevent stalls).
Successful loading of the stylesheet within the timeout period is signalled by the invocation of the
client-supplied callback function. There is no guarantee of this callback being invoked.
@method poll
@param {HTMLElement} node Style node to poll.
@param {function} callback Callback function is invoked if the polled resource finishes loading
within the timeout period.
@private
*/
CSSNotificationGeckoStrategy.prototype.invokeNotificationIfResourceHasLoaded = function(node, callback) {
var hasRules;
try {
// We don't really need to store this value or ever refer to it again, but
// if we don't store it, Closure Compiler assumes the code is useless and
// removes it.
hasRules = !!node.sheet.cssRules;
} catch (ex) {
// An exception means the stylesheet is still loading.
this.continuePolling();
return;
}
// If we get here, the stylesheet has loaded.
callback();
}
////////////////////////////////////////////////////
//
// CSSNotificationWebkitStrategy
//
////////////////////////////////////////////////////
/**
@class CSSNotificationWebkitStrategy
@extends CSSNotificationPollingStrategy
@constructor
*/
function CSSNotificationWebkitStrategy() {
}
CSSNotificationWebkitStrategy.prototype.__proto__ = new CSSNotificationPollingStrategy();
/**
This method was borrowed and adapted from lazyload, Copyright (c) 2011 Ryan Grove <ryan@wonko.com>
See original source and license information at this link:
https://github.com/rgrove/lazyload/blob/master/lazyload.js
Begins polling to determine when pending stylesheets have finished loading
in WebKit. Polling stops when all pending stylesheets have loaded or after configured
timeout period (to prevent stalls).
Successful loading of the stylesheet within the timeout period is signalled by the invocation of the
client-supplied callback function. There is no guarantee of this callback being invoked.
@method poll
@param {HTMLElement} node Style node to poll.
@param {function} callback Callback function is invoked if the polled resource finishes loading
within the timeout period.
@private
*/
CSSNotificationWebkitStrategy.prototype.invokeNotificationIfResourceHasLoaded = function(node, callback) {
i = document.styleSheets.length;
// Look for a stylesheet matching the pending URL.
while (--i >= 0) {
if (this.resourceMatchesStyleSheet(node, document.styleSheets[i])) {
callback();
break;
}
}
this.continuePolling();
}
CSSNotificationWebkitStrategy.prototype.resourceMatchesStyleSheet = function(node,style_sheet) {
var node_href = node.href || node.attributes["data-href"];
var css_href = style_sheet.href || style_sheet.ownerNode.attributes["data-href"];
return node_href == css_href;
}