d3
Version:
A small, free JavaScript library for manipulating documents based on data.
1,590 lines (1,439 loc) • 94 kB
JavaScript
/*
* Envjs core-env.1.3.pre03
* Pure JavaScript Browser Environment
* By John Resig <http://ejohn.org/> and the Envjs Team
* Copyright 2008-2010 John Resig, under the MIT License
*/
Envjs = exports.Envjs = function(){
var i,
override = function(){
for(i=0;i<arguments.length;i++){
for ( var name in arguments[i] ) {
if(arguments[i].hasOwnProperty(name)){
var g = arguments[i].__lookupGetter__(name),
s = arguments[i].__lookupSetter__(name);
if ( g || s ) {
if ( g ) { Envjs.__defineGetter__(name, g); }
if ( s ) { Envjs.__defineSetter__(name, s); }
} else {
Envjs[name] = arguments[i][name];
}
}
}
}
};
if(arguments.length === 1 && typeof(arguments[0]) == 'string'){
window.location = arguments[0];
}else if (arguments.length === 1 && typeof(arguments[0]) == "object"){
override(arguments[0]);
}else if(arguments.length === 2 && typeof(arguments[0]) == 'string'){
override(arguments[1]);
window.location = arguments[0];
}
return;
};
var log = {
debug: function(){return this;},
info: function(){return this;},
warn: function(){return this;},
error: function(){return this;},
exception: function(){return this;}
};
if (typeof console == "undefined") {
console = require('./console').console;
}
//eg "Mozilla"
Envjs.appCodeName = "Envjs";
//eg "Gecko/20070309 Firefox/2.0.0.3"
Envjs.appName = "Netscape";
Envjs.version = "1.618";//
Envjs.revision = '';
Envjs.exit = function(){};
/*
* Envjs core-env.1.3.pre03
* Pure JavaScript Browser Environment
* By John Resig <http://ejohn.org/> and the Envjs Team
* Copyright 2008-2010 John Resig, under the MIT License
*/
//CLOSURE_START
(function(){
/**
* @author john resig
*/
// Helper method for extending one object with another.
function __extend__(a,b) {
for ( var i in b ) {
if(b.hasOwnProperty(i)){
var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
if ( g || s ) {
if ( g ) { a.__defineGetter__(i, g); }
if ( s ) { a.__defineSetter__(i, s); }
} else {
a[i] = b[i];
}
}
}
return a;
}
/**
* @author ariel flesler
* http://flesler.blogspot.com/2008/11/fast-trim-function-for-javascript.html
* @param {Object} str
*/
function __trim__( str ){
return (str || "").replace( /^\s+|\s+$/g, "" );
}
/**
* Borrowed with love from:
*
* jQuery AOP - jQuery plugin to add features of aspect-oriented programming (AOP) to jQuery.
* http://jquery-aop.googlecode.com/
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Version: 1.1
*/
(function(){
var _after = 1;
var _before = 2;
var _around = 3;
var _intro = 4;
var _regexEnabled = true;
/**
* Private weaving function.
*/
var weaveOne = function(source, method, advice) {
var old = source[method];
var aspect;
if (advice.type == _after){
aspect = function() {
var returnValue = old.apply(this, arguments);
return advice.value.apply(this, [returnValue, method]);
};
}else if (advice.type == _before){
aspect = function() {
advice.value.apply(this, [arguments, method]);
return old.apply(this, arguments);
};
}else if (advice.type == _intro){
aspect = function() {
return advice.value.apply(this, arguments);
};
}else if (advice.type == _around) {
aspect = function() {
var invocation = { object: this, args: arguments };
return advice.value.apply(invocation.object, [{ 'arguments': invocation.args, method: method, proceed :
function() {
return old.apply(invocation.object, invocation.args);
}
}] );
};
}
aspect.unweave = function() {
source[method] = old;
source = aspect = old = null;
};
source[method] = aspect;
return aspect;
};
/**
* Private weaver and pointcut parser.
*/
var weave = function(pointcut, advice)
{
var source = (typeof(pointcut.target.prototype) != 'undefined') ? pointcut.target.prototype : pointcut.target;
var advices = [];
// If it's not an introduction and no method was found, try with regex...
if (advice.type != _intro && typeof(source[pointcut.method]) == 'undefined')
{
for (var method in source)
{
if (source[method] && source[method] instanceof Function && method.match(pointcut.method))
{
advices[advices.length] = weaveOne(source, method, advice);
}
}
if (advices.length === 0){
throw 'No method: ' + pointcut.method;
}
}
else
{
// Return as an array of one element
advices[0] = weaveOne(source, pointcut.method, advice);
}
return _regexEnabled ? advices : advices[0];
};
exports.Aspect = Aspect =
{
/**
* Creates an advice after the defined point-cut. The advice will be executed after the point-cut method
* has completed execution successfully, and will receive one parameter with the result of the execution.
* This function returns an array of weaved aspects (Function).
*
* @example jQuery.aop.after( {target: window, method: 'MyGlobalMethod'}, function(result) { alert('Returned: ' + result); } );
* @result Array<Function>
*
* @example jQuery.aop.after( {target: String, method: 'indexOf'}, function(index) { alert('Result found at: ' + index + ' on:' + this); } );
* @result Array<Function>
*
* @name after
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called after the execution of the point-cut. It receives one parameter
* with the result of the point-cut's execution.
*
* @type Array<Function>
* @cat Plugins/General
*/
after : function(pointcut, advice)
{
return weave( pointcut, { type: _after, value: advice } );
},
/**
* Creates an advice before the defined point-cut. The advice will be executed before the point-cut method
* but cannot modify the behavior of the method, or prevent its execution.
* This function returns an array of weaved aspects (Function).
*
* @example jQuery.aop.before( {target: window, method: 'MyGlobalMethod'}, function() { alert('About to execute MyGlobalMethod'); } );
* @result Array<Function>
*
* @example jQuery.aop.before( {target: String, method: 'indexOf'}, function(index) { alert('About to execute String.indexOf on: ' + this); } );
* @result Array<Function>
*
* @name before
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called before the execution of the point-cut.
*
* @type Array<Function>
* @cat Plugins/General
*/
before : function(pointcut, advice)
{
return weave( pointcut, { type: _before, value: advice } );
},
/**
* Creates an advice 'around' the defined point-cut. This type of advice can control the point-cut method execution by calling
* the functions '.proceed()' on the 'invocation' object, and also, can modify the arguments collection before sending them to the function call.
* This function returns an array of weaved aspects (Function).
*
* @example jQuery.aop.around( {target: window, method: 'MyGlobalMethod'}, function(invocation) {
* alert('# of Arguments: ' + invocation.arguments.length);
* return invocation.proceed();
* } );
* @result Array<Function>
*
* @example jQuery.aop.around( {target: String, method: 'indexOf'}, function(invocation) {
* alert('Searching: ' + invocation.arguments[0] + ' on: ' + this);
* return invocation.proceed();
* } );
* @result Array<Function>
*
* @example jQuery.aop.around( {target: window, method: /Get(\d+)/}, function(invocation) {
* alert('Executing ' + invocation.method);
* return invocation.proceed();
* } );
* @desc Matches all global methods starting with 'Get' and followed by a number.
* @result Array<Function>
*
*
* @name around
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called around the execution of the point-cut. This advice will be called with one
* argument containing one function '.proceed()', the collection of arguments '.arguments', and the matched method name '.method'.
*
* @type Array<Function>
* @cat Plugins/General
*/
around : function(pointcut, advice)
{
return weave( pointcut, { type: _around, value: advice } );
},
/**
* Creates an introduction on the defined point-cut. This type of advice replaces any existing methods with the same
* name. To restore them, just unweave it.
* This function returns an array with only one weaved aspect (Function).
*
* @example jQuery.aop.introduction( {target: window, method: 'MyGlobalMethod'}, function(result) { alert('Returned: ' + result); } );
* @result Array<Function>
*
* @example jQuery.aop.introduction( {target: String, method: 'log'}, function() { alert('Console: ' + this); } );
* @result Array<Function>
*
* @name introduction
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved.
* @param Function advice Function containing the code that will be executed on the point-cut.
*
* @type Array<Function>
* @cat Plugins/General
*/
introduction : function(pointcut, advice)
{
return weave( pointcut, { type: _intro, value: advice } );
},
/**
* Configures global options.
*
* @name setup
* @param Map settings Configuration options.
* @option Boolean regexMatch Enables/disables regex matching of method names.
*
* @example jQuery.aop.setup( { regexMatch: false } );
* @desc Disable regex matching.
*
* @type Void
* @cat Plugins/General
*/
setup: function(settings)
{
_regexEnabled = settings.regexMatch;
}
};
}(/*Aspect*/));
(function(){
Envjs.Configuration = {
/** Please see each module for specific configuration options */
//this is a short list of well knowns, but can always be extended
logging:[
{category:'Envjs.Core', level:'WARN'},
{category:'Envjs.Core.REPL', level:'WARN'},
{category:'Envjs.DOM', level:'WARN'},
{category:'Envjs.DOM.Node', level:'WARN'},
{category:'Envjs.DOM.NodeList', level:'WARN'},
{category:'Envjs.DOM.NamedNodeMap', level:'WARN'},
{category:'Envjs.DOM.Element', level:'WARN'},
{category:'Envjs.DOM.Document', level:'WARN'},
{category:'Envjs.DOM.EventTarget', level:'WARN'},
{category:'Envjs.Timer', level:'WARN'},
{category:'Envjs.Location', level:'WARN'},
{category:'Envjs.XMLHttpRequest', level:'WARN'},
{category:'Envjs.Parser', level:'WARN'},
{category:'Envjs.Parser.HTMLParser', level:'WARN'},
{category:'Envjs.Parser.XMLParser', level:'WARN'},
{category:'Envjs.HTML.FrameElement', level:'WARN'},
{category:'Envjs.Window', level:'WARN'},
{category:'Envjs.Platform', level:'WARN'},
{category:'root', level:'WARN'}
],
env : {
dev:{},
prod:{},
test:{}
}
};
Envjs.config = function(){
var config, subconfig;
if(arguments.length === 0){
return Envjs.Configuration;
}else if(arguments.length === 1 && typeof(arguments[0]) == "string"){
return Envjs.Configuration[arguments[0]];
}else{
Envjs.Configuration[arguments[0]] = arguments[1];
}
return this;//chain
};
var $guid = 0;
Envjs.guid = function(){
return ++$guid;
};
}(/*Envjs.Configuration*/));
/**
* Writes message to system out
* @param {String} message
*/
Envjs.log = function(message){};
/**
* Prints stack trace of current execution point
*/
Envjs.trace = function(){};
/**
* Writes error info out to console
* @param {Error} e
*/
Envjs.lineSource = function(e){};
/**
* Provides prompt to system stdout
* @param {Error} e
*/
Envjs.prompt = function(e){};
/**
* Reads line from system stdin
* @param {Error} e
*/
Envjs.readConsole = function(e){};
/**
* Starts a read-eval-print-loop
*/
Envjs.repl = function(){
var line,
waiting,
$_ = null;
var log = Envjs.logger('Envjs.Core.REPL');
Envjs.on('tick', function(){
log.debug('tick for REPL');
if(!waiting){
log.debug('not waiting in REPL');
waiting = true;
Envjs.prompt();
log.debug('waiting for line in');
Envjs.spawn(function(){
log.info('async waiting for line in');
line = Envjs.readConsole();
if(line === "quit" || line === "exit") {
log.info('%sting', line);
Envjs.emit('exit', 'repl');
}else{
setTimeout(function(){
try{
if(line){
log.info('evaluating console line in: %s', line);
$_ = Envjs['eval'](__this__, line);
if($_!==undefined){
log.info('result of evaluation: %s', $_);
Envjs.log( $_ );
}
}else{
log.info('not evaluating console line in: %s', line);
}
}catch(e) {
Envjs.log('[Envjs.REPL Error] ' + e);
}finally{
waiting = false;
}
},1);
}
});
}else{
log.debug('waiting in REPL');
}
});
};
Envjs.CURSOR = "envjs>";
Envjs.Logging = {};
/**
* Descibe this class
* @author
* @version $Rev$
* @requires OtherClassName
*/
(function($$Log){
var loggerFactory = null;
Envjs.logging = function(config){
return Envjs.config('logging', config);
};
Envjs.logger = function(category){
if(!category){
return new $$Log.NullLogger();
}
if(!$$Log.loggerFactory){
$$Log.loggerFactory = new $$Log.Factory();
}
if($$Log.updated){
$$Log.loggerFactory.updateConfig();
$$Log.updated = false;
}
return $$Log.loggerFactory.create(category);
};
$$Log.Level = {
DEBUG:0,
INFO:1,
WARN:2,
ERROR:3,
NONE:4
};
$$Log.NullLogger = function(){
//for speed why bother implement the interface, just null the functions
var nullFunction = function(){
return this;
};
__extend__(this, {
debug:nullFunction,
info:nullFunction,
warn:nullFunction,
error:nullFunction,
exception:nullFunction
});
};
//leaked globally intentionally
log = new $$Log.NullLogger();
$$Log.Logger = function(options){
this.category = "root";
this.level = null;
__extend__(this, options);
try{
this.level = $$Log.Level[
this.level?this.level:"NONE"
];
this.appender = new $$Log.ConsoleAppender(options);
return this;
}catch(e){
return new $$Log.NullLogger();
}
};
//All logging calls are chainable
__extend__($$Log.Logger.prototype, {
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
debug: function(){
if(this.level<=$$Log.Level.DEBUG){
this.appender.append("DEBUG",this.category,arguments);
return this;
}else{
this.debug = function(){
return this;
};
}
return this;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
info: function(){
if(this.level<=$$Log.Level.INFO){
this.appender.append("INFO",this.category,arguments);
return this;
}else{
this.info = function(){
return this;
};
}
return this;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
warn: function(){
if(this.level<=$$Log.Level.WARN){
this.appender.append("WARN",this.category,arguments);
return this;
}else{ this.warn = function(){return this;}; }
return this;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
error: function(){
if(this.level<=$$Log.Level.ERROR){
this.appender.append("ERROR",this.category,arguments);
return this;
}else{ this.error = function(){return this;}; }
return this;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
exception: function(e){
if(this.level < $$Log.Level.NONE){
if(e){
this.appender.append("EXCEPTION", this.category,e);
return this;
}
}else{
this.exception = function(){
return this;
};
}
return this;
}
});
/**
* @constructor
*/
$$Log.ConsoleAppender = function(options){
__extend__(this,options);
try{
this.formatter = new $$Log.FireBugFormatter(options);
return this;
}catch(e){
//Since the console and print arent available use a null implementation.
throw e;
}
return this;
};
__extend__( $$Log.ConsoleAppender.prototype, {
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
append: function(level, category, message){
switch(level){
case ("DEBUG"):
console.log.apply(console, this.formatter.format(level, category, message));
break;
case ("INFO"):
console.info.apply(console, this.formatter.format(level, category, message));
break;
case ("WARN"):
console.warn.apply(console, this.formatter.format(level, category, message));
break;
case ("ERROR"):
console.error.apply(console,this.formatter.format(level, category, message));
break;
case ("EXCEPTION"):
//message is e
console.error.apply(console, this.formatter.format(level, category,
message.message?[message.message]:[]));
console.trace();
break;
}
}
});
$$Log.FireBugFormatter = function(options){
__extend__(this, options);
};
__extend__($$Log.FireBugFormatter.prototype, {
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
getDateString: function(){
return " ["+ new Date().toUTCString() +"] ";
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
format: function(level, category, objects){
var msgPrefix = category + " "+level+": "+ this.getDateString();
objects = (objects&&objects.length&&(objects.length>0))?objects:[];
var msgFormat = (objects.length > 0)?objects[0]:null;
if (typeof(msgFormat) != "string"){
objects.unshift(msgPrefix);
}else{
objects[0] = msgPrefix + msgFormat;
}
return objects;
}
});
$$Log.Factory = function(options){
__extend__(this,options);
this.configurationId = 'logging';
//The LogFactory is unique in that it will create its own logger
//so that it's events can be logged to console or screen in a
//standard way.
this.logger = new $$Log.Logger({
category:"Envjs.Logging.Factory",
level:"INFO",
appender:"Envjs.Logging.ConsoleAppender"
});
this.attemptedConfigure = false;
this.clear();
this.updateConfig();
};
__extend__($$Log.Factory.prototype, {
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
clear: function(){
this.logger.debug("Clearing Cache.");
this.cache = null;
this.cache = {};
this.size = 0;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
add: function(id, object){
this.logger.debug("Adding To Cache: %s", id);
if ( !this.cache[id] ){
this.cache[id] = object;
this.size++;
return id;
}
return null;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
remove: function(id){
this.logger.debug("Removing From Cache id: %s", id);
if(this.find(id)){
return (delete this.cache[id])?--this.size:-1;
}return null;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
find: function(id){
this.logger.debug("Searching Cache for id: %s", id);
return this.cache[id] || null;
},
/**
* returns the portion configuration specified by 'configurationId'
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
getConfig: function(){
if( !this.configuration ){
//First look for an object name Envjs.Configuration
this.logger.debug( "Configuration for <%s> has not been set explicitly or has been updated implicitly.", this.configurationId );
try{
if(Envjs.Configuration[this.configurationId]){
this.logger.debug("Found Envjs.Configuration");
this.configuration = Envjs.Configuration[this.configurationId];
}
}catch(e){
this.logger.exception(e);
throw new Error('Logging Configuration Error');
}
}
return this.configuration;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
setConfig: function(id, configuration){
this.logger.debug("Setting configuration");
this.configuration = configuration;
Envjs.Configuration[id] = configuration;
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
create: function(category){
var categoryParts,
subcategory,
loggerConf,
rootLoggerConf,
i;
if(!this.configuration){
//Only warn about lack of configuration once
if(!this.attemptedConfigure){
this.logger.warn("Claypool Logging was not initalized correctly. Logging will not occur unless initialized.");
}
this.attemptedConfigure = true;
return new $$Log.NullLogger();
}else{
//Find the closest configured category
categoryParts = category.split(".");
for(i=0;i<categoryParts.length;i++){
subcategory = categoryParts.slice(0,categoryParts.length-i).join(".");
loggerConf = this.find(subcategory);
if(loggerConf !== null){
//The level is set by the closest subcategory, but we still want the
//full category to display when we log the messages
loggerConf.category = category;
//TODO: we need to use the formatter/appender specified in the config
return new $$Log.Logger( loggerConf );
}
}
//try the special 'root' category
rootLoggerConf = this.find('root');
this.logger.debug('root logging category is set to %s', rootLoggerConf);
if(rootLoggerConf !== null){
//The level is set by the closest subcategory, but we still want the
//full category to display when we log the messages
rootLoggerConf.category = category;
return new $$Log.Logger(rootLoggerConf);
}
}
//No matching category found
this.logger.warn("No Matching category: %s Please configure a root logger.", category);
return new $$Log.NullLogger();
},
/**
* Describe what this method does
* @private
* @param {String} paramName Describe this parameter
* @returns Describe what it returns
* @type String
*/
updateConfig: function(){
var loggingConfiguration;
var logconf;
var i;
try{
this.logger.debug("Configuring Logging");
this.clear();
loggingConfiguration = this.getConfig()||[];
for(i=0;i<loggingConfiguration.length;i++){
try{
logconf = loggingConfiguration[i];
this.add( logconf.category, logconf );
}catch(ee){
this.logger.exception(ee);
return false;
}
}
}catch(e){
this.logger.exception(e);
throw new Error('Logging configuration error');
}
return true;
}
});
Envjs.logging = function(){
if(arguments.length === 0){
return Envjs.config('logging');
}else{
Envjs.Logging.updated = true;
return Envjs.config('logging', arguments[0]);
}
};
}( Envjs.Logging ));
(function(){
/**
* The main event loop is unrelated to html/dom events
*/
var log;
var eventListeners = {
newListener:[],
tick:[],
exit:[]
},
eventQueue = [];
Envjs.eventLoop = Envjs.eventLoop || function(){
log = log||Envjs.logger('Envjs.Core');
while(true) {
Envjs.tick();
}
};
Envjs.on = function(event, callback){
if(!(event in eventListeners)){
eventListeners[event] = [];
}
eventListeners[event].push(callback);
};
Envjs.once = function(event, callback){
var once = function(){
Envjs.removeListener(event, once);
callback.apply(callback, arguments);
};
Envjs.on(event, once);
};
Envjs.removeListener = function(event, callback){
if(!(event in eventListeners)){
return;
}
var index = eventListeners[event].indexOf(callback);
if(index > -1){
eventListeners[event].splice(index, 1);
}
};
Envjs.removeAllListeners = function(event){
eventListeners[event] = [];
};
Envjs.listeners = function(event){
return (event in eventListeners) ?
eventListeners[event] : [];
};
Envjs.emit = function(event /*, arg1, arg2, etc*/ ){
eventQueue.push({event:event, args:arguments});
};
var $warming = 10;
Envjs.tick = function(){
var y, next, fn, arg, file;
log = log||Envjs.logger('Envjs.Core');
log.debug('go through %s events', eventQueue.length);
next = eventQueue.shift();
while( next ){
log.debug('next event %s', next.event);
if(next.event in eventListeners){
if('exit' === next.event){
log.info('exiting');
Envjs.exit();
}
for(y = 0; y < eventListeners[next.event].length; y++){
log.debug('event %s %s', y, next.event);
fn = eventListeners[next.event][y];
fn.apply(fn, next.args);
}
}
next = eventQueue.shift();
}
if(!$warming && Envjs.argv && Envjs.argv.length){
log.debug('parsing args %s', Envjs.argv);
arg = Envjs.argv.shift();
if(arg && typeof(arg) == 'string'){
file = arg;
log.debug('will load file %s next', file);
setTimeout(function(){
log.debug('loading script %s', file);
Envjs['eval'](__this__, Envjs.readFromFile(file), file, !$warming);
},1);
}
}else if($warming === 0 && Envjs.argv.length === 0){
$warming = false;
//prevents repl from being opened twice
//Envjs.repl();
}else if($warming){
log.debug('warming');
$warming--;
}
if($warming === false && !eventQueue.length && !Envjs.connections.length && !Envjs.timers.length ){
log.debug('ready to exit warming %s eventQueue %s connections %s timers %s',
$warming,
eventQueue.length,
Envjs.connections.length,
Envjs.timers.length
);
log.info('event loop is passive, exiting');
Envjs.emit('exit');
}
Envjs.emit('tick', Date.now());
Envjs.sleep(4);
};
/**
* Used in ./event/eventtarget.js These are DOM related events
* @param {Object} event
*/
Envjs.defaultEventBehaviors = {
'submit': function(event) {
var target = event.target,
serialized,
method,
action;
while (target && target.nodeName !== 'FORM') {
target = target.parentNode;
}
if (target && target.nodeName === 'FORM') {
serialized = Envjs.serializeForm(target);
//console.log('serialized %s', serialized);
method = target.method?target.method.toUpperCase():"GET";
action = Envjs.uri(
target.action !== ""?target.action:target.ownerDocument.baseURI,
target.ownerDocument.baseURI
);
if(method=='GET' && !action.match(/^file:/)){
action = action + "?" + serialized;
}
//console.log('replacing document with form submission %s', action);
target.ownerDocument.location.replace(
action, method, serialized
);
}
},
'click': function(event) {
//console.log("handling default behavior for click %s", event.target);
var target = event.target,
url,
form,
inputs;
while (target && target.nodeName !== 'A' && target.nodeName !== 'INPUT') {
target = target.parentNode;
}
if (target && target.nodeName === 'A') {
//console.log('target is a link');
if(target.href && !target.href.match(/^#/)){
url = Envjs.uri(target.href, target.ownerDocument.baseURI);
target.ownerDocument.location.replace(url);
}
}else if (target && target.nodeName === 'INPUT') {
//console.log('input %s', target.xml);
if(target.type.toLowerCase() === 'submit'){
if(!target.value){
target.value = 'submit';
}
//console.log('submit click %s %s', target.name, target.value);
form = target.parentNode;
while (form && form.nodeName !== 'FORM' ) {
form = form.parentNode;
}
if(form && form.nodeName === 'FORM'){
//disable other submit buttons before serializing
inputs = form.getElementsByTagName('input');
for(var i=0;i<inputs.length;i++){
//console.log('checking for non-relevant submit buttons %s', inputs[i].name);
if(inputs[i].type == 'submit' && inputs[i]!=target){
//console.log('disabling the non-relevant submit button %s', inputs[i].value);
inputs[i].disabled = true;
inputs[i].value = null;
}
}
form.submit();
}
}
}
}
};
}(/*Envjs.Platform.Core*/));
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.Platform.HTML').
debug('Platform Core HTML API available');
});
/**
* describes which script src values will trigger Envjs to load
* the script like a browser would
*/
Envjs.scriptTypes = {
"": true, //anonymous/inline
"text/javascript" :true,
"text/envjs" :true
};
/**
* will be called when loading a script throws an error
* @param {Object} script
* @param {Object} e
*/
Envjs.onScriptLoadError = function(script, e){
log.error('error loading script %s %s', script, e);
};
/**
* load and execute script tag text content
* @param {Object} script
*/
Envjs.loadInlineScript = function(script){
if(script.ownerDocument.ownerWindow){
log.debug('evaulating inline in window %s', script.ownerDocument.ownerWindow);
Envjs['eval'](
script.ownerDocument.ownerWindow,
script.text,
'eval('+script.text.substring(0,16)+'...):'+new Date().getTime()
);
}else{
log.debug('evaulating inline in global %s', __this__);
Envjs['eval'](
__this__,
script.text,
'eval('+script.text.substring(0,16)+'...):'+new Date().getTime()
);
}
//console.log('evaluated at scope %s \n%s',
// script.ownerDocument.ownerWindow.guid, script.text);
};
/**
* Should evaluate script in some context
* @param {Object} context
* @param {Object} source
* @param {Object} name
*/
Envjs['eval'] = function(context, source, name){};
/**
* Executes a script tag
* @param {Object} script
* @param {Object} parser
*/
Envjs.loadLocalScript = function(script){
var types,
src,
i,
base,
filename,
xhr,
brief = script.src||script.text.substring(0,64);
log.debug("loading script type %s : %s", script.type, brief);
if(script.type){
types = script.type.split(";");
for(i=0;i<types.length;i++){
if(Envjs.scriptTypes[types[i].toLowerCase()]){
//ok this script type is allowed
break;
}
if(i+1 == types.length){
log.debug('wont load script type %s', script.type);
return false;
}
}
}else if(!Envjs.scriptTypes['']){
log.debug('wont load anonymous script type ""');
return false;
}
try{
if(!script.src.length ){
if(Envjs.scriptTypes[""]){
log.debug('handling inline scripts %s %s', script.src, Envjs.scriptTypes[""] );
Envjs.loadInlineScript(script);
return true;
}else{
return false;
}
}
}catch(e){
log.error("Error loading script. %s", e);
Envjs.onScriptLoadError(script, e);
return false;
}
log.debug("loading allowed external script %s", script.src);
//lets you register a function to execute
//before the script is loaded
if(Envjs.beforeScriptLoad){
for(src in Envjs.beforeScriptLoad){
if(script.src.match(src)){
Envjs.beforeScriptLoad[src](script);
}
}
}
base = "" + script.ownerDocument.location;
//filename = Envjs.uri(script.src.match(/([^\?#]*)/)[1], base );
log.debug('loading script from base %s', base);
filename = Envjs.uri(script.src, base);
try {
xhr = new XMLHttpRequest();
xhr.open("GET", filename, false/*syncronous*/);
log.debug("loading external script %s", filename);
xhr.onreadystatechange = function(){
log.debug("readyState %s", xhr.readyState);
if(xhr.readyState === 4){
Envjs['eval'](
script.ownerDocument.ownerWindow,
xhr.responseText,
filename
);
}
log.debug('finished evaluating %s', filename);
};
xhr.send(null, false);
} catch(ee) {
log.error("could not load script %s \n %s", filename, ee );
Envjs.onScriptLoadError(script, ee);
return false;
}
//lets you register a function to execute
//after the script is loaded
if(Envjs.afterScriptLoad){
for(src in Envjs.afterScriptLoad){
if(script.src.match(src)){
Envjs.afterScriptLoad[src](script);
}
}
}
return true;
};
/**
* An 'image' was requested by the document.
*
* - During inital parse of a <link>
* - Via an innerHTML parse of a <link>
* - A modificiation of the 'src' attribute of an Image/HTMLImageElement
*
* NOTE: this is optional API. If this doesn't exist then the default
* 'loaded' event occurs.
*
* @param node {Object} the <img> node
* @param node the src value
* @return 'true' to indicate the 'load' succeed, false otherwise
*/
Envjs.loadImage = function(node, src) {
return true;
};
Envjs.loadImage = function(node, value) {
var event;
var owner = node.ownerDocument;
if (value) {
// value has to be something (easy)
// if the user-land API doesn't exist
// Or if the API exists and it returns true, then ok:
event = owner.createEvent('Events');
event.initEvent('load');
} else {
// oops
event = owner.createEvent('Events');
event.initEvent('error');
}
node.dispatchEvent(event, false);
};
/**
* A 'link' was requested by the document. Typically this occurs when:
* - During inital parse of a <link>
* - Via an innerHTML parse of a <link>
* - A modificiation of the 'href' attribute on a <link> node in the tree
*
* @param node {Object} is the link node in question
* @param href {String} is the href.
*
* Return 'true' to indicate that the 'load' was successful, or false
* otherwise. The appropriate event is then triggered.
*
* NOTE: this is optional API. If this doesn't exist then the default
* 'loaded' event occurs
*/
Envjs.loadLink = function(node, value) {
var event;
var owner = node.ownerDocument;
if (owner.fragment) {
/**
* if we are in an innerHTML fragment parsing step
* then ignore. It will be handled once the fragment is
* added to the real doco
*/
return;
}
if (node.parentNode === null) {
/*
* if a <link> is parentless (normally by create a new link
* via document.createElement('link'), then do *not* fire an
* event, even if it has a valid 'href' attribute.
*/
return;
}
if (value != '') {
// value has to be something (easy)
// if the user-land API doesn't exist
// Or if the API exists and it returns true, then ok:
event = owner.createEvent('Events');
event.initEvent('load');
} else {
// oops
event = owner.createEvent('Events');
event.initEvent('error');
}
node.dispatchEvent(event, false);
};
var HTMLParser;
Envjs.exchangeHTMLDocument = function(doc, text, url) {
var html, head, title, body,
event,
frame = doc.__ownerFrame__,
i;
try {
HTMLParser = HTMLParser || require('./../parser').HTMLParser;
//do some cleanup so we can reuse the document
doc.baseURI = url;
log.debug('reseting document indexes');
doc._indexes_ = {
'ancestors' : new NodeList(doc, doc),
'*': new NodeList(doc, doc)
};
//TODO: we should remove the events for the document but
// its so nice to setup a load handler before calling
// document.location
//doc.removeEventListener('*');
log.debug('parsing document for window exchange %s', url);
HTMLParser.parseDocument(text, doc);
log.debug('finsihed parsing document for window exchange %s', url);
//if this document is inside a frame make sure to trigger
//a new load event on the frame
if(frame){
event = doc.createEvent('HTMLEvents');
event.initEvent('load', false, false);
frame.dispatchEvent( event, false );
}
} catch (e) {
log.warn('parsererror %s', e);
try {
log.debug('document before error\n %s', doc.documentElement.outerHTML);
} catch (ee) {
// swallow
}
doc = new HTMLDocument(new DOMImplementation(), doc.ownerWindow);
html = doc.createElement('html');
head = doc.createElement('head');
title = doc.createElement('title');
body = doc.createElement('body');
title.appendChild(doc.createTextNode('Error'));
body.appendChild(doc.createTextNode('' + e));
head.appendChild(title);
html.appendChild(head);
html.appendChild(body);
doc.appendChild(html);
log.debug('default error document \n %s', doc.documentElement.outerHTML);
//DOMContentLoaded event
if (doc.createEvent) {
event = doc.createEvent('Event');
event.initEvent('DOMContentLoaded', false, false);
doc.dispatchEvent( event, false );
event = doc.createEvent('HTMLEvents');
event.initEvent('load', false, false);
doc.dispatchEvent( event, false );
}
//finally fire the window.onload event
//TODO: this belongs in window.js which is a event
// event handler for DOMContentLoaded on document
try {
if (doc === window.document) {
console.log('triggering window.load');
event = doc.createEvent('HTMLEvents');
event.initEvent('load', false, false);
window.dispatchEvent( event, false );
}
} catch (eee) {
log.debug('window load event failed %s', eee);
//swallow
}
}
};
(function(){
/*
* cookie handling
* Private internal helper class used to save/retreive cookies
*/
/**
* Specifies the location of the cookie file
*/
Envjs.cookieFile = function(){
return 'file://'+Envjs.homedir+'/.cookies';
};
/**
* saves cookies to a local file
* @param {Object} htmldoc
*/
Envjs.saveCookies = function(){
var cookiejson = JSON.stringify(Envjs.cookies.persistent,null,'\t');
log.debug('persisting cookies %s', cookiejson);
Envjs.writeToFile(cookiejson, Envjs.cookieFile());
};
/**
* loads cookies from a local file
* @param {Object} htmldoc
*/
Envjs.loadCookies = function(){
var cookiejson,
js,
file;
try{
file = Envjs.cookieFile();
log.debug('load cookies %s', file);
cookiejson = Envjs.readFromFile(file);
log.debug('cookies json %s', cookiejson);
js = JSON.parse(cookiejson, null, '\t');
}catch(e){
log.debug('failed to load cookies %s', e);
js = {};
}
return js;
};
Envjs.cookies = {
persistent:{
//domain - key on domain name {
//path - key on path {
//name - key on name {
//value : cookie value
//other cookie properties
//}
//}
//}
//expire - provides a timestamp for expiring the cookie
//cookie - the cookie!
},
temporary:{//transient is a reserved word :(
//like above
}
};
var __cookies__;
function __domainValid__(url, value){
var i,
domainParts = url.hostname.split('.').reverse(),
newDomainParts = value.split('.').reverse();
if(newDomainParts.length > 1){
for(i=0;i<newDomainParts.length;i++){
if(newDomainParts[i] !== domainParts[i]){
return false;
}
}
return true;
}
return false;
}
function __mergeCookie__(target, cookie, properties){
var name, now;
if(!target[cookie.domain]){
target[cookie.domain] = {};
}
if(!target[cookie.domain][cookie.path]){
target[cookie.domain][cookie.path] = {};
}
for(name in properties){
if(properties.hasOwnProperty(name)){
now = new Date().getTime();
target[cookie.domain][cookie.path][name] = {
"value":properties[name],
"secure":cookie.secure,
"max-age":cookie['max-age'],
"date-created":now,
"expiration":(cookie['max-age']===0) ?
0 :
now + cookie['max-age']
};
}
//console.log('cookie is %o',target[cookie.domain][cookie.path][name]);
}
}
//HTMLDocument cookie
Envjs.setCookie = function(url, cookie){
var i,
index,
name,
value,
properties = {},
attr,
attrs;
url = Envjs.urlsplit(url);
if(cookie){
attrs = cookie.split(";");
}else{
return;
}
//for now the strategy is to simply create a json object
//and post it to a file in the .cookies.js file. I hate parsing
//dates so I decided not to implement support for 'expires'
//(which is deprecated) and instead focus on the easier 'max-age'
//(which succeeds 'expires')
cookie = {};//keyword properties of the cookie
cookie.domain = url.hostname;
cookie.path = url.path||'/';
for(i=0;i<attrs.length;i++){
index = attrs[i].indexOf("=");
if(index > -1){
name = __trim__(attrs[i].slice(0,index));
value = __trim__(attrs[i].slice(index+1));
if(name.toLowerCase() == 'max-age'){
//we'll have to when to check these
//and garbage collect expired cookies
cookie[name] = parseInt(value, 10);
} else if( name.toLowerCase() == 'domain' ){