/* Copyright (c) 2007, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 2.3.0 */ /** * Provides dynamic loading for the YUI library. It includes the dependency * info for the library, and will automatically pull in dependencies for * the modules requested. It supports rollup files (such as utilities.js * and yahoo-dom-event.js), and will automatically use these when * appropriate in order to minimize the number of http connections * required to load all of the dependencies. * * @module yuiloader * @namespace YAHOO.util */ /** * YUILoader provides dynamic loading for YUI. * @class YAHOO.util.YUILoader * @todo * version management, automatic sandboxing */ (function() { // Define YAHOO_config if it doesn't exist. Only relevant if YAHOO is not // already on the page if (typeof YAHOO_config === "undefined") { YAHOO_config = {}; } // YUI is locally scoped, only pieces of it will be referenced in YAHOO // after YAHOO has been loaded. var YUI = { /* * The library metadata for the current release The is the default * value for YAHOO.util.YUILoader.moduleInfo * @property YUIInfo * @static */ info: { 'base': 'http://yui.yahooapis.com/2.3.0/build/', 'skin': { 'defaultSkin': 'sam', 'base': 'assets/skins/', 'path': 'skin.css', 'rollup': 3 }, 'moduleInfo': { 'animation': { 'type': 'js', 'path': 'animation/animation-min.js', 'requires': ['dom', 'event'] }, 'autocomplete': { 'type': 'js', 'path': 'autocomplete/autocomplete-min.js', 'requires': ['dom', 'event'], 'optional': ['connection', 'animation'], 'skinnable': true }, 'button': { 'type': 'js', 'path': 'button/button-beta-min.js', 'requires': ['element'], 'optional': ['menu'], 'skinnable': true }, 'calendar': { 'type': 'js', 'path': 'calendar/calendar-min.js', 'requires': ['event', 'dom'], 'skinnable': true }, 'colorpicker': { 'type': 'js', 'path': 'colorpicker/colorpicker-beta-min.js', 'requires': ['slider', 'element'], 'optional': ['animation'], 'skinnable': true }, 'connection': { 'type': 'js', 'path': 'connection/connection-min.js', 'requires': ['event'] }, 'container': { 'type': 'js', 'path': 'container/container-min.js', 'requires': ['dom', 'event'], // button is optional, but creates a circular dep //'optional': ['dragdrop', 'animation', 'button'], 'optional': ['dragdrop', 'animation'], 'supersedes': ['containercore'], 'skinnable': true }, 'containercore': { 'type': 'js', 'path': 'container/container_core-min.js', 'requires': ['dom', 'event'] }, 'datasource': { 'type': 'js', 'path': 'datasource/datasource-beta-min.js', 'requires': ['event'], 'optional': ['connection'] }, 'datatable': { 'type': 'js', 'path': 'datatable/datatable-beta-min.js', 'requires': ['element', 'datasource'], 'optional': ['calendar', 'dragdrop'], 'skinnable': true }, 'dom': { 'type': 'js', 'path': 'dom/dom-min.js', 'requires': ['yahoo'] }, 'dragdrop': { 'type': 'js', 'path': 'dragdrop/dragdrop-min.js', 'requires': ['dom', 'event'] }, 'editor': { 'type': 'js', 'path': 'editor/editor-beta-min.js', 'requires': ['menu', 'container', 'element', 'button'], 'optional': ['animation', 'dragdrop'], 'skinnable': true }, 'element': { 'type': 'js', 'path': 'element/element-beta-min.js', 'requires': ['dom', 'event'] }, 'event': { 'type': 'js', 'path': 'event/event-min.js', 'requires': ['yahoo'] }, 'fonts': { 'type': 'css', 'path': 'fonts/fonts-min.css' }, 'grids': { 'type': 'css', 'path': 'grids/grids-min.css', 'requires': ['fonts'], 'optional': ['reset'] }, 'history': { 'type': 'js', 'path': 'history/history-beta-min.js', 'requires': ['event'] }, 'imageloader': { 'type': 'js', 'path': 'imageloader/imageloader-experimental-min.js', 'requires': ['event', 'dom'] }, 'logger': { 'type': 'js', 'path': 'logger/logger-min.js', 'requires': ['event', 'dom'], 'optional': ['dragdrop'], 'skinnable': true }, 'menu': { 'type': 'js', 'path': 'menu/menu-min.js', 'requires': ['containercore'], 'skinnable': true }, 'reset': { 'type': 'css', 'path': 'reset/reset-min.css' }, 'reset-fonts-grids': { 'type': 'css', 'path': 'reset-fonts-grids/reset-fonts-grids.css', 'supersedes': ['reset', 'fonts', 'grids'] }, 'slider': { 'type': 'js', 'path': 'slider/slider-min.js', 'requires': ['dragdrop'], 'optional': ['animation'] }, 'tabview': { 'type': 'js', 'path': 'tabview/tabview-min.js', 'requires': ['element'], 'optional': ['connection'], 'skinnable': true }, 'treeview': { 'type': 'js', 'path': 'treeview/treeview-min.js', 'requires': ['event'], 'skinnable': true }, 'utilities': { 'type': 'js', 'path': 'utilities/utilities.js', 'supersedes': ['yahoo', 'event', 'dragdrop', 'animation', 'dom', 'connection', 'element', 'yahoo-dom-event'], 'rollup': 6 }, 'yahoo': { 'type': 'js', 'path': 'yahoo/yahoo-min.js' }, 'yahoo-dom-event': { 'type': 'js', 'path': 'yahoo-dom-event/yahoo-dom-event.js', 'supersedes': ['yahoo', 'event', 'dom'], 'rollup': 3 }, 'yuiloader': { 'type': 'js', 'path': 'yuiloader/yuiloader-beta-min.js' }, 'yuitest': { 'type': 'js', 'path': 'yuitest/yuitest-beta-min.js', 'requires': ['logger'], 'skinnable': true } } } , // Simple utils since we can't count on YAHOO.lang being available. ObjectUtil: { appendArray: function(o, a) { if (a) { for (var i=0; i * skin: { * * // The default skin, which is automatically applied if not * // overriden by a component-specific skin definition. * // Change this in to apply a different skin globally * defaultSkin: 'sam', * * // This is combined with the loader base property to get * // the default root directory for a skin. ex: * // http://yui.yahooapis.com/2.3.0/build/assets/skins/sam/ * base: 'assets/skins/', * * // The name of the rollup css file for the skin * path: 'skin.css', * * // The number of skinnable components requested that are * // required before using the rollup file rather than the * // individual component css files * rollup: 3, * * // Any component-specific overrides can be specified here, * // making it possible to load different skins for different * // components. It is possible to load more than one skin * // for a given component as well. * overrides: { * calendar: ['skin1', 'skin2'] * } * } * * @property skin */ this.skin = o.skin || YUI.ObjectUtil.clone(YUI.info.skin); if (o.require) { this.require(o.require); } YUI.loaders.push(this); }; YUI.YUILoader.prototype = { FILTERS: { RAW: { 'searchExp': "-min\\.js", 'replaceStr': ".js" }, DEBUG: { 'searchExp': "-min\\.js", 'replaceStr': "-debug.js" } }, SKIN_PREFIX: "skin-", /** Add a new module to the component metadata. The javascript * component must also use YAHOO.register to notify the loader * when it has been loaded, or a verifier function must be * provided *
*
name:
required, the component name
*
type:
required, the component type (js or css)
*
path:
required, the path to the script from "base"
*
requires:
the modules required by this component
*
optional:
the optional modules for this component
*
supersedes:
the modules this component replaces
*
rollup:
the number of superseded modules required for automatic rollup
*
verifier:
a function that is executed to determine when the module is fully loaded
*
fullpath:
If fullpath is specified, this is used instead of the configured base + path
*
skinnable:
flag to determine if skin assets should automatically be pulled in
*
* @method addModule * @param o An object containing the module data * @return {boolean} true if the module was added, false if * the object passed in did not provide all required attributes */ addModule: function(o) { if (!o || !o.name || !o.type || (!o.path && !o.fullpath)) { return false; } this.moduleInfo[o.name] = o; this.dirty = true; return true; }, /** * Add a requirement for one or more module * @method require * @param what {string[] | string*} the modules to load */ require: function(what) { var a = (typeof what === "string") ? arguments : what; this.dirty = true; for (var i=0; iformatSkin, providing the skin name and * module name if the string matches the pattern for skins. * @method parseSkin * @param mod {string} the module name to parse * @return {skin: string, module: string} the parsed skin name * and module name, or null if the supplied string does not match * the skin pattern */ parseSkin: function(mod) { if (mod.indexOf(this.SKIN_PREFIX) === 0) { var a = mod.split("-"); return {skin: a[1], module: a[2]}; } return null; }, /** * Look for rollup packages to determine if all of the modules a * rollup supersedes are required. If so, include the rollup to * help reduce the total number of connections required. Called * by calculate() * @method _rollup * @private */ _rollup: function() { var i, j, m, s, rollups={}, r=this.required, roll; // find and cache rollup modules if (this.dirty || !this.rollups) { for (i in this.moduleInfo) { m = this.moduleInfo[i]; //if (m && m.rollup && m.supersedes) { if (m && m.rollup) { rollups[i] = m; } } this.rollups = rollups; } // make as many passes as needed to pick up rollup rollups for (;;) { var rolled = false; // go through the rollup candidates for (i in rollups) { // there can be only one if (!r[i] && !this.loaded[i]) { m =this.moduleInfo[i]; s = m.supersedes; roll=true; if (!m.rollup) { continue; } var skin = this.parseSkin(i), c = 0; if (skin) { for (j in r) { if (i !== j && this.parseSkin(j)) { c++; roll = (c >= m.rollup); if (roll) { break; } } } } else { // require all modules to trigger a rollup (using the // threshold value has not proved worthwhile) for (j=0;j= m.rollup); if (roll) { break; } } } } if (roll) { // add the rollup r[i] = true; rolled = true; // expand the rollup's dependencies this.getRequires(m); } } } // if we made it here w/o rolling up something, we are done if (!rolled) { break; } } }, /** * Remove superceded modules and loaded modules. Called by * calculate() after we have the mega list of all dependencies * @method _reduce * @private */ _reduce: function() { var i, j, s, m, r=this.required; for (i in r) { // remove if already loaded if (i in this.loaded) { delete r[i]; // remove anything this module supersedes } else { var skinDef = this.parseSkin(i); if (skinDef) { //console.log("skin found in reduce: " + skinDef.skin + ", " + skinDef.module); // the skin rollup will not have a module name if (!skinDef.module) { var skin_pre = this.SKIN_PREFIX + skinDef.skin; //console.log("skin_pre: " + skin_pre); for (j in r) { if (j !== i && j.indexOf(skin_pre) > -1) { //console.log ("removing component skin: " + j); delete r[j]; } } } } else { m = this.moduleInfo[i]; s = m && m.supersedes; if (s) { for (j=0;j -1) { return true; } var ss=info[bb] && info[bb].supersedes; if (ss) { for (ii=0; iitype can be "js" or "css". Both script and * css are inserted if type is not provided. * @method insert * @param callback {Function} a function to execute when the load * is complete. * @param o optional options object * @param type {string} the type of dependency to insert */ insert: function(callback, o, type) { //if (!this.onLoadComplete) { //this.onLoadComplete = callback; //} if (!type) { var self = this; this._internalCallback = function() { self._internalCallback = null; self.insert(callback, o, "js"); }; this.insert(null, o, "css"); return; } o = o || {}; // store the callback for when we are done this.onLoadComplete = callback || this.onLoadComplete; // store the optional filter var f = o && o.filter || null; if (typeof f === "string") { f = f.toUpperCase(); // the logger must be available in order to use the debug // versions of the library if (f === "DEBUG") { this.require("logger"); } } this.filter = this.FILTERS[f] || f || this.FILTERS[this.filter] || this.filter; // store the options... not currently in use this.insertOptions = o; // build the dependency list this.calculate(o); // set a flag to indicate the load has started this.loading = true; // keep the loadType (js, css or undefined) cached this.loadType = type; // start the load this.loadNext(); }, /** * Executed every time a module is loaded, and if we are in a load * cycle, we attempt to load the next script. Public so that it * is possible to call this if using a method other than * YAHOO.register to determine when scripts are fully loaded * @method loadNext * @param mname {string} optional the name of the module that has * been loaded (which is usually why it is time to load the next * one) */ loadNext: function(mname) { // console.log("loadNext executing, just loaded " + mname); // The global handler that is called when each module is loaded // will pass that module name to this function. Storing this // data to avoid loading the same module multiple times if (mname) { this.inserted[mname] = true; //var o = this.getProvides(mname); //this.inserted = YUI.ObjectUtil.merge(this.inserted, o); } // It is possible that this function is executed due to something // else one the page loading a YUI module. Only react when we // are actively loading something if (!this.loading) { return; } // if the module that was just loaded isn't what we were expecting, // continue to wait if (mname && mname !== this.loading) { return; } var s=this.sorted, len=s.length, i, m, url; for (i=0; i