UNPKG

d3

Version:

A small, free JavaScript library for manipulating documents based on data.

1,590 lines (1,439 loc) 94 kB
/* * 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' ){