app/assets/javascripts/lib/dominoes-1.0-rc2.js
/**
* Dominoes v.1.0 (rc2) [2/3/10 01:04:06.934 - CET]
* Copyright 2010, Julian Aubourg
* Dual licensed under the MIT and GPL Version 2 licenses
*/
(function(
window ,
document ,
TRUE ,
FALSE ,
NULL ,
STR_APPLY,
STR_DOMINOES,
undefined ) {
if ( ! window[ STR_DOMINOES ] )
(function(
STR_ASYNC,
STR_CACHE,
STR_CALL,
STR_CHAIN,
STR_CHARSET,
STR_CREATE_ELEMENT,
STR_GET_ELEMENTS_BY_TAG_NAME,
STR_HREF,
STR_LENGTH,
STR_ON_LOAD,
STR_ON_READY_STATE_CHANGE,
STR_PLUS,
STR_PUSH,
STR_READY_STATE,
STR_URL ) {
// Throw an exception
function error( type , msg ) {
throw [ STR_DOMINOES , type , msg ].join( ": " );
}
// Main function
function dominoes() {
execute ( slice[ STR_CALL ]( arguments , 0 ) , {} , {} , noop );
return dominoes;
}
dominoes.run = dominoes;
var // Head node
head = document[ STR_GET_ELEMENTS_BY_TAG_NAME ]( "head" )[ 0 ] || document.documentElement,
// References
toString = {}.toString,
slice = [].slice,
// RegExp
loadedCompleteRegExp = /loaded|complete/,
// Temp var
temp;
// noop
function noop() {}
// Defer execution
function later( func , self ) {
setTimeout( function() {
func[ STR_APPLY ]( self || window , slice[ STR_CALL ]( arguments , 2 ) );
} , 0 );
return dominoes;
}
dominoes.later = later;
// Utilities
for ( temp in { Array:1 , Function:1 , String:1 } ) {
( function( name , str ) {
str = "[object " + name + "]";
dominoes[ "is" + name ] = function( object ) {
return toString[ STR_CALL ]( object ) === str;
};
} )( temp );
}
var isArray = dominoes.isArray,
isFunction = dominoes.isFunction,
isString = dominoes.isString;
function pollFunction() {
var tmp = [],
args;
while( pollTasks[ STR_LENGTH ] ) {
args = pollTasks.shift();
try {
if ( args[ 0 ][ STR_APPLY ]( slice[ STR_CALL ]( args , 1 ) ) !== FALSE ) {
tmp[ STR_PUSH ]( args );
}
} catch ( _ ) {}
}
pollTasks = tmp;
if ( ! pollTasks[ STR_LENGTH ] ) {
clearInterval( pollTimer );
}
}
var pollTimer,
pollTasks = [],
poll = dominoes.poll = function( func ) {
if ( isFunction( func ) ) {
if ( ! pollTasks[ STR_LENGTH ] ) {
pollTimer = setInterval( pollFunction , 13 );
}
pollTasks[ STR_PUSH ]( arguments );
}
return dominoes;
};
var readyCallbacks = [],
readyListenedTo = FALSE,
readyAcknowledged = FALSE,
readyFireing = FALSE;
function fireReady() {
while ( readyCallbacks[ STR_LENGTH ] ) {
args = readyCallbacks.shift();
args[ 0 ][ STR_APPLY ]( document , slice[ STR_CALL ]( args , 1 ) );
}
readyFireing = FALSE;
}
function testReady() {
if ( ( ! document[ STR_READY_STATE ] || document[ STR_READY_STATE ] === "complete" )
&& document.body ) {
readyAcknowledged = readyFireing = TRUE;
later( fireReady );
return FALSE;
}
}
function ready( func ) {
if ( isFunction ( func ) ) {
readyCallbacks[ STR_PUSH ]( arguments );
if ( ! readyListenedTo ) {
readyListenedTo = TRUE;
if ( ! testReady() ) {
poll( testReady );
}
} else if ( readyAcknowledged && ! readyFireing ) {
readyFireing = TRUE;
fireReady();
}
}
return FALSE;
}
// Generic data holder
function dataHolder( create ) {
var data = {};
return function( id , del ) {
var length = arguments[ STR_LENGTH ];
if ( length > 1 ) {
if ( del === FALSE ) {
if ( data[ id ] ) {
delete data[ id ];
}
} else if (create) {
create[ STR_APPLY ]( data , arguments );
} else {
data[ id ] = del;
}
} else if ( id === FALSE ) {
data = {};
} else if ( length ) {
return data[ id ];
}
return dominoes;
};
}
var property = dominoes.property = dataHolder();
var // Predefined functors
predefinedFunctors = {},
// Make predefined
predefinedFunctor = function( name , types , action ) {
functor( name + "(" + types + ")" , function( arg ) {
return function( callback ) {
action( arg , callback );
return FALSE;
}
} );
predefinedFunctors[ name ] = functor( name );
functor( name , FALSE );
},
// Declare a functor
functor = dominoes.functor = dataHolder( function( _id , func ) {
var parts = /^([^$()]+)(?:\(([|SOF+]*)\))?$/.exec( _id );
if ( parts ) {
if ( isFunction( func ) ) {
var functors = this,
id = parts[ 1 ],
functor = functors[ id ] = functors[ id ] || function( _data , thread ) {
var data = _data,
context = this;
if ( data ) {
if ( subFunctors[ STR_PLUS ] && isString( data ) ) {
if ( subFunctors[ STR_PLUS ] !== plus ) {
plus = subFunctors[ STR_PLUS ];
accu = accumulator( plus );
}
data = function( callback ) {
accu( { url : _data } , callback );
return FALSE;
};
} else if ( isString( data ) && ( subFunctors.S || subFunctors.O ) ) {
if ( subFunctors.S ) {
data = subFunctors.S[ STR_CALL ]( context , data , thread );
} else if ( subFunctors.O ) {
data = subFunctors.O[ STR_CALL ]( context , { url : data } , thread );
}
} else if ( data.url && subFunctors.O ) {
data = subFunctors.O[ STR_CALL ]( context , data , thread );
} else if ( subFunctors.F ) {
data = subFunctors.F[ STR_CALL ]( context , isFunction( data ) ? data : function ( callback , thread ) {
execute( _data , this , thread , callback );
return FALSE;
} , thread );
}
}
return data;
},
accu = functor.A,
subFunctors = functor.S = functor.S || {},
plus = subFunctors[ STR_PLUS ],
types = ( parts[ 2 ] || "F|S|O" ).split( /\|/ ),
i = types[ STR_LENGTH ];
while( i-- ) {
subFunctors[ types[ i ] ] = func;
}
}
}
} );
var rule = dominoes.rule = dataHolder( function( id ) {
var rules = this,
running = FALSE,
callbacks = [],
rule = rules[ id ] = rules[ id ] || function( callback , thread ) {
if ( callback && callback !== noop ) {
callbacks[ STR_PUSH ]( callback );
}
if ( ! running ) {
running = TRUE;
var context = this;
( function internal() {
if ( list[ STR_LENGTH ] ) {
execute( list.splice( 0 , list[ STR_LENGTH ] ) , context , thread , internal );
} else if ( callbacks[ STR_LENGTH ] ) {
while( callbacks[ STR_LENGTH ] ) {
( callbacks.shift() )();
}
internal();
} else {
running = FALSE;
}
} )();
}
return FALSE;
},
list = rule.A = rule.A || [];
list[ STR_PUSH ]( slice[ STR_CALL ]( arguments , 1 ) );
} );
// Execute an item
function execute( item , context , thread , callback ) {
var url,
length;
if ( item ) {
if ( item.O && callback ) {
callback();
callback = noop;
}
if ( item[ STR_CHAIN ] ) {
context = item;
item = item[ STR_CHAIN ];
}
if ( item[ STR_URL ] ) {
url = item[ STR_URL ];
} else if ( isString( item ) ) {
url = item;
}
if ( url ) {
url = parse( url , context , thread );
if ( isString( url ) ) {
if ( isString( item ) ) {
item = {
url: url
};
} else {
item[ STR_URL ] = url;
}
loadScript( item , callback );
} else {
execute( url , context , thread , callback );
}
} else if ( isFunction( item ) ) {
if ( item[ STR_CALL ]( context , callback , thread ) !== FALSE ) {
callback();
}
} else if ( isArray( item ) && ( length = item[ STR_LENGTH ] ) ) {
if ( item.P ) {
var i = 0,
num = length;
while ( i < length ) {
execute( item[ i++ ] , context , thread , function() {
if ( ! --num ) {
callback();
}
} );
}
} else {
function iterate( i ) {
if ( i < length ) {
execute( item[ i++ ] , context , thread , function() {
iterate( i );
} );
} else {
callback();
}
}
iterate( 0 );
}
} else {
callback();
}
} else {
callback();
}
}
var // Regular expressions
/** @const */ R_DELIM = /\s+/,
// Symbols
SYMBOLS = {},
/** @const */ SYM_WAIT = 1,
/** @const */ SYM_READY = 2,
/** @const */ SYM_BEGIN = 3,
/** @const */ SYM_END = 4,
/** @const */ SYM_BEGIN_OPT = 5,
/** @const */ SYM_END_OPT = 6,
// Miscellaneous
symbolsArray = "0 > >| ( ) (( ))".split( R_DELIM ),
i = symbolsArray[ STR_LENGTH ];
// Initialize symbols
for (; --i ; SYMBOLS[ symbolsArray[ i ] ] = i ) {}
// Parse a chain
function parseChain( chain ) {
chain = chain.split( R_DELIM );
var i = 0,
length = chain[ STR_LENGTH ],
stack = [],
root = [],
current = root,
tmp,
item;
current.P = TRUE;
for( ; i < length ; i++ ) {
if ( item = chain[ i ] ) {
if ( SYMBOLS[ item ] ) {
item = SYMBOLS[ item ];
if ( item === SYM_WAIT || item === SYM_READY ) {
if ( item === SYM_READY ) {
current[ STR_PUSH ]( ready );
}
if ( current[ STR_LENGTH ] ) {
tmp = current.splice( 0 , current[ STR_LENGTH ] );
tmp.P = current.P;
current[ STR_PUSH ]( tmp , [] );
current.P = FALSE;
current = current[ 1 ];
current.P = TRUE;
}
} else if ( item === SYM_BEGIN || item === SYM_BEGIN_OPT ) {
tmp = [];
current[ STR_PUSH ]( tmp );
stack[ STR_PUSH ]( current );
current = tmp;
current.P = TRUE;
current.O = item === SYM_BEGIN_OPT;
} else if ( item === SYM_END || item === SYM_END_OPT ) {
if ( stack[ STR_LENGTH ] ) {
current = stack.pop();
} else {
error( "unexpected symbol" , chain[i] );
}
}
} else {
current[ STR_PUSH ]( item );
}
}
}
return root;
}
// Parse a string item
function parseStringItem( string , context , thread ) {
var done,
func,
data = {},
id = 0,
tmp;
function parseTemp( string ) {
tmp = /^ { ([0-9]+) } $/.exec( string );
return tmp ? data[ 1 * tmp[ 1 ] ] : string.replace( / { ([0-9]+) } /g , function( _ , key ) {
tmp = data[ 1 * key ];
if ( ! isString( tmp ) ) {
error( "type mismatch" , "string expected" );
}
return tmp;
} );
}
while ( ! done ) {
done = TRUE;
string = string.replace( /\$([^$()]*)\(([^$()]*)\)/g , function( _ , name , args ) {
done = FALSE;
if ( name && ! ( func = predefinedFunctors[ name ] || functor( name ) ) ) {
error( "unknown functor" , name );
}
args = parseTemp( args );
if ( isString( args ) ) {
args = parse ( args , context , thread );
}
data[ ++ id ] = name ? func[ STR_CALL ]( context , args , thread ) : property( args );
if ( isString( data[ id ] ) ) {
data[ id ] = parse( data[ id ] , context , thread );
}
return " { " + id + " } ";
});
}
return parseTemp( string );
}
// Parse a string
function parse( string , context , thread ) {
var parsed;
if ( R_DELIM.test( string ) ) {
parsed = parseChain( string );
} else if ( parsed = context[ string ] || rule( string ) ) {
parsed = isString( parsed ) ? parse( parsed , context , thread ) : parsed;
} else {
parsed = parseStringItem( string , context , thread );
}
return parsed;
}
function loader( loadFunction ) {
var loaded = {},
loading = {};
return function( options , callback ) {
var _options = {},
callbacks,
url = options[ STR_URL ],
key;
if ( options[ STR_CACHE ] === FALSE ) {
for ( key in options ) {
_options[ key ] = options[ key ];
}
options = _options;
options[ STR_URL ] += ( /\?/.test( url ) ? "&" : "?" ) + "_=" + ( new Date() ).getTime();
loadFunction( options , callback );
} else if ( loaded[ url ] ) {
callback();
} else if ( callbacks = loading[ url ] ) {
callbacks[ STR_PUSH ]( callback );
} else {
loading[ url ] = callbacks = [ callback ];
loadFunction( options , function() {
while( callbacks[ STR_LENGTH ] ) {
( callbacks.shift() )();
}
delete loading[ url ];
loaded[ url ] = TRUE;
} );
}
};
}
function accumulator( functor ) {
var callbacks = {},
launched = FALSE;
return loader ( function( options , callback ) {
callbacks[ options[ STR_URL ] ] = callback;
if ( ! launched ) {
launched = TRUE;
later( function() {
var array = [],
string,
_callbacks = callbacks;
callbacks = {};
launched = FALSE;
for ( string in _callbacks ) {
array[ STR_PUSH ]( string );
}
execute( functor( array ) , {} , {} , function() {
for ( string in _callbacks ) {
_callbacks[ string ]();
}
} );
} );
}
} );
}
var loadScript = loader( function ( options , callback ) {
var script = document[ STR_CREATE_ELEMENT ]( "script" ),
readyState;
script[ STR_ASYNC ] = STR_ASYNC;
if ( options[ STR_CHARSET ] ) {
script[ STR_CHARSET ] = options[ STR_CHARSET ];
}
script.src = options[ STR_URL ];
// Attach handlers for all browsers
script[ STR_ON_LOAD ] = script[ STR_ON_READY_STATE_CHANGE ] = function() {
if ( ! ( readyState = script[ STR_READY_STATE ] ) || loadedCompleteRegExp.test( readyState ) ) {
// Handle memory leak in IE
script[ STR_ON_LOAD ] = script[ STR_ON_READY_STATE_CHANGE ] = NULL;
head.removeChild( script );
if ( callback ) {
// Give time for execution (thank you so much, Opera devs!)
later( callback );
}
}
};
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (jQuery #2709 and #4378).
head.insertBefore( script, head.firstChild );
} );
var loadStyleSheet = loader( function( options , callback ) {
var link = document[ STR_CREATE_ELEMENT ]( "link" ),
title = options.title;
link.rel = "stylesheet";
link.type = "text/css";
link.media = options.media || "screen";
link[ STR_HREF ] = options[ STR_URL ];
if ( options[ STR_CHARSET ] ) {
link[ STR_CHARSET ] = options[ STR_CHARSET ];
}
// Watch the link
cssPoll( link , function() {
if ( title ) {
link.title = title;
}
callback();
} );
// Add it to the doc
head.appendChild( link );
} ),
// Number of css being polled
cssPollingNb = 0,
// Polled css callbacks
cssCallbacks = {},
// Main poller function
cssPollFunction = function () {
var callback,
stylesheet,
stylesheets = document.styleSheets,
href,
i = stylesheets[ STR_LENGTH ];
while ( i-- ) {
stylesheet = stylesheets[ i ];
if ( ( href = stylesheet[ STR_HREF ] )
&& ( callback = cssCallbacks[ href ] ) ) {
try {
// We store so that minifiers don't remove the code
callback.r = stylesheet.cssRules;
// Webkit:
// Webkit browsers don't create the stylesheet object
// before the link has been loaded.
// When requesting rules for crossDomain links
// they simply return nothing (no exception thrown)
// Gecko:
// NS_ERROR_DOM_INVALID_ACCESS_ERR thrown if the stylesheet is not loaded
// If the stylesheet is loaded:
// * no error thrown for same-domain
// * NS_ERROR_DOM_SECURITY_ERR thrown for cross-domain
throw "SECURITY";
} catch(e) {
// Gecko: catch NS_ERROR_DOM_SECURITY_ERR
// Webkit: catch SECURITY
if ( /SECURITY/.test( e ) ) {
later( callback );
delete cssCallbacks[ href ];
if ( ! --cssPollingNb ) {
return FALSE;
}
}
}
}
}
},
// Poll / Unpoll
cssPoll = function ( link , callback ) {
// onreadystatechange
if ( link[ STR_READY_STATE ] ) {
link[ STR_ON_READY_STATE_CHANGE ] = function() {
if ( loadedCompleteRegExp.test( link[ STR_READY_STATE ] ) ) {
link[ STR_ON_READY_STATE_CHANGE ] = NULL;
callback();
}
};
// If onload is available, use it
} else if ( link[ STR_ON_LOAD ] === NULL /* exclude Webkit => */ && link.all ) {
link[ STR_ON_LOAD ] = function() {
link[ STR_ON_LOAD ] = NULL;
callback();
}
// In any other browser, we poll
} else {
cssCallbacks[ link[ STR_HREF ] ] = callback;
if ( ! cssPollingNb++ ) {
poll( cssPollFunction );
}
}
};
// Create the associated predefined functor
predefinedFunctor( "css" , "O" , loadStyleSheet );
// EXPOSE
window[ STR_DOMINOES ] = dominoes;
})[ STR_APPLY ](
window ,
"async cache call chain charset createElement getElementsByTagName href length onload onreadystatechange + push readyState url".split( " " )
);
})(
window ,
document ,
!0 ,
!1 ,
null,
"apply",
"dominoes"
);