talho/openphin

View on GitHub
app/assets/javascripts/lib/dominoes-1.0-rc2.js

Summary

Maintainability
F
3 days
Test Coverage
/**
 * 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"
);