blossom
Version:
Modern, Cross-Platform Application Framework
584 lines (477 loc) • 18.1 kB
JavaScript
sc_require('tasks/task');
// for development
SC.DEBUG_PACKAGES = false;
//...............................................
// Package mode is set globally and allows (mostly for
// development but may serve other purposes) for
// only specific portions of code to be evaluated.
// For instance, if during developement there is a need
// to test only the model layer one would not want to
// include any views or (possibly) control layer objects
// or else it would require loading the entire sproutcore
// framework to run it. Any combination of these settings
// could be used.
SC.PACKAGE_MODE_MODELS = 0x01;
SC.PACKAGE_MODE_VIEWS = 0x02;
SC.PACKAGE_MODE_CONTROLLERS = 0x04;
// Other is simply mixins/ext/runtime code not otherwise
// categorized that *should* not be dependent on layers
// not otherwise specified to include. Ultimately that
// is the perogative of the developer.
SC.PACKAGE_MODE_OTHER = 0x08;
// The normal mode includes all of the possible flags
SC.PACKAGE_MODE_NORMAL = 0x10;
// The default package flag is set here but can be
// overridden later or by an extended SC.Package class.
SC.PACKAGE_MODE = SC.PACKAGE_MODE_NORMAL;
// To make a combination of other settings do something
// like the following to create the proper bitmask:
// SC.PACKAGE_MODE = SC.PACKAGE_MODE_MODELS | SC.PACKAGE_MODE_OTHER;
/**
@class
By default, packages are logically separated containers of code
built-out by the build tools that can be included in the
running application in various forms (core, lazy or demand).
There need only be one package manager for any running
application as multiples would simply look at the same
package information.
There are global mode settings that can be set to override
defaults at the application level. Packages can be set
to evaluate only portions of the source code from the package.
The buildtools separate content by convention not by inspection.
Files located in the `models` directory will be included as
models, `controllers` as controllers and `views` as views. All
other files (exempting `core.js` that is a specially prioritized
file) will be included (and ordered) as `other`. The application
can instruct packages to load combinations of these separated
layers or all of the (SC.PACKAGE_MODE_NORMAL the default setting).
It is up to the developer to ensure the files can be programatically
ordered by their sc_require statements and that files are organized
in the correct directory structures. If type `other` is selected it
will be evaluated _before any other types_. This option is
currently non-configurable.
@see #SC#PACKAGE_MODE
@see #SC#PACKAGE_MODE_MODELS
@see #SC#PACKAGE_MODE_VIEWS
@see #SC#PACKAGE_MODE_CONTROLLERS'
@see #SC#PACKAGE_MODE_OTHER
@see #SC#PACKAGE_MODE_NORMAL
@see #SC#DEBUG_PACKAGES
See build-tools documentation for more details @see#BT#Package
@author W. Cole Davis
@author Original author(s) of SC.Module
*/
SC.Package = SC.Object.extend(
/** @lends SC.Package.prototype */ {
/** @private */
log: SC.DEBUG_PACKAGES,
/**
Attempt to load a package. If it has been loaded but not evaluated will be
evaluated. If already loaded and evaluated but a new callback is supplied it
will be executed immediately (along with any other queued callbacks). Callbacks
will always be supplied with the first parameter as the name of the package
that was loaded and that is invoking it.
@param {String} packageName The name of the package to load.
@param {Object} [target] The context from which to call the callback method.
@param {Function} [method] The function to execute as the callback.
@param {...} [args] Any additional arguments will be supplied to the
callback as paramters.
*/
loadPackage: function(packageName, target, method) {
var package = SC.PACKAGE_MANIFEST[packageName];
var args = SC.A(arguments).slice(3);
var log = this.log;
if (method === undefined && target instanceof Function) {
method = target;
target = null;
}
if (!package) {
throw "SC.Package: could not find package '%@'".fmt(packageName);
}
if (package.isReady) {
if (log) SC.Logger.info("loadPackage() package '%@' already loaded and ready".fmt(packageName));
this.registerCallbackForPackage(packageName, target, method, args);
this._sc_invokeCallbacksForPackage(packageName);
return true;
} else if (package.isLoaded && !package.isWaitingForRunloop) {
if (log) SC.Logger.info("loadPackage() package '%@' was loaded and ".fmt(packageName) +
"was not waiting for runloop");
if (!this._sc_dependenciesMetForPackage(packageName)) {
if (log) SC.Logger.info("loadPackage() package dependencies were not met for " +
"package '%@'".fmt(packageName));
this.registerCallbackForPackage(packageName, target, method, args);
this._sc_loadDependenciesForPackage(packageName);
return false;
}
if (package.source) {
if (log) SC.Logger.info("loadPackage() package '%@' source is available and will ".fmt(packageName) +
"be executed now");
this._sc_evaluatePackageSource(packageName);
this.registerCallbackForPackage(packageName, target, method, args);
this.invokeLast(function() {
package.isReady = true;
this._sc_packageDidBecomeReady(packageName);
});
return false;
}
} else if (package.isWaitingForRunLoop) {
this.registerCallbackForPackage(packageName, target, method, args);
return true;
} else {
this.registerCallbackForPackage(packageName, target, method, args);
if (!package.isLoading) {
if (log) SC.Logger.info("loadPackage() package '%@' loading dependencies and ".fmt(packageName) +
"source");
this._sc_loadDependenciesForPackage(packageName);
this.loadJavaScriptForPackage(packageName);
package.isLoading = true;
}
return false;
}
},
/**
Load all available packages.
@method
*/
loadAll: function() {
var packageName;
var package;
for (packageName in SC.PACKAGE_MANIFEST) {
package = SC.PACKAGE_MANIFEST[packageName];
if (!package.isLoading && !package.isLoaded && !package.isReady) {
this.loadPackage(packageName);
}
}
},
/**
Register a callback method to be called once the package has successfully
loaded and been evaluated. Additional arguments will be applied as
arguments to the callback when it is executed.
@param {String} packageName The name of the package for which to
register the callback.
@param {Object} [target] The context for which to run the callback method.
@param {Function} [method] The callback function to execute.
@param {...} [args] Any other arguments will be used as parameters to the callback.
*/
registerCallbackForPackage: function(packageName, target, method, args) {
var package = SC.PACKAGE_MANIFEST[packageName];
var cb;
if (!args) args = [];
args.unshift(packageName);
if (target) {
if (target instanceof Function) {
method = target;
target = null;
}
} else { return; }
if (typeof method === 'string') {
method = target[method];
}
cb = function() {
var needsRunLoop = !!SC.RunLoop.currentRunloop;
if (needsRunLoop) {
SC.run(function() {
method.apply(target, args);
});
} else {
method.apply(target, args);
}
}
if (!package.callbacks) {
package.callbacks = [];
}
package.callbacks.push(cb);
},
/**
Attempt to retrieve the source JavaScript for a
package from the webserver. This method can be
overridden in extensions of SC.Package to
retrieve the source by some other means.
@param {String} packageName The name of the requested
package to retrieve.
@returns {SC.Package} receiver
*/
loadJavaScriptForPackage: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var log = this.log;
var el;
var url = this._sc_urlForPackage(packageName);
var self = this;
if (package.isLoaded) {
if (log) SC.Logger.warn("loadJavaScript() package '%@' already loaded".fmt(packageName));
return;
}
// set the isLoading flag to true
package.isLoading = true;
// create the script element that will generate the
// request for the source
el = document.createElement('script');
el.setAttribute('type', 'text/javascript');
el.setAttribute('src', url);
// needs to be tested with various versions of IE to determine
// if this will work
el.onload = function() {
SC.run(function() {
// we don't know who is handling this thus
// the scoping back to self
self._sc_packageDidLoad(packageName);
});
}
// go ahead and add the node to the dom so
// the request will be made...
document.body.appendChild(el);
return this;
},
/**
Creates the url for the script to request when loading
the JavaScript source of packages.
@param {String} packageName The name of the package being
requested.
@returns {String} The constructed url or null if it could
not be built or determined.
*/
_sc_urlForPackage: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var mask = SC.PACKAGE_MODE;
var url = [];
url.push(package.rootNode);
url.push('packages');
url.push(package.basename);
url = '/' + url.join('/');
return url;
},
/**
Determines if all of the dependencies for a package have
been loaded and executed.
@param {String} packageName The name of the package whose
dependencies are being evaluated.
@returns {Boolean} YES|NO depending on whether all of the
dependencies have been met.
*/
_sc_dependenciesMetForPackage: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var dependencies = package.dependencies || [];
var dependency;
var name;
var idx = 0;
for (; idx < dependencies.length; ++idx) {
name = dependencies[idx];
dependency = SC.PACKAGE_MANIFEST[name];
if (!dependency) {
throw "SC.Package: could not find dependency '%@' for package ".fmt(name) +
"'%@'".fmt(packageName);
}
if (!dependency.isReady) return false;
}
return true;
},
/**
Find and load any dependencies for a given package.
@param {String} packageName The target package.
*/
_sc_loadDependenciesForPackage: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var log = this.log;
var dependencies = package.dependencies || [];
var dependency;
var name;
var dependents;
var idx = 0;
if (log) SC.Logger.info("_sc_loadDependenciesForPackage() loading dependencies " +
"for package '%@'".fmt(packageName));
for (; idx < dependencies.length; ++idx) {
name = dependencies[idx];
dependency = SC.PACKAGE_MANIFEST[name];
if (!dependency) {
throw "SC.Package: could not find dependency '%@' for package ".fmt(name) +
"'%@'".fmt(packageName);
} else {
if (dependency.isLoading) {
dependents = dependency.dependents;
if (!dependents) dependency.dependents = dependents = [];
dependents.push(packageName);
} else if (dependency.isReady) {
continue;
} else {
dependents = dependency.dependents;
if (!dependents) dependency.dependents = dependents = [];
dependents.push(packageName);
if (log) SC.Logger.info("_sc_loadDependenciesForPackage() package '%@' ".fmt(packageName) +
"requires loading '%@'".fmt(name));
this.loadPackage(name);
}
}
}
},
/**
Called once a package's source is loaded and ready.
@param {String} packageName The name of the package that
was loaded.
*/
_sc_packageDidLoad: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var log = this.log;
if (log) SC.Logger.info("_sc_packageDidLoad() package '%@' loaded".fmt(packageName));
delete package.isLoading;
package.isLoaded = true;
if (!this._sc_dependenciesMetForPackage(packageName)) {
if (log) SC.Logger.info("_sc_packageDidLoad() package '%@' still waiting on ".fmt(packageName) +
"some dependencies to load");
} else if (package.source) {
this._sc_evaluatePackageSource(packageName);
package.isWaitingForRunLoop = true;
this.invokeLast(function() {
package.isReady = true;
this._sc_packageDidBecomeReady(packageName);
});
} else {
throw "SC.Package: package '%@' was loaded without any source".fmt(packageName);
}
},
/**
Attempts to notify any waiting dependent packages that this
dependency is now available. If the dependent is now ready it is
executed.
@param {String} packageName The name of the package that became ready.
*/
_sc_packageDidBecomeReady: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var dependents = package.dependents || [];
var idx = 0;
var dependent;
var name;
var log = this.log;
if (log) SC.Logger.info("_sc_packageDidBecomeReady() invoking callbacks and " +
"evaluating dependents for package '%@'".fmt(packageName));
package.isWaitingForRunLoop = false;
this._sc_invokeCallbacksForPackage(packageName);
for (; idx < dependents.length; ++idx) {
name = dependents[idx];
dependent = SC.PACKAGE_MANIFEST[name];
if (dependent.isLoaded && this._sc_dependenciesMetForPackage(name)) {
if (log) SC.Logger.info("_sc_packageDidBecomeReady() package '%@' dependent ".fmt(packageName) +
"'%@' is ready to be executed now".fmt(name));
this._sc_evaluatePackageSource(name);
dependent.isWaitingForRunLoop = true;
this.invokeLast(function() {
dependent.isReady = true;
this._sc_packageDidBecomeReady(name);
});
}
}
},
/**
Retrieves any available callbacks for the named package
and executes them in the order they were entered in the
queue. Callbacks are freed once they have been executed.
@param {String} packageName The name of the package whose
callbacks need to be executed.
*/
_sc_invokeCallbacksForPackage: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var callbacks = package.callbacks || [];
var idx = 0;
var callback;
for (; idx < callbacks.length; ++idx) {
callback = callbacks[idx];
callback();
}
},
/**
Evaluates the source or portion of the loaded source for
a given package.
@param {String} packageName The name of the package from which
to evaluate source code.
*/
_sc_evaluatePackageSource: function(packageName) {
var package = SC.PACKAGE_MANIFEST[packageName];
var flags = SC.PACKAGE_MODE;
var log = this.log;
var source;
var code;
var parts = [];
var part;
var idx = 0;
if (package.isEvaluated) {
return;
}
if (log) SC.Logger.info("_sc_evaluatePackageSource() attempting to " +
"execute source for package '%@'".fmt(packageName));
// if there is no source we can't do much either
if (!package.source) {
throw "SC.Package: cannot execute non-existant source for package '%@'".fmt(packageName);
}
source = package.source;
// the ordering of inclusion is important here
if (flags & SC.PACKAGE_MODE_NORMAL) {
parts = "other models controllers views".w();
} else {
if (flags & SC.PACKAGE_MODE_OTHER) {
parts.push('other');
}
if (flags & SC.PACKAGE_MODE_MODELS) {
parts.push('models');
}
if (flags & SC.PACKAGE_MODE_CONTROLLERS) {
parts.push('controllers');
}
if (flags & SC.PACKAGE_MODE_VIEWS) {
parts.push('views');
}
}
code = '';
for (; idx < parts.length; ++idx) {
part = parts[idx];
if (source[part]) {
if (code && code.length > 0) code += ';';
code += source[part];
// free the used element of the source from
// the package source object
delete package.source[part];
}
}
// if we accumulated any code go ahead and execute it
if (code && code.length > 0) {
try {
// need to execute in the global scope and
// make up for msie shortcomings
(window.execScript || function(data) {
window['eval'].call(window, data);
})(code);
package.isEvaluated = true;
} catch(err) {
throw "SC.Package: failed to evaluate source for package '%@' ".fmt(packageName) +
"due to the following error: " + err.message;
}
} else {
if (log) SC.Logger.warn("_sc_evaluatePackageSource() " +
"no package source found for given package mode for package '%@'".fmt(packageName));
}
package.isReady = true;
},
/** @private */
init: function() {
arguments.callee.base.apply(this, arguments);
var self = this;
// update the task definition to use the correct
// package manager
SC.Package.LazyPackageTask.prototype.run = function() {
self.loadPackage(this.lazyPackageName);
}
SC.ready(function() {
var packages = SC.PACKAGE_MANIFEST;
var package;
var task;
for (packageName in packages) {
package = packages[packageName];
if (package.type === 'lazy') {
task = SC.Package.LazyPackageTask.create({ lazyPackageName: packageName });
SC.backgroundTaskQueue.push(task);
}
}
});
} // init
});
SC.Package.LazyPackageTask = SC.Task.extend({
lazyPackageName: null,
});