(function(){ // Gets the window (even if there is none) var win = (function(){return this}).call(null), // String constants (for better minification) STR_ONLOAD = "onload", STR_ONERROR = "onerror", STR_ONREADYSTATECHANGE = "onreadystatechange", STR_CREATE_ELEMENT = 'createElement', STR_GET_BY_TAG = 'getElementsByTagName', // the document ( might not exist in rhino ) doc = win.document, // creates a script tag scriptTag = function() { var start = doc[STR_CREATE_ELEMENT]('script'); start.type = 'text/javascript'; return start; }, // a function that returns the head element // creates and caches the lookup if necessary head = function() { var d = doc, de = d.documentElement, heads = d[STR_GET_BY_TAG]("head"), hd = heads[0]; if (! hd ) { hd = d[STR_CREATE_ELEMENT]('head'); de.insertBefore(hd, de.firstChild); } // replace head so it runs fast next time. head = function(){ return hd; } return hd; }, // extends one object with another extend = function( d, s ) { for ( var p in s ) { d[p] = s[p]; } return d; }, // a jQuery-like $.each each = function(arr, cb){ for(var i =0, len = arr.length; i steal/steal.js and add * the path to the first file to load in the query string like: * * <script type='text/javascript' * src='../steal/steal.js?myapp/myapp.js'> * </script> * * Then, start loading things and using them like: * * steal('myapp/tabs.js', * 'myapp/slider.js', * 'myapp/style.css',function(){ * * // tabs and slider have loaded * $('#tabs').tabs(); * $('#slider').slider() * }) * * Make sure your widgets load their dependencies too: * * // myapp/tabs.js * steal('jquery', function(){ * $.fn.tabs = function(){ * ... * } * }) * * ## Examples: * * // Loads ROOT/jquery/controller/controller.js * steal('jquery/controller') * steal('jquery/controller/controller.js') * * // Loads coffee script type and a coffee file relative to * // the current file * steal('steal/coffee').then('./mycoffee.coffee') * * // Load 2 files and dependencies in parallel and * // callback when both have completed * steal('jquery/controller','jquery/model', function(){ * // $.Controller and $.Model are available * }) * * // Loads a coffee script with a non-standard extension (cf) * // relative to the current page and instructs the build * // system to not package it (but it will still be loaded). * steal({ * src: "./foo.cf", * packaged: false, * type: "coffee" * }) * * The following is a longer walkthrough of how to install * and use steal: * * ## Adding steal to a page * * After installing StealJS (or JavaScriptMVC), * find the steal folder with * steal/steal.js. * * To use steal, add a script tag * to steal/steal.js to your * html pages. * * This walkthrough assumes you have the steal script * in public/steal/steal.js and a directory * structure like: * * @codestart text * /public * /steal * /pages * myapp.html * /myapp * myapp.js * jquery.js * jquery.ui.tabs.js * @codeend * * To use steal in public/pages/myapp.html, * add a script tag in myapp.html: * * @codestart html * <script type='text/javascript' * src='../steal/steal.js'> * </script> * @codeend * *
PRO TIP: Bottom load your scripts. It * will increase your application's percieved response time.
* * ## Loading the First Script * * Once steal has been added to your page, it's time * to load scripts. We want to load myapp.js * and have it load jquery.js and * jquery.ui.tabs.js. * * By default, steal likes your scripts * to be within in the [steal.static.root steal.root] folder. The [steal.root] the * folder contains the steal folder. In this example, * it is the public folder. * * To load myapp/myapp.js, we have two options: * * #### Add a script tag * * Add a script tag after the steal * script that 'steals' myapp.js like: * * @codestart html * <script type='text/javascript'> * steal('myapp/myapp.js') * </script> * @codeend * * #### Add the script parameter * * The most common (and shortest) way to load myapp.js * is to add the script path to the steal script's src after in the * query params. So, instead of adding a script, we change * the steal script from: * * @codestart html * <script type='text/javascript' * src='../steal/steal.js'> * </script> * @codeend * * To * * @codestart html * <script type='text/javascript' * src='../steal/steal.js?myapp/myapp.js'> * </script> * @codeend * *
PRO TIP: You can also just add * ?myapp to the query string.
* * ## Loading Scripts * * We want to load jquery.js and * jquery.ui.tabs.js into the page and then * add then create a tabs widget. First we need to load * jquery.js. * * By default, steal loads script relative to [steal.root]. To * load myapp/jquery.js we can the following to * myapp.js: * * steal('myapp/jquery.js'); * * But, we can also load relative to myapp.js like: * * steal('./jquery.js'); * * Next, we need to load jquery.ui.tabs.js. You * might expect something like: * * steal('./jquery.js','./jquery.ui.tabs.js') * * to work. But there are two problems / complications: * * - steal loads scripts in parallel and runs out of order * - jquery.ui.tabs.js depends on jQuery being loaded * * This means that steal might load jquery.ui.tabs.js * before jquery.js. But this is easily fixed. * * [steal.static.then] waits until all previous scripts have loaded and * run before loading scripts after it. We can load jquery.ui.tabs.js * after jquery.js like: * * steal('./jquery.js').then('./jquery.ui.tabs.js') * * Finally, we need to add tabs to the page after * the tabs's widget has loaded. We can add a callback function to * steal that will get called when all previous scripts have finished * loading: * * steal('./jquery.js').then('./jquery.ui.tabs.js', function($){ * $('#tabs').tabs(); * }) * * ## Other Info * * ### Exclude Code Blocks From Production * * To exclude code blocks from being included in * production builds, add the following around * the code blocks. * * //!steal-remove-start * code to be removed at build * //!steal-remove-end * * ### Lookup Paths * * By default steal loads resources relative * to [steal.static.root steal.root]. For example, the following * loads foo.js in steal.root: * * steal('foo.js'); // loads //foo.js * * This is the same as writing: * * steal('//foo.js'); * * Steal uses '//' to designate the [steal.static.root steal.root] * folder. * * To load relative to the current file, add "./" or * "../": * * steal("./bar.js","../folder/zed.js"); * * Often, scripts can be found in a folder within the same * name. For example, [jQuery.Controller $.Controller] is * in //jquery/controller/controller.js. For convience, * if steal is provided a path without an extension like: * * steal('FOLDER/PLUGIN'); * * It is the same as writing: * * steal('FOLDER/PLUGIN/PLUGIN.js') * * This means that //jquery/controller/controller.js * can be loaded like: * * steal('jquery/controller') * * ### Types * * steal can load resources other than JavaScript. * * * @constructor * * Loads resources specified by each argument. By default, resources * are loaded in parallel and run in any order. * * * @param {String|Function|Object} resource... * * Each argument specifies a resource. Resources can * be given as a: * * ### Object * * An object that specifies the loading and build * behavior of a resource. * * steal({ * src: "myfile.cf", * type: "coffee", * packaged: true, * unique: true, * ignore: false, * waits: false * }) * * The available options are: * * - __src__ {*String*} - the path to the resource. * * - __waits__ {*Boolean default=false*} - true the resource should wait * for prior steals to load and run. False if the resource should load and run in * parallel. This defaults to true for functions. * * - __unique__ {*Boolean default=true*} - true if this is a unique resource * that 'owns' this url. This is true for files, false for functions. * * - __ignore__ {*Boolean default=false*} - true if this resource should * not be built into a production file and not loaded in * production. This is great for script that should only be available * in development mode. * * - __packaged__ {*Boolean default=true*} - true if the script should be built * into the production file. false if the script should not be built * into the production file, but still loaded. This is useful for * loading 'packages'. * * - __type__ {*String default="js"*} - the type of the resource. This * is typically inferred from the src. * * ### __String__ * * Specifies src of the resource. For example: * * steal('./file.js') * * Is the same as calling: * * steal({src: './file.js'}) * * ### __Function__ * * A callback function that runs when all previous steals * have completed. * * steal('jquery', 'foo',function(){ * // jquery and foo have finished loading * // and runing * }) * * @return {steal} the steal object for chaining */ function steal() { // convert arguments into an array var args = makeArray(arguments); pending.push.apply(pending, args); // steal.after is called everytime steal is called // it kicks off loading these files steal.after(args); // return steal for chaining return steal; }; // =============================== PATHS .8 ============================ // things that matter ... // - the current file being loaded // - the location of the page // - the file /** * @class * Used for getting information out of a path * @constructor * Takes a path * @param {String} path */ steal.File = function( path ) { // if new was not used, use it. if ( this.constructor != steal.File ) { return new steal.File(path); } // save the path this.path = typeof path == 'string'? path : path.path; }; // alias steal.File to File var File = steal.File, // a reference to the current file curFile; // get and sets the current file File.cur = function(newCurFile){ if(newCurFile !== undefined){ curFile = File(newCurFile); }else{ return curFile || File(""); } }; extend(File.prototype, /** * @prototype */ { // Removes hash and querystring clean: function() { return this.path.match(/([^\?#]*)/)[1]; }, // gets the files extension ext : function(){ var match = this.clean().match(/\.([\w\d]+)$/) return match ? match[1] : ""; }, // Returns everything before the last / dir: function() { // remove any query string var cleaned = this.clean(), // get the last / last = cleaned.lastIndexOf('/'), // if there is a last /, get everything up to that point dir = (last != -1) ? cleaned.substring(0, last) : ''; // make sure we aren't left with just https/ or file:/ return /^(https?:\/|file:\/)$/.test(dir) ? cleaned : dir; }, // Returns everything after the last / filename: function() { var cleaned = this.clean(), last = cleaned.lastIndexOf('/'), filename = (last != -1) ? cleaned.substring(last+1, cleaned.length) : cleaned; return /^(https?:\/|file:\/)$/.test(filename) ? cleaned : filename; }, // Returns the domain for the current path. // Returns null if the domain is a file. domain: function() { var http = this.path.match(/^(?:https?:\/\/)([^\/]*)/); return http ? http[1] : null; }, /** * Joins a url onto a path. One way of understanding this is that your File object represents your current location, and calling join() is analogous to "cd" on a command line. * @codestart * new steal.File("d/e").join("../a/b/c"); // Yields the path "d/a/b/c" * @codeend * @param {String} url */ join: function( url ) { return File(url).joinFrom(this.path); }, /** * Returns the path of this file referenced from another url or path. * * new steal.File('a/b.c').joinFrom('/d/e')//-> /d/e/a/b.c * * @param {String} url * @param {Boolean} expand if the path should be expanded * @return {String} */ joinFrom: function( url, expand ) { var u = File(url); // if this.path is absolutely referenced if ( this.protocol() ) { //if we are absolutely referenced //try to shorten the path as much as possible: var firstDomain = this.domain(), secondDomain = u.domain(); // if domains are equal, shorten if ( firstDomain && firstDomain == secondDomain ) { return this.toReferenceFromSameDomain(url); } else { // if there is no domain or not equal, use our path return this.path; } // the path is the same as the folder the page is in } else if ( url === steal.pageUrl().dir() && !expand ) { return this.path; } else if ( this.isLocalAbsolute() ) { // we are a path like /page.js return (u.domain() ? u.protocol() + "//" + u.domain() : "" )+ this.path; } else { //we have 2 relative paths, remove folders with every ../ if ( url === '' ) { return this.path.replace(/\/$/, ''); } var urls = url.split('/'), paths = this.path.split('/'), path = paths[0]; //if we are joining from a folder like cookbook/, remove the last empty part if ( url.match(/\/$/) ) { urls.pop(); } // for each .. remove one folder while ( path == '..' && paths.length > 0 ) { // if we've emptied out, folders, just break // leaving any additional ../s if(! urls.pop() ){ break; } paths.shift(); path = paths[0]; } return urls.concat(paths).join('/'); } }, // Returns true if the file is relative to a domain or a protocol relative: function() { return this.path.match(/^(https?:|file:|\/)/) === null; }, /** * Returns the relative path between two paths with common folders. * @codestart * new steal.File('a/b/c/x/y').toReferenceFromSameDomain('a/b/c/d/e')//-> ../../x/y * @codeend * @param {Object} url * @return {String} */ toReferenceFromSameDomain: function( url ) { var parts = this.path.split('/'), other_parts = url.split('/'), result = ''; while ( parts.length > 0 && other_parts.length > 0 && parts[0] == other_parts[0] ) { parts.shift(); other_parts.shift(); } each(other_parts, function(){ result += '../'; }) return result + parts.join('/'); }, /** * Is the file on the same domain as our page. */ isCrossDomain: function() { return this.isLocalAbsolute() ? false : this.domain() != File(win.location.href).domain(); }, isLocalAbsolute: function() { return this.path.indexOf('/') === 0; }, protocol: function() { var match = this.path.match(/^(https?:|file:)/); return match && match[0]; }, /** * For a given path, a given working directory, and file location, update the path so * it points to a location relative to steal's root. * * We want everything relative to steal's root so the same app can work in multiple pages. * * ./files/a.js = steals a.js * ./files/a = a/a.js * files/a = //files/a/a.js * files/a.js = loads //files/a.js */ normalize: function() { var current = File.cur().dir(), //if you are cross domain from the page, and providing a path that doesn't have an domain path = this.path; if (/^\/\//.test(this.path)) { //if path is rooted from steal's root (DEPRECATED) path = this.path.substr(2); } else if (/^\.\//.test(this.path)) { // should be relative this.path = this.path.substr(2); path = this.joinFrom(current); this.path = "./" + this.path; } else if (/^[^\.|\/]/.test(this.path)) {} else { if (this.relative() || File.cur().isCrossDomain() && //if current file is on another domain and !this.protocol()) { //this file doesn't have a protocol path = this.joinFrom(current); } } return path; } }); var pending = [], s = steal, id = 0, steals = {}; // this is for methods on a 'steal instance'. A file can be in one of a few states: // created - the steal instance is created, but we haven't started loading it yet // this happens when thens are used // loading - (loading=true) By calling load, this will tell steal to load a file // loaded - (isLoaded=true) The file has been run, but its dependency files have been completed // complete - all of this files dependencies have loaded and completed. steal.p = { // adds a new steal and throws an error if the script doesn't load // this also checks the steals map make: function(options){ var stel = new steal.p.init(options), rootSrc = stel.options.rootSrc; if(stel.unique && rootSrc){ // the .js is b/c we are not adding that automatically until // load because we defer 'type' determination until then if(!steals[rootSrc] && ! steals[rootSrc+".js"]){ //if we haven't loaded it before steals[rootSrc] = stel; } else{ // already have this steal stel = steals[rootSrc]; // extend the old stolen file with any new options extend(stel.options, typeof options === "string" ? {} : options) } } return stel; }, init: function( options ) { this.dependencies = []; // id for debugging this.id = (++id); // if we have no options, we are the global init ... set ourselves up ... if(!options){ //global init cur ... this.options = {}; this.waits = false; this.pack = "production.js"; } //handle callback functions else if ( typeof options == 'function' ) { var path = File.cur().path; this.options = { fn : function() { //set the path .. File.cur(path); // call the function, someday soon this will be requireJS-like options(steal.send || win.jQuery || steal); }, rootSrc: path, orig: options, type: "fn" } // this has nothing to do with 'loading' options this.waits = true; this.unique = false; } else { // save the original options this.orig = options; this.options = steal.makeOptions(extend({}, typeof options == 'string' ? { src: options } : options)); this.waits = this.options.waits || false; this.unique = true; } }, complete : function(){ this.completed = true; }, /** * @hide * After the script has been loaded and run * * - check what else has been stolen, load them * - mark yourself as complete when everything is completed * - this is where all the actions is */ loaded: function(script){ var myqueue, stel, src = (script && script.src) || this.options.src, rootSrc = this.options.rootSrc; //set yourself as the current file File.cur(rootSrc); // mark yourself as 'loaded'. this.isLoaded = true; // If we are IE, get the queue from interactives // TODO move this out of this function if (support.interactive && src) { myqueue = interactives[src]; } // is there a case in IE where, this makes sense? // in other browsers, the queue of items to load is // what is in pending if(!myqueue){ myqueue = pending.slice(0); pending = []; } // if we have nothing, mark us as complete (resolve if deferred) if(!myqueue.length){ this.complete(); return; } // now we have to figure out how to wire up our pending steals var self = this, set = [], // the current joiner, initial = [], isProduction = steal.options.env == 'production', files = [], // a helper that basically does a join // when everything in arr's func method is called, // call func2 on obj whenEach = function(arr, func, obj, func2){ var big = [obj, func2]; each(arr, function(i, item){ big.unshift(item, func) }); when.apply(steal, big); }, // a helper that does the oposite of a join. When // obj's func method is called, call func2 on all items. whenThe = function(obj, func, items, func2){ each(items, function(i, item){ when(obj, func, item, func2) }) }; //now go through what you stole and hook everything up //BUT, we go through backwards each(myqueue.reverse(), function(i, item){ //in production, ignore ignored items (like steal/dev if(isProduction && item.ignore){ return; } // make a steal object stel = steal.p.make( item ); // add it as a dependency, circular are not allowed self.dependencies.unshift(stel) if(stel.waits === false){ // file // on the current files.push(stel); }else{ // function // essentially have to bind current files to call previous joiner's load // and to wait for current stel's complete if(!joiner){ // if no previous joiner, then we are at the start of a file // when they are complete, complete the file whenEach( files.concat(stel), "complete", self, "complete"); // if there was a function then files, then end, function loads all files if(files.length){ whenThe(stel,"complete", files ,"load") } } else { // function, file1, file2, file3, joiner function whenEach(files.concat(stel) , "complete", joiner, "load"); // make stel complete load files whenThe(stel,"complete", files.length ? files : [joiner] ,"load") } // the joiner is the previous thing joiner = stel; files = []; } }); // now we should be left with the starting files if(files.length){ // we have initial files // if there is a joiner, we need to load it when the initial files are complete if(joiner){ whenEach(files, "complete", joiner, "load"); } else { whenEach(files, "complete", self, "complete"); } // reverse it back and load each initial file each(files.reverse(), function(){ this.load(); }); } else if(joiner){ // we have inital function joiner.load() } else { // we had nothing self.complete(); } }, /** * Loads this steal */ load: function(returnScript) { // if we are already loading / loaded if(this.loading || this.isLoaded){ return; } this.loading = true; var self = this; // get yourself steal.require(this.options, function load_calling_loaded(script){ self.loaded(script); }, function(error, src){ win.clearTimeout && win.clearTimeout(self.completeTimeout) throw "steal.js : "+self.options.src+" not completed" }); } }; steal.p.init.prototype = steal.p; /** * @add steal */ // =============================== STATIC API =============================== var page; /** * @static */ extend(steal,{ /** * @attribute root * The location of the steal folder. */ root : File(""), /** * Gets or sets the path from the current page to * steal's (or JavaScriptMVC's) root folder. When passed a src, it sets the root folder. * Otherwise, it returns the path to the root folder. * * This is the path from which * all plugins are stolen. When you steal a plugin like steal("jquery/controller"), * the plugin path is joined with this rootUrl to create a full path * to the controller.js file. * * By default, the rootUrl is calculated from the * steal script and the window location. For example, if the * script tag looks like this: * * * * rootUrl will be set to "../../". * Setting the rootUrl can be useful if you want to have * steal.js in a different location. * * ## Example * * The following sets steal root to a different folder. * * steal.rootUrl("../../jmvc/") * * This appends "../../jmvc" to paths * loaded from [steal.static.root]. In some strange cases this might be desirable if * plugin folders are in a different location from the steal directory. * * It also sets the current url to this directory so the first calls to steal work relative to the root JMVC directory. * * @param {String} [src] a relative path from the current page to the root directory of JMVC, like ../../ * @return {String} returns the last path passed to rootUrl */ rootUrl : function(src){ if (src !== undefined) { steal.root = File(src); // set cur with the location var cleaned = steal.pageUrl(), loc = cleaned.join(src); File.cur( cleaned.toReferenceFromSameDomain(loc) ); return steal; } else { return steal.root.path; } }, extend : extend, /** * @function pageUrl * Gets or sets the location of the page using * steal. This defaults to the window's location. * * However, sometimes it is necessary to * have steal believe it is making requests from * another page. * * @param {String} [newPage] a path to the page using steal (probably the windows location) * @return {steal.File} returns the last path to a page passed to pageUrl, converted to a steal.File object */ pageUrl : function(newPage){ if(newPage){ page = File( File(newPage).clean() ); return steal; } else{ return page || File(""); } }, //gets and sets which page steal thinks it's at // TODO: make location change-able ... /** * Gets the currently running script location. * * @return String */ cur: function( file ) { if (file === undefined) { return File.cur(); } else { File.cur(file); return steal; } }, isRhino: win.load && win.readUrl && win.readFile, /** * @attribute options * Configurable options */ options : { env : 'development', // TODO: document this loadProduction : true }, /** * @hide * when a 'unique' steal gets added ... * @param {Object} stel */ add : function(stel){ steals[stel.rootSrc] = stel; }, /** * @hide * Makes options * @param {Object} options */ makeOptions : function(options){ var ext = File(options.src).ext(); if (!ext) { // if first character of path is a . or /, just load this file if (options.src.indexOf(".") == 0 || options.src.indexOf("/") == 0) { options.src = options.src + ".js" } // else, load as a plugin else { options.src = options.src + "/" + File(options.src).filename() + ".js"; } } var orig = options.src, // path relative to the current files path // this is done relative to jmvcroot normalized = steal.File(orig).normalize(), protocol = steal.File(options.src).protocol(); extend(options,{ originalSrc : options.src, rootSrc : normalized, // path from the page src : steal.root.join(normalized), // "file:" or "http:" depending on what protocol the request uses protocol: protocol || (doc? location.protocol: "file:") }); options.originalSrc = options.src; return options; }, /** * Calls steal, but waits until all previous steals * have completed loading until loading the * files passed to the arguments. */ then : function(){ var args = typeof arguments[0] == 'function' ? arguments : [function(){}].concat(makeArray( arguments ) ) return steal.apply(win, args ); }, /** * Listens to events on Steal * @param {String} event * @param {Function} listener */ bind: function(event, listener){ if(!events[event]){ events[event] = [] } var special = steal.events[event] if(special && special.add){ listener = special.add(listener); } listener && events[event].push(listener); return steal; }, one : function(event, listener){ steal.bind(event,function(){ listener.apply(this, arguments); steal.unbind(event, arguments.callee); }); return steal; }, events : {}, /** * Unbinds an event listener on steal * @param {Object} event * @param {Object} listener */ unbind : function(event, listener){ var evs = events[event] || [], i = 0; while(i < evs.length){ if(listener === evs[i]){ evs.splice(i,1); }else{ i++; } } }, trigger : function(event, arg){ var arr = events[event] || [], copy = []; // array items might be removed during each iteration (with unbind), so we iterate over a copy for(var i =0, len = arr.length; i 0) { if (css.firstChild.nodeValue !== node.nodeValue) { css.replaceChild(node, css.firstChild); } } else { css.appendChild(node); } })(doc.createTextNode(options.text)); } head().appendChild(css); } else { if( createSheet ){ // IE has a 31 sheet and 31 import per sheet limit if(cssCount == 30 || !lastSheet){ lastSheet = document.createStyleSheet(); cssCount = 0; } lastSheet.addImport(options.src); cssCount++; } else { options = options || {}; var link = doc[STR_CREATE_ELEMENT]('link'); link.rel = options.rel || "stylesheet"; link.href = options.src; link.type = 'text/css'; head().appendChild(link); } } success(); }); // Overwrite if(opts.types){ for(var type in opts.types){ steal.type(type, opts.types[type]); } } // =============================== HELPERS =============================== var factory = function() { return win.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(); }; steal. /** * Performs an XHR request * @param {Object} options * @param {Function} success * @param {Function} error */ request = function(options, success, error){ var request = new factory(), contentType = (options.contentType || "application/x-www-form-urlencoded; charset=utf-8"), clean = function(){ request = check = clean = null; }, check = function(){ if ( request.readyState === 4 ) { if ( request.status === 500 || request.status === 404 || request.status === 2 || request.status < 0 || (request.status === 0 && request.responseText === '') ) { error && error(request.status); clean(); } else { success(request.responseText); clean(); } return; } }; request.open("GET", options.src, options.async === false ? false : true); request.setRequestHeader('Content-type', contentType); if ( request.overrideMimeType ) { request.overrideMimeType(contentType); } request.onreadystatechange = function(){ check(); } try { request.send(null); } catch (e) { if (clean) { console.error(e); error && error(); clean(); } } }; // =============================== MAPPING =============================== var insertMapping = function(p){ // don't worry about // rooted paths var mapName, map; // go through mappings for(var mapName in steal.mappings){ map = steal.mappings[mapName] if(map.test.test(p)){ return p.replace(mapName, map.path); } } return p; }; File.prototype.mapJoin = function( url ){ url = insertMapping(url); return File(url).joinFrom(this.path); }; // modifies src steal.makeOptions = after(steal.makeOptions,function(raw){ raw.src = steal.root.join(raw.rootSrc = insertMapping(raw.rootSrc)); }); //root mappings to other locations steal.mappings = {}; steal. /** * Maps a 'rooted' folder to another location. * @param {String|Object} from the location you want to map from. For example: * 'foo/bar' * @param {String} [to] where you want to map this folder too. Ex: 'http://foo.cdn/bar' * @return {steal} */ map = function(from, to){ if(typeof from == "string"){ steal.mappings[from] = { test : new RegExp("^("+from+")([/.]|$)"), path: to }; } else { // its an object for(var key in from){ steal.map(key, from[key]); } } return this; } // =============================== STARTUP =============================== var currentCollection; // essentially ... we need to know when we are on our first steal // then we need to know when the collection of those steals ends ... // and, it helps if we use a 'collection' steal because of it's natural // use for going through the pending queue // extend(steal,{ // called after steals are added to the pending queue after: function(){ // if we don't have a current 'top' steal // we create one and set it up // to start loading its dependencies (the current pending steals) if(! currentCollection ){ currentCollection = new steal.p.init(); // keep a reference in case it disappears var cur = currentCollection, // runs when a steal is starting go = function(){ // indicates that a collection of steals has started steal.trigger("start", cur); when(cur,"complete", function(){ steal.trigger("end", cur); }); cur.loaded(); }; // if we are in rhino, start loading dependencies right away if(!win.setTimeout){ go() }else{ // otherwise wait a small timeout to make // sure we get all steals in the current file setTimeout(go,0) } } }, _before : before, _after: after }); // this can probably move above steal.p.complete = before(steal.p.complete, function(){ if(this === currentCollection){ // this is the last steal currentCollection = null; } }); // =============================== jQuery =============================== (function(){ var jQueryIncremented = false, jQ, ready = false; // check if jQuery loaded after every script load ... steal.p.loaded = before(steal.p.loaded, function(){ var $ = typeof jQuery !== "undefined" ? jQuery : null; if ($ && "readyWait" in $) { //Increment jQuery readyWait if ncecessary. if (!jQueryIncremented) { jQ = $; $.readyWait += 1; jQueryIncremented = true; } } }); // once the current batch is done, fire ready if it hasn't already been done steal.bind('end', function(){ if (jQueryIncremented && !ready) { jQ.ready(true); ready = true; } }) })(); // =============================== ERROR HANDLING =============================== steal.p.load = after(steal.p.load, function(stel){ if(win.document && !this.completed && !this.completeTimeout && !steal.isRhino && (this.options.protocol == "file:" || !support.error)){ var self = this; this.completeTimeout = setTimeout(function(){ throw "steal.js : "+self.options.src+" not completed" },5000); } }); steal.p.complete = after(steal.p.complete, function(){ this.completeTimeout && clearTimeout(this.completeTimeout) }) // =============================== AOP =============================== function before(f, before, changeArgs){ return changeArgs ? function before_changeArgs(){ return f.apply(this,before.apply(this,arguments)); }: function before_args(){ before.apply(this,arguments); return f.apply(this,arguments); } } /** * Set up a function that runs after the first param. * @param {Object} f * @param {Object} after * @param {Object} changeRet If true, the result of the function will be passed to the after function as the first param. If false, the after function's params are the * same as the original function's params */ function after(f, after, changeRet){ return changeRet ? function after_CRet(){ return after.apply(this,[f.apply(this,arguments)].concat(makeArray(arguments))); }: function after_Ret(){ var ret = f.apply(this,arguments); after.apply(this,arguments); return ret; } } // converts a function to work with when function convert(ob, func){ var oldFunc = ob[func]; // if we don't have callbacks if(!ob[func].callbacks){ //replace start with a function that will call ob2's method ob[func] = function(){ var me = arguments.callee, ret; // call the original function ret = oldFunc.apply(ob,arguments); var cbs = me.callbacks, len = cbs.length; //mark as called so any callees added to this caller will //automatically get called me.called = true; // call other callbacks for(var i =0; i < len; i++){ cbs[i].called() } return ret; } ob[func].callbacks = []; } return ob[func]; }; // maintains function join(obj, meth){ this.obj = obj; this.meth = meth; convert(obj, meth); this.calls = 0 }; extend(join.prototype,{ called : function(){ this.calls--; this.go(); }, // adds functions that will call this join add : function(obj, meth){ // converts the function to be able to call // this join var f = convert(obj, meth); if(!f.called){ // adds us to the callback ... the callback will call // called f.callbacks.push(this); this.calls++; } }, // call go every time the funtion is called go : function(){ if(this.calls === 0){ this.obj[this.meth]() } } }) // chains two functions. When the first one is called, // it calls the second function. // If the second function has multiple callers, it waits until all have been called // // when(parent,"start", steal, "start") // function when(){ // handle if we get called with a function var args = makeArray(arguments), last = args[args.length -1]; if(typeof last === 'function' ){ args[args.length -1] = { 'fn' : last } args.push("fn"); }; var waitMeth = args.pop(), waitObj = args.pop(), joined = new join(waitObj, waitMeth); for(var i =0; i < args.length; i = i+2){ joined.add(args[i], args[i+1]) } // call right away if it should joined.go(); } // =========== DEBUG ========= /*var name = function(stel){ if(stel.options && stel.options.type == "fn"){ return stel.options.orig.toString().substr(0,50) } return stel.options ? stel.options.rootSrc : "CONTAINER" } steal.p.load = before(steal.p.load, function(){ console.log("load", name(this), this.loading, this.id) }) steal.p.loaded = before(steal.p.loaded, function(){ console.log("loaded", name(this), this.id) }) steal.p.complete = before(steal.p.complete, function(){ console.log("complete", name(this), this.id) })*/ // ============= WINDOW LOAD ======== var addEvent = function(elem, type, fn) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } }; var loaded = { load : function(){}, end : function(){} }; var firstEnd = false; addEvent(win, "load", function(){ loaded.load(); }); steal.one("end", function(collection){ loaded.end(); firstEnd = collection; steal.trigger("done", firstEnd) }) when(loaded,"load",loaded,"end", function(){ steal.trigger("ready") steal.isReady = true; }); steal.events.done = { add : function(cb){ if(firstEnd){ cb(firstEnd); return false; } else { return cb; } } }; // =========== HAS ARRAY STUFF ============ // Logic that deals with files that have collections of other files within them. This is usually a production.css file, // which when it loads, needs to mark several CSS and LESS files it represents as being "loaded". This is done // by the production.js file having steal({src: "production.css", has: ["file1.css", "file2.css"] // after a steal is created, if its been loaded already and has a "has", mark those files as loaded steal.p.make = after(steal.p.make, function(stel){ // if we have things if( stel.options.has ) { // if we have loaded this already (and we are adding has's) if( stel.isLoaded ) { stel.loadHas(); } else { // have to mark has as loading steal.loading.apply(steal,stel.options.has) } } return stel; }, true) // if we're about to mark a file as loaded, mark its "has" array files as loaded also steal.p.loaded = before(steal.p.loaded, function(){ if(this.options.has){ this.loadHas(); } }) steal.p. /** * @hide * Goes through the array of files listed in this.options.has, marks them all as loaded. * This is used for files like production.css, which, once they load, need to mark the files they * contain as loaded. */ loadHas = function(){ var stel, i, current = File.cur(); // mark everything in has loaded for(i=0; i -1 && (script = scripts[i]); i--) { if (script.readyState === 'interactive') { return script; } } }, getCachedInteractiveScript = function() { var scripts, i, script; if (interactiveScript && interactiveScript.readyState === 'interactive') { return interactiveScript; } if(script = getInteractiveScript()){ interactiveScript = script; return script; } // check last inserted if(lastInserted && lastInserted.readyState == 'interactive'){ return lastInserted; } return null; }; support.interactive = doc && !!getInteractiveScript(); if (support.interactive) { // after steal is called, check which script is "interactive" (for IE) steal.after = after(steal.after, function(){ var interactive = getCachedInteractiveScript(); // if no interactive script, this is a steal coming from inside a steal, let complete handle it if (!interactive || !interactive.src || /steal\.(production|production\.[a-zA-Z0-9\-\.\_]*)*js/.test(interactive.src)) { return; } // get the source of the script var src = interactive.src; // create an array to hold all steal calls for this script if (!interactives[src]) { interactives[src] = [] } // add to the list of steals for this script tag if (src) { interactives[src].push.apply(interactives[src], pending); pending = []; } }) // This is used for packaged scripts. As the packaged script executes, we grab the // dependencies that have come so far and assign them to the loaded script steal.preloaded = before(steal.preloaded, function(stel){ // get the src name var src = stel.options.src, // and the src of the current interactive script interactiveSrc = getCachedInteractiveScript().src; interactives[src] = interactives[interactiveSrc]; interactives[interactiveSrc] = null; }); } // =========== OPTIONS ========== var stealCheck = /steal\.(production\.)?js.*/, getStealScriptSrc = function(){ if(!doc){ return; } var scripts = doc[STR_GET_BY_TAG]("script"), i = 0, len = scripts.length; //find the steal script and setup initial paths. for ( ; i < len; i++ ) { var src = scripts[i].src; if ( src && stealCheck.test(src) ) { //if script has steal.js return scripts[i]; } } return; }; steal.getScriptOptions = function(script){ var script = script || getStealScriptSrc(), src, scriptOptions, options = {}, commaSplit; if(script){ var src = script.src, start = src.replace(stealCheck,""); if(/steal\/$/.test(start)){ options.rootUrl = start.substr(0, start.length - 6); } else { options.rootUrl = start+"../" } if ( /steal\.production\.js/.test(src) ) { options.env = "production"; } if ( src.indexOf('?') !== -1 ) { scriptOptions = src.split('?')[1]; commaSplit = scriptOptions.split(","); if ( commaSplit[0] ) { options.startFile = commaSplit[0]; } if ( commaSplit[1] && steal.options.env != "production" ) { options.env = commaSplit[1]; } } } return options; }; startup = after(startup, function(){ var options = steal.options, startFiles = []; extend(options, steal.getScriptOptions()); // a steal that existed before this steal if(typeof oldsteal == 'object'){ extend(options, oldsteal); } // if it looks like steal[xyz]=bar, add those to the options var search = win.location && decodeURIComponent(win.location.search); search && search.replace(/steal\[([^\]]+)\]=([^&]+)/g, function( whoe, prop, val ) { // support array like params var commaSeparated = val.split(","); if(commaSeparated.length > 1){ val = commaSeparated; } options[prop] = val; }); // CALCULATE CURRENT LOCATION OF THINGS ... steal.rootUrl(options.rootUrl); // CLEAN UP OPTIONS // make startFile have .js ending if(options.startFile && options.startFile.indexOf(".") == '-1'){ options.startFile = options.startFile + "/" + options.startFile.match(/[^\/]+$/)[0] + ".js"; } if(!options.logLevel){ options.logLevel = 0; } //calculate production location; if (!options.production && options.startFile ) { options.production = File(options.startFile).dir() + '/production.js'; } if ( options.production ) { options.production = options.production + (options.production.indexOf('.js') == -1 ? '.js' : ''); } each(options.loaded || [], function(i, stel){ steal.loaded(stel) }) if(typeof options.startFiles === "string"){ startFiles.push(options.startFiles); } else if(options.startFiles && options.startFiles.length){ startFiles = options.startFiles; } var steals = []; // need to load startFiles in dev or production mode (to run funcunit in production) if( startFiles.length ){ steal.options.startFiles = startFiles; steals.push.apply(steals, startFiles) } // either instrument is in this page (if we're the window opened from steal.browser), or its opener has it if ( options.instrument || (!options.browser && win.top && win.top.opener && win.top.opener.steal && win.top.opener.steal.options.instrument) ) { // force startFiles to load before instrument steals.push(function(){}, { src: "steal/instrument", waits: true }); } //we only load things with force = true if (options.env == 'production' && options.loadProduction) { if (options.production) { //steal(steal.options.startFile); steal({ src: options.production, force: true }); } } else { if (options.loadDev !== false) { steals.unshift({ src: 'steal/dev/dev.js', ignore: true }); } if (options.startFile) { steals.push(options.startFile) } } if (steals.length) { steal.apply(null, steals); } }); steal.when = when; // make steal public win.steal = steal; startup(); })()