From edca2a9f031c7421c4b155a74261bd3845a72b53 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 14 Aug 2015 14:42:25 -0700 Subject: [PATCH] [Code Style] Use prototypes in framework layer WTD-1482 --- .../framework/src/FrameworkInitializer.js | 36 ++- platform/framework/src/LogLevel.js | 62 ++-- .../src/bootstrap/ApplicationBootstrapper.js | 36 ++- platform/framework/src/load/Bundle.js | 265 ++++++++------- platform/framework/src/load/BundleLoader.js | 51 ++- platform/framework/src/load/Extension.js | 171 +++++----- .../src/register/CustomRegistrars.js | 302 +++++++++++------- .../src/register/ExtensionRegistrar.js | 107 ++++--- .../framework/src/register/ExtensionSorter.js | 34 +- .../src/register/ServiceCompositor.js | 60 ++-- .../framework/src/resolve/BundleResolver.js | 48 +-- .../src/resolve/ExtensionResolver.js | 70 ++-- .../src/resolve/ImplementationLoader.js | 42 ++- .../src/resolve/RequireConfigurator.js | 142 ++++---- 14 files changed, 750 insertions(+), 676 deletions(-) diff --git a/platform/framework/src/FrameworkInitializer.js b/platform/framework/src/FrameworkInitializer.js index a0db162f01..add6846e9f 100644 --- a/platform/framework/src/FrameworkInitializer.js +++ b/platform/framework/src/FrameworkInitializer.js @@ -40,24 +40,36 @@ define( * * @memberof platform/framework * @constructor - * @param {BundleLoader} loader - * @param {BundleResolver} resolver - * @param {ExtensionRegistrar} registrar - * @param {ApplicationBootstrapper} bootstrapper + * @param {platform/framework.BundleLoader} loader + * @param {platform/framework.BundleResolver} resolver + * @param {platform/framework.ExtensionRegistrar} registrar + * @param {platform/framework.ApplicationBootstrapper} bootstrapper */ function FrameworkInitializer(loader, resolver, registrar, bootstrapper) { + this.loader = loader; + this.resolver = resolver; + this.registrar = registrar; + this.bootstrapper = bootstrapper; + } - return { - runApplication: function (bundleList) { - return loader.loadBundles(bundleList) - .then(resolver.resolveBundles) - .then(registrar.registerExtensions) - .then(bootstrapper.bootstrap); - } - + function bind(method, thisArg) { + return function () { + return method.apply(thisArg, arguments); }; } + /** + * Run the application defined by this set of bundles. + * @param bundleList + * @returns {*} + */ + FrameworkInitializer.prototype.runApplication = function (bundleList) { + return this.loader.loadBundles(bundleList) + .then(bind(this.resolver.resolveBundles, this.resolver)) + .then(bind(this.registrar.registerExtensions, this.registrar)) + .then(bind(this.bootstrapper.bootstrap, this.bootstrapper)); + }; + return FrameworkInitializer; } ); diff --git a/platform/framework/src/LogLevel.js b/platform/framework/src/LogLevel.js index 1c54659ff6..973811ca07 100644 --- a/platform/framework/src/LogLevel.js +++ b/platform/framework/src/LogLevel.js @@ -53,7 +53,29 @@ define( */ function LogLevel(level) { // Find the numeric level associated with the string - var index = LOG_LEVELS.indexOf(level); + this.index = LOG_LEVELS.indexOf(level); + + // Default to 'warn' level if unspecified + if (this.index < 0) { + this.index = 1; + } + } + + /** + * Configure logging to suppress log output if it is + * not of an appropriate level. Both the Angular app + * being initialized and a reference to `$log` should be + * passed; the former is used to configure application + * logging, while the latter is needed to apply the + * same configuration during framework initialization + * (since the framework also logs.) + * + * @param app the Angular app to configure + * @param $log Angular's $log (also configured) + * @memberof platform/framework.LogLevel# + */ + LogLevel.prototype.configure = function (app, $log) { + var index = this.index; // Replace logging methods with no-ops, if they are // not of an appropriate level. @@ -67,36 +89,14 @@ define( }); } - // Default to 'warn' level if unspecified - if (index < 0) { - index = 1; - } - - return { - /** - * Configure logging to suppress log output if it is - * not of an appropriate level. Both the Angular app - * being initialized and a reference to `$log` should be - * passed; the former is used to configure application - * logging, while the latter is needed to apply the - * same configuration during framework initialization - * (since the framework also logs.) - * - * @param app the Angular app to configure - * @param $log Angular's $log (also configured) - * @memberof platform/framework.LogLevel# - */ - configure: function (app, $log) { - decorate($log); - app.config(function ($provide) { - $provide.decorator('$log', function ($delegate) { - decorate($delegate); - return $delegate; - }); - }); - } - }; - } + decorate($log); + app.config(function ($provide) { + $provide.decorator('$log', function ($delegate) { + decorate($delegate); + return $delegate; + }); + }); + }; return LogLevel; } diff --git a/platform/framework/src/bootstrap/ApplicationBootstrapper.js b/platform/framework/src/bootstrap/ApplicationBootstrapper.js index 82b1714e31..f191bbbdaa 100644 --- a/platform/framework/src/bootstrap/ApplicationBootstrapper.js +++ b/platform/framework/src/bootstrap/ApplicationBootstrapper.js @@ -42,25 +42,27 @@ define( * @constructor */ function ApplicationBootstrapper(angular, document, $log) { - return { - /** - * Bootstrap the application. - * - * @method - * @memberof ApplicationBootstrapper# - * @param {angular.Module} app the Angular application to - * bootstrap - * @memberof platform/framework.ApplicationBootstrapper# - */ - bootstrap: function (app) { - $log.info("Bootstrapping application " + (app || {}).name); - angular.element(document).ready(function () { - angular.bootstrap(document, [app.name]); - }); - } - }; + this.angular = angular; + this.document = document; + this.$log = $log; } + /** + * Bootstrap the application. + * + * @param {angular.Module} app the Angular application to + * bootstrap + */ + ApplicationBootstrapper.prototype.bootstrap = function (app) { + var angular = this.angular, + document = this.document, + $log = this.$log; + $log.info("Bootstrapping application " + (app || {}).name); + angular.element(document).ready(function () { + angular.bootstrap(document, [app.name]); + }); + }; + return ApplicationBootstrapper; } ); diff --git a/platform/framework/src/load/Bundle.js b/platform/framework/src/load/Bundle.js index 147ca01ef1..a53d2761fd 100644 --- a/platform/framework/src/load/Bundle.js +++ b/platform/framework/src/load/Bundle.js @@ -56,13 +56,7 @@ define( function Bundle(path, bundleDefinition) { // Start with defaults var definition = Object.create(Constants.DEFAULT_BUNDLE), - logName = path, - self; - - // Utility function for resolving paths in this bundle - function resolvePath(elements) { - return [path].concat(elements || []).join(Constants.SEPARATOR); - } + logName = path; // Override defaults with specifics from bundle definition Object.keys(bundleDefinition).forEach(function (k) { @@ -81,135 +75,138 @@ define( logName += ")"; } - self = { - /** - * Get the path to this bundle. - * @memberof Bundle# - * @returns {string} - * @memberof platform/framework.Bundle# - */ - getPath: function () { - return path; - }, - /** - * Get the path to this bundle's source folder. If an - * argument is provided, the path will be to the source - * file within the bundle's source file. - * - * @memberof Bundle# - * @param {string} [sourceFile] optionally, give a path to - * a specific source file in the bundle. - * @returns {string} - * @memberof platform/framework.Bundle# - */ - getSourcePath: function (sourceFile) { - var subpath = sourceFile ? - [ definition.sources, sourceFile ] : - [ definition.sources ]; - - return resolvePath(subpath); - }, - /** - * Get the path to this bundle's resource folder. If an - * argument is provided, the path will be to the resource - * file within the bundle's resource file. - * - * @memberof Bundle# - * @param {string} [resourceFile] optionally, give a path to - * a specific resource file in the bundle. - * @returns {string} - * @memberof platform/framework.Bundle# - */ - getResourcePath: function (resourceFile) { - var subpath = resourceFile ? - [ definition.resources, resourceFile ] : - [ definition.resources ]; - - return resolvePath(subpath); - }, - /** - * Get the path to this bundle's library folder. If an - * argument is provided, the path will be to the library - * file within the bundle's resource file. - * - * @memberof Bundle# - * @param {string} [libraryFile] optionally, give a path to - * a specific library file in the bundle. - * @returns {string} - * @memberof platform/framework.Bundle# - */ - getLibraryPath: function (libraryFile) { - var subpath = libraryFile ? - [ definition.libraries, libraryFile ] : - [ definition.libraries ]; - - return resolvePath(subpath); - }, - /** - * Get library configuration for this bundle. This is read - * from the bundle's definition; if the bundle is well-formed, - * it will resemble a require.config object. - * @memberof Bundle# - * @returns {object} - * @memberof platform/framework.Bundle# - */ - getConfiguration: function () { - return definition.configuration || {}; - }, - /** - * Get a log-friendly name for this bundle; this will - * include both the key (machine-readable name for this - * bundle) and the name (human-readable name for this - * bundle.) - * @returns {string} log-friendly name for this bundle - * @memberof platform/framework.Bundle# - */ - getLogName: function () { - return logName; - }, - /** - * Get all extensions exposed by this bundle of a given - * category. - * - * @param category - * @memberof Bundle# - * @returns {Array} - * @memberof platform/framework.Bundle# - */ - getExtensions: function (category) { - var extensions = definition.extensions[category] || []; - - return extensions.map(function objectify(extDefinition) { - return new Extension(self, category, extDefinition); - }); - }, - /** - * Get a list of all categories of extension exposed by - * this bundle. - * - * @memberof Bundle# - * @returns {Array} - * @memberof platform/framework.Bundle# - */ - getExtensionCategories: function () { - return Object.keys(definition.extensions); - }, - /** - * Get the plain definition of this bundle, as read from - * its JSON declaration. - * - * @memberof Bundle# - * @returns {BundleDefinition} the raw definition of this bundle - * @memberof platform/framework.Bundle# - */ - getDefinition: function () { - return definition; - } - }; - - return self; + this.path = path; + this.definition = definition; + this.logName = logName; } + + // Utility function for resolving paths in this bundle + Bundle.prototype.resolvePath = function (elements) { + var path = this.path; + return [path].concat(elements || []).join(Constants.SEPARATOR); + }; + + + /** + * Get the path to this bundle. + * @returns {string} path to this bundle; + */ + Bundle.prototype.getPath = function () { + return this.path; + }; + + /** + * Get the path to this bundle's source folder. If an + * argument is provided, the path will be to the source + * file within the bundle's source file. + * + * @param {string} [sourceFile] optionally, give a path to + * a specific source file in the bundle. + * @returns {string} path to the source folder (or to the + * source file within it) + */ + Bundle.prototype.getSourcePath = function (sourceFile) { + var subpath = sourceFile ? + [ this.definition.sources, sourceFile ] : + [ this.definition.sources ]; + + return this.resolvePath(subpath); + }; + + /** + * Get the path to this bundle's resource folder. If an + * argument is provided, the path will be to the resource + * file within the bundle's resource file. + * + * @param {string} [resourceFile] optionally, give a path to + * a specific resource file in the bundle. + * @returns {string} path to the resource folder (or to the + * resource file within it) + */ + Bundle.prototype.getResourcePath = function (resourceFile) { + var subpath = resourceFile ? + [ this.definition.resources, resourceFile ] : + [ this.definition.resources ]; + + return this.resolvePath(subpath); + }; + + /** + * Get the path to this bundle's library folder. If an + * argument is provided, the path will be to the library + * file within the bundle's resource file. + * + * @param {string} [libraryFile] optionally, give a path to + * a specific library file in the bundle. + * @returns {string} path to the resource folder (or to the + * resource file within it) + */ + Bundle.prototype.getLibraryPath = function (libraryFile) { + var subpath = libraryFile ? + [ this.definition.libraries, libraryFile ] : + [ this.definition.libraries ]; + + return this.resolvePath(subpath); + }; + + /** + * Get library configuration for this bundle. This is read + * from the bundle's definition; if the bundle is well-formed, + * it will resemble a require.config object. + * @returns {object} library configuration + */ + Bundle.prototype.getConfiguration = function () { + return this.definition.configuration || {}; + }; + + /** + * Get a log-friendly name for this bundle; this will + * include both the key (machine-readable name for this + * bundle) and the name (human-readable name for this + * bundle.) + * @returns {string} log-friendly name for this bundle + */ + Bundle.prototype.getLogName = function () { + return this.logName; + }; + + /** + * Get all extensions exposed by this bundle of a given + * category. + * + * @param {string} category name of the extension category + * @returns {Array} extension definitions of that cataegory + */ + Bundle.prototype.getExtensions = function (category) { + var extensions = this.definition.extensions[category] || [], + self = this; + + return extensions.map(function objectify(extDefinition) { + return new Extension(self, category, extDefinition); + }); + }; + + /** + * Get a list of all extension categories exposed by this bundle. + * + * @returns {string[]} the extension categories + */ + Bundle.prototype.getExtensionCategories = function () { + return Object.keys(this.definition.extensions); + }; + + /** + * Get the plain definition of this bundle, as read from + * its JSON declaration. + * + * @returns {platform/framework.BundleDefinition} the raw + * definition of this bundle + */ + Bundle.prototype.getDefinition = function () { + return this.definition; + }; + return Bundle; } ); diff --git a/platform/framework/src/load/BundleLoader.js b/platform/framework/src/load/BundleLoader.js index 34f70b2502..14b404f195 100644 --- a/platform/framework/src/load/BundleLoader.js +++ b/platform/framework/src/load/BundleLoader.js @@ -41,10 +41,26 @@ define( * * @memberof platform/framework * @constructor - * @param {object} $http Angular's HTTP requester - * @param {object} $log Angular's logging service + * @param $http Angular's HTTP requester + * @param $log Angular's logging service */ function BundleLoader($http, $log) { + this.$http = $http; + this.$log = $log; + + } + + /** + * Load a group of bundles, to be used to constitute the + * application by later framework initialization phases. + * + * @param {string|string[]} an array of bundle names to load, or + * the name of a JSON file containing that array + * @returns {Promise.} a promise for the loaded bundles + */ + BundleLoader.prototype.loadBundles = function (bundles) { + var $http = this.$http, + $log = this.$log; // Utility function; load contents of JSON file using $http function getJSON(file) { @@ -92,7 +108,7 @@ define( var bundlePromises = bundleArray.map(loadBundle); return Promise.all(bundlePromises) - .then(filterBundles); + .then(filterBundles); } // Load all bundles named in the referenced file. The file is @@ -101,31 +117,10 @@ define( return getJSON(listFile).then(loadBundlesFromArray); } - // Load all indicated bundles. If the argument is an array, - // this is taken to be a list of all bundles to load; if it - // is a string, then it is treated as the name of a JSON - // file containing the list of bundles to load. - function loadBundles(bundles) { - return Array.isArray(bundles) ? loadBundlesFromArray(bundles) : - (typeof bundles === 'string') ? loadBundlesFromFile(bundles) : - Promise.reject(new Error(INVALID_ARGUMENT_MESSAGE)); - } - - - return { - /** - * Load a group of bundles, to be used to constitute the - * application by later framework initialization phases. - * - * @memberof BundleLoader# - * @param {string|string[]} an array of bundle names to load, or - * the name of a JSON file containing that array - * @returns {Promise.} - * @memberof platform/framework.BundleLoader# - */ - loadBundles: loadBundles - }; - } + return Array.isArray(bundles) ? loadBundlesFromArray(bundles) : + (typeof bundles === 'string') ? loadBundlesFromFile(bundles) : + Promise.reject(new Error(INVALID_ARGUMENT_MESSAGE)); + }; return BundleLoader; } diff --git a/platform/framework/src/load/Extension.js b/platform/framework/src/load/Extension.js index 7a2d46ae7b..d3b19f94fa 100644 --- a/platform/framework/src/load/Extension.js +++ b/platform/framework/src/load/Extension.js @@ -78,94 +78,93 @@ define( // Attach bundle metadata extensionDefinition.bundle = bundle.getDefinition(); - return { - /** - * Get the machine-readable identifier for this extension. - * - * @returns {string} - * @memberof platform/framework.Extension# - */ - getKey: function () { - return definition.key || "undefined"; - }, - /** - * Get the bundle which declared this extension. - * - * @memberof Extension# - * @returns {Bundle} - * @memberof platform/framework.Extension# - */ - getBundle: function () { - return bundle; - }, - /** - * Get the category into which this extension falls. - * (e.g. "directives") - * - * @memberof Extension# - * @returns {string} - * @memberof platform/framework.Extension# - */ - getCategory: function () { - return category; - }, - /** - * Check whether or not this extension should have an - * associated implementation module which may need to - * be loaded. - * - * @returns {boolean} true if an implementation separate - * from this definition should also be loaded - * @memberof platform/framework.Extension# - */ - hasImplementation: function () { - return definition.implementation !== undefined; - }, - /** - * Get the path to the AMD module which implements this - * extension. Will return undefined if there is no - * implementation associated with this extension. - * - * @memberof Extension# - * @returns {string} path to implementation, or undefined - * @memberof platform/framework.Extension# - */ - getImplementationPath: function () { - return definition.implementation ? - bundle.getSourcePath(definition.implementation) : - undefined; - }, - /** - * Get a log-friendly name for this extension; this will - * include both the key (machine-readable name for this - * extension) and the name (human-readable name for this - * extension.) - * @returns {string} log-friendly name for this extension - * @memberof platform/framework.Extension# - */ - getLogName: function () { - return logName; - }, - /** - * Get the plain definition of the extension. - * - * Note that this definition will have an additional "bundle" - * field which points back to the bundle which defined the - * extension, as a convenience. - * - * @memberof Extension# - * @returns {ExtensionDefinition} the plain definition of - * this extension, as read from the bundle - * declaration. - * @memberof platform/framework.Extension# - */ - getDefinition: function () { - return extensionDefinition; - } - - }; + this.logName = logName; + this.bundle = bundle; + this.category = category; + this.definition = definition; + this.extensionDefinition = extensionDefinition; } + /** + * Get the machine-readable identifier for this extension. + * + * @returns {string} the identifier for this extension + */ + Extension.prototype.getKey = function () { + return this.definition.key || "undefined"; + }; + + /** + * Get the bundle which declared this extension. + * + * @returns {Bundle} the declaring bundle + */ + Extension.prototype.getBundle = function () { + return this.bundle; + }; + + /** + * Get the category into which this extension falls. + * (e.g. "directives") + * + * @returns {string} the extension category + */ + Extension.prototype.getCategory = function () { + return this.category; + }; + + /** + * Check whether or not this extension should have an + * associated implementation module which may need to + * be loaded. + * + * @returns {boolean} true if an implementation separate + * from this definition should also be loaded + */ + Extension.prototype.hasImplementation = function () { + return this.definition.implementation !== undefined; + }; + + /** + * Get the path to the AMD module which implements this + * extension. Will return undefined if there is no + * implementation associated with this extension. + * + * @returns {string} path to implementation, or undefined + */ + Extension.prototype.getImplementationPath = function () { + return this.definition.implementation ? + this.bundle.getSourcePath(this.definition.implementation) : + undefined; + }; + + /** + * Get a log-friendly name for this extension; this will + * include both the key (machine-readable name for this + * extension) and the name (human-readable name for this + * extension.) + * + * @returns {string} log-friendly name for this extension + */ + Extension.prototype.getLogName = function () { + return this.logName; + }; + + /** + * Get the plain definition of the extension. + * + * Note that this definition will have an additional "bundle" + * field which points back to the bundle which defined the + * extension, as a convenience. + * + * @returns {ExtensionDefinition} the plain definition of + * this extension, as read from the bundle + * declaration. + */ + Extension.prototype.getDefinition = function () { + return this.extensionDefinition; + }; + return Extension; } diff --git a/platform/framework/src/register/CustomRegistrars.js b/platform/framework/src/register/CustomRegistrars.js index c9d40dd0ab..04c3bbce7a 100644 --- a/platform/framework/src/register/CustomRegistrars.js +++ b/platform/framework/src/register/CustomRegistrars.js @@ -37,131 +37,191 @@ define( * @constructor */ function CustomRegistrars(app, $log) { + this.app = app; + this.$log = $log; + } - // Used to create custom registration functions which map to - // named methods on Angular modules, which follow the normal - // app.method(key, [ deps..., function ]) pattern. - function CustomRegistrar(angularFunction) { - return function (extension, index) { - var key = extension.key, - dependencies = extension.depends || []; - - if (!key) { - $log.warn([ - "Cannot register ", - angularFunction, - " ", - index, - ", no key specified. ", - JSON.stringify(extension) - ].join("")); - } else { - $log.info([ - "Registering ", - angularFunction, - ": ", - key - ].join("")); - app[angularFunction]( - key, - dependencies.concat([extension]) - ); - } - }; - } - - function registerConstant(extension) { - var key = extension.key, - value = extension.value; - - if (typeof key === "string" && value !== undefined) { - $log.info([ - "Registering constant: ", - key, - " with value ", - value - ].join("")); - app.constant(key, value); - } else { - $log.warn([ - "Cannot register constant ", - key, - " with value ", - value - ].join("")); - } - - } - - // Custom registration function for extensions of category "runs" - function registerRun(extension) { - if (typeof extension === 'function') { - // Prepend dependencies, and schedule to run - app.run((extension.depends || []).concat([extension])); - } else { - // If it's not a function, no implementation was given - $log.warn([ - "Cannot register run extension from ", - (extension.bundle || {}).path, - "; no implementation." - ].join("")); - } - } - - // Custom registration function for extensions of category "route" - function registerRoute(extension) { - var route = Object.create(extension); - - // Adjust path for bundle - if (route.templateUrl) { - route.templateUrl = [ - route.bundle.path, - route.bundle.resources, - route.templateUrl - ].join(Constants.SEPARATOR); - } - - // Log the registration - $log.info("Registering route: " + (route.key || route.when)); - - // Register the route with Angular - app.config(['$routeProvider', function ($routeProvider) { - if (route.when) { - $routeProvider.when(route.when, route); - } else { - $routeProvider.otherwise(route); - } - }]); - } - - // Handle service compositing - function registerComponents(components) { - return new ServiceCompositor(app, $log) - .registerCompositeServices(components); - } - - // Utility; create a function which converts another function - // (which acts on single objects) to one which acts upon arrays. - function mapUpon(func) { - return function (array) { - return array.map(func); - }; - } - - // More like key-value pairs than methods; key is the - // name of the extension category to be handled, and the value - // is the function which handles it. - return { - constants: mapUpon(registerConstant), - routes: mapUpon(registerRoute), - directives: mapUpon(new CustomRegistrar("directive")), - controllers: mapUpon(new CustomRegistrar("controller")), - services: mapUpon(new CustomRegistrar("service")), - runs: mapUpon(registerRun), - components: registerComponents + // Utility; bind a function to a "this" pointer + function bind(fn, thisArg) { + return function () { + return fn.apply(thisArg, arguments); }; } + // Used to create custom registration functions which map to + // named methods on Angular modules, which follow the normal + // app.method(key, [ deps..., function ]) pattern. + function customRegistrar(angularFunction) { + return function (extension, index) { + var app = this.app, + $log = this.$log, + key = extension.key, + dependencies = extension.depends || []; + + if (!key) { + $log.warn([ + "Cannot register ", + angularFunction, + " ", + index, + ", no key specified. ", + JSON.stringify(extension) + ].join("")); + } else { + $log.info([ + "Registering ", + angularFunction, + ": ", + key + ].join("")); + app[angularFunction]( + key, + dependencies.concat([extension]) + ); + } + }; + } + + function registerConstant(extension) { + var app = this.app, + $log = this.$log, + key = extension.key, + value = extension.value; + + if (typeof key === "string" && value !== undefined) { + $log.info([ + "Registering constant: ", + key, + " with value ", + value + ].join("")); + app.constant(key, value); + } else { + $log.warn([ + "Cannot register constant ", + key, + " with value ", + value + ].join("")); + } + + } + + // Custom registration function for extensions of category "runs" + function registerRun(extension) { + var app = this.app, + $log = this.$log; + + if (typeof extension === 'function') { + // Prepend dependencies, and schedule to run + app.run((extension.depends || []).concat([extension])); + } else { + // If it's not a function, no implementation was given + $log.warn([ + "Cannot register run extension from ", + (extension.bundle || {}).path, + "; no implementation." + ].join("")); + } + } + + // Custom registration function for extensions of category "route" + function registerRoute(extension) { + var app = this.app, + $log = this.$log, + route = Object.create(extension); + + // Adjust path for bundle + if (route.templateUrl) { + route.templateUrl = [ + route.bundle.path, + route.bundle.resources, + route.templateUrl + ].join(Constants.SEPARATOR); + } + + // Log the registration + $log.info("Registering route: " + (route.key || route.when)); + + // Register the route with Angular + app.config(['$routeProvider', function ($routeProvider) { + if (route.when) { + $routeProvider.when(route.when, route); + } else { + $routeProvider.otherwise(route); + } + }]); + } + + // Handle service compositing + function registerComponents(components) { + var app = this.app, + $log = this.$log; + return new ServiceCompositor(app, $log) + .registerCompositeServices(components); + } + + // Utility; create a function which converts another function + // (which acts on single objects) to one which acts upon arrays. + function mapUpon(func) { + return function (array) { + return array.map(bind(func, this)); + }; + } + + // More like key-value pairs than methods; key is the + // name of the extension category to be handled, and the value + // is the function which handles it. + + /** + * Register constant values. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.constants = + mapUpon(registerConstant); + + /** + * Register Angular routes. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.routes = + mapUpon(registerRoute); + + /** + * Register Angular directives. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.directives = + mapUpon(customRegistrar("directive")); + + /** + * Register Angular controllers. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.controllers = + mapUpon(customRegistrar("controller")); + + /** + * Register Angular services. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.services = + mapUpon(customRegistrar("service")); + + /** + * Register functions which will run after bootstrapping. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.runs = + mapUpon(registerRun); + + /** + * Register components of composite services. + * @param {Array} extensions the resolved extensions + */ + CustomRegistrars.prototype.components = + registerComponents; + return CustomRegistrars; } ); diff --git a/platform/framework/src/register/ExtensionRegistrar.js b/platform/framework/src/register/ExtensionRegistrar.js index bc6f58084e..352d3b9f3f 100644 --- a/platform/framework/src/register/ExtensionRegistrar.js +++ b/platform/framework/src/register/ExtensionRegistrar.js @@ -47,14 +47,41 @@ define( // Track which extension categories have already been registered. // Exceptions will be thrown if the same extension category is // registered twice. - var registeredCategories = {}; + this.registeredCategories = {}; + this.customRegistrars = customRegistrars || {}; + this.app = app; + this.sorter = sorter; + this.$log = $log; + } + + /** + * Register a group of resolved extensions with the Angular + * module managed by this registrar. + * + * For convenient chaining (particularly from the framework + * initializer's perspective), this returns the Angular + * module with which extensions were registered. + * + * @param {Object.} extensionGroup an object + * containing key-value pairs, where keys are extension + * categories and values are arrays of resolved + * extensions + * @returns {angular.Module} the application module with + * which extensions were registered + */ + ExtensionRegistrar.prototype.registerExtensions = function (extensionGroup) { + var registeredCategories = this.registeredCategories, + customRegistrars = this.customRegistrars, + app = this.app, + sorter = this.sorter, + $log = this.$log; // Used to build unique identifiers for individual extensions, // so that these can be registered separately with Angular function identify(category, extension, index) { var name = extension.key ? - ("extension-" + extension.key + "#" + index) : - ("extension#" + index); + ("extension-" + extension.key + "#" + index) : + ("extension#" + index); return category + "[" + name + "]"; } @@ -76,8 +103,8 @@ define( function makeServiceArgument(category, extension) { var dependencies = extension.depends || [], factory = (typeof extension === 'function') ? - new PartialConstructor(extension) : - staticFunction(extension); + new PartialConstructor(extension) : + staticFunction(extension); return dependencies.concat([factory]); } @@ -129,9 +156,9 @@ define( // an extension category (e.g. is suffixed by []) function isExtensionDependency(dependency) { var index = dependency.indexOf( - Constants.EXTENSION_SUFFIX, - dependency.length - Constants.EXTENSION_SUFFIX.length - ); + Constants.EXTENSION_SUFFIX, + dependency.length - Constants.EXTENSION_SUFFIX.length + ); return index !== -1; } @@ -153,8 +180,8 @@ define( (extension.depends || []).filter( isExtensionDependency ).forEach(function (dependency) { - needed[dependency] = true; - }); + needed[dependency] = true; + }); }); // Remove categories which have been provided @@ -174,53 +201,29 @@ define( findEmptyExtensionDependencies( extensionGroup ).forEach(function (name) { - $log.info("Registering empty extension category " + name); - app.factory(name, [staticFunction([])]); - }); + $log.info("Registering empty extension category " + name); + app.factory(name, [staticFunction([])]); + }); } - function registerExtensionGroup(extensionGroup) { - // Announce we're entering a new phase - $log.info("Registering extensions..."); + // Announce we're entering a new phase + $log.info("Registering extensions..."); - // Register all declared extensions by category - Object.keys(extensionGroup).forEach(function (category) { - registerExtensionsForCategory( - category, - sorter.sort(extensionGroup[category]) - ); - }); + // Register all declared extensions by category + Object.keys(extensionGroup).forEach(function (category) { + registerExtensionsForCategory( + category, + sorter.sort(extensionGroup[category]) + ); + }); - // Also handle categories which are needed but not declared - registerEmptyDependencies(extensionGroup); + // Also handle categories which are needed but not declared + registerEmptyDependencies(extensionGroup); - // Return the application to which these extensions - // have been registered - return app; - } - - customRegistrars = customRegistrars || {}; - - return { - /** - * Register a group of resolved extensions with the Angular - * module managed by this registrar. - * - * For convenient chaining (particularly from the framework - * initializer's perspective), this returns the Angular - * module with which extensions were registered. - * - * @param {Object.} extensionGroup an object - * containing key-value pairs, where keys are extension - * categories and values are arrays of resolved - * extensions - * @returns {angular.Module} the application module with - * which extensions were registered - * @memberof platform/framework.ExtensionRegistrar# - */ - registerExtensions: registerExtensionGroup - }; - } + // Return the application to which these extensions + // have been registered + return app; + }; return ExtensionRegistrar; } diff --git a/platform/framework/src/register/ExtensionSorter.js b/platform/framework/src/register/ExtensionSorter.js index f10d7b62ce..9bc902da9e 100644 --- a/platform/framework/src/register/ExtensionSorter.js +++ b/platform/framework/src/register/ExtensionSorter.js @@ -38,6 +38,17 @@ define( * @constructor */ function ExtensionSorter($log) { + this.$log = $log; + } + + /** + * Sort extensions according to priority. + * + * @param {object[]} extensions array of resolved extensions + * @returns {object[]} the same extensions, in priority order + */ + ExtensionSorter.prototype.sort = function (extensions) { + var $log = this.$log; // Handle unknown or malformed priorities specified by extensions function unrecognizedPriority(extension) { @@ -68,7 +79,7 @@ define( // Should be a number; otherwise, issue a warning and // fall back to default priority level. return (typeof priority === 'number') ? - priority : unrecognizedPriority(extension); + priority : unrecognizedPriority(extension); } // Attach a numeric priority to an extension; this is done in @@ -98,22 +109,11 @@ define( return (b.priority - a.priority) || (a.index - b.index); } - return { - /** - * Sort extensions according to priority. - * - * @param {object[]} extensions array of resolved extensions - * @returns {object[]} the same extensions, in priority order - * @memberof platform/framework.ExtensionSorter# - */ - sort: function (extensions) { - return (extensions || []) - .map(prioritize) - .sort(compare) - .map(deprioritize); - } - }; - } + return (extensions || []) + .map(prioritize) + .sort(compare) + .map(deprioritize); + }; return ExtensionSorter; } diff --git a/platform/framework/src/register/ServiceCompositor.js b/platform/framework/src/register/ServiceCompositor.js index c76beeba49..349e0c92e4 100644 --- a/platform/framework/src/register/ServiceCompositor.js +++ b/platform/framework/src/register/ServiceCompositor.js @@ -37,8 +37,30 @@ define( * @constructor */ function ServiceCompositor(app, $log) { - var latest = {}, - providerLists = {}; // Track latest services registered + this.latest = {}; + this.providerLists = {}; // Track latest services registered + this.app = app; + this.$log = $log; + } + + /** + * Register composite services with Angular. This will build + * up a dependency hierarchy between providers, aggregators, + * and/or decorators, such that a dependency upon the service + * type they expose shall be satisfied by their fully-wired + * whole. + * + * Note that this method assumes that a complete set of + * components shall be provided. Multiple calls to this + * method may not behave as expected. + * + * @param {Array} components extensions of category component + */ + ServiceCompositor.prototype.registerCompositeServices = function (components) { + var latest = this.latest, + providerLists = this.providerLists, + app = this.app, + $log = this.$log; // Log a warning; defaults to "no service provided by" function warn(extension, category, message) { @@ -200,33 +222,13 @@ define( registerLatest(); } - // Initial point of entry; just separate components by type - function registerCompositeServices(components) { - registerComposites( - components.filter(hasType("provider")), - components.filter(hasType("aggregator")), - components.filter(hasType("decorator")) - ); - } - - return { - /** - * Register composite services with Angular. This will build - * up a dependency hierarchy between providers, aggregators, - * and/or decorators, such that a dependency upon the service - * type they expose shall be satisfied by their fully-wired - * whole. - * - * Note that this method assumes that a complete set of - * components shall be provided. Multiple calls to this - * method may not behave as expected. - * - * @param {Array} components extensions of category component - * @memberof platform/framework.ServiceCompositor# - */ - registerCompositeServices: registerCompositeServices - }; - } + // Initial point of entry; split into three component types. + registerComposites( + components.filter(hasType("provider")), + components.filter(hasType("aggregator")), + components.filter(hasType("decorator")) + ); + }; return ServiceCompositor; } diff --git a/platform/framework/src/resolve/BundleResolver.js b/platform/framework/src/resolve/BundleResolver.js index 02bb76110d..4360764aee 100644 --- a/platform/framework/src/resolve/BundleResolver.js +++ b/platform/framework/src/resolve/BundleResolver.js @@ -38,8 +38,27 @@ define( * @constructor */ function BundleResolver(extensionResolver, requireConfigurator, $log) { + this.extensionResolver = extensionResolver; + this.requireConfigurator = requireConfigurator; + this.$log = $log; + } - /** + /** + * Resolve all extensions exposed by these bundles. + * + * @param {Bundle[]} bundles the bundles to resolve + * @returns {Promise.>} an promise + * for an object containing + * key-value pairs, where keys are extension + * categories and values are arrays of resolved + * extensions belonging to those categories + */ + BundleResolver.prototype.resolveBundles = function (bundles) { + var extensionResolver = this.extensionResolver, + requireConfigurator = this.requireConfigurator, + $log = this.$log; + + /* * Merge resolved bundles (where each is expressed as an * object containing key-value pairs, where keys are extension * categories and values are arrays of resolved extensions) @@ -99,28 +118,13 @@ define( .then(giveResult); } - return { - /** - * Resolve all extensions exposed by these bundles. - * - * @param {Bundle[]} bundles the bundles to resolve - * @returns {Promise.>} an promise - * for an object containing - * key-value pairs, where keys are extension - * categories and values are arrays of resolved - * extensions belonging to those categories - * @memberof platform/framework.BundleResolver# - */ - resolveBundles: function (bundles) { - // First, make sure Require is suitably configured - requireConfigurator.configure(bundles); + // First, make sure Require is suitably configured + requireConfigurator.configure(bundles); - // Then, resolve all extension implementations. - return Promise.all(bundles.map(resolveBundle)) - .then(mergeResolvedBundles); - } - }; - } + // Then, resolve all extension implementations. + return Promise.all(bundles.map(resolveBundle)) + .then(mergeResolvedBundles); + }; return BundleResolver; } diff --git a/platform/framework/src/resolve/ExtensionResolver.js b/platform/framework/src/resolve/ExtensionResolver.js index 126b9ab3f9..e4c2710c0f 100644 --- a/platform/framework/src/resolve/ExtensionResolver.js +++ b/platform/framework/src/resolve/ExtensionResolver.js @@ -39,6 +39,27 @@ define( * @constructor */ function ExtensionResolver(loader, $log) { + this.loader = loader; + this.$log = $log; + } + + /** + * Resolve the provided extension; this will give a promise + * for the extension's implementation, if one has been + * specified, or for the plain definition of the extension + * otherwise. The plain definition will also be given + * if the implementation fails to load for some reason. + * + * All key-value pairs from the extension definition + * will additionally be attached to any loaded implementation. + * + * @param {Extension} extension the extension to resolve + * @returns {Promise} a promise for the resolved extension + */ + ExtensionResolver.prototype.resolve = function (extension) { + var loader = this.loader, + $log = this.$log; + function loadImplementation(extension) { var implPath = extension.getImplementationPath(), implPromise = loader.load(implPath), @@ -57,8 +78,8 @@ define( // loaded implementation. function attachDefinition(impl) { var result = (typeof impl === 'function') ? - constructorFor(impl) : - Object.create(impl); + constructorFor(impl) : + Object.create(impl); // Copy over static properties Object.keys(impl).forEach(function (k) { @@ -84,11 +105,11 @@ define( function handleError(err) { // Build up a log message from parts var message = [ - "Could not load implementation for extension ", - extension.getLogName(), - " due to ", - err.message - ].join(""); + "Could not load implementation for extension ", + extension.getLogName(), + " due to ", + err.message + ].join(""); // Log that the extension was not loaded $log.warn(message); @@ -107,33 +128,16 @@ define( return implPromise.then(attachDefinition, handleError); } - return { - /** - * Resolve the provided extension; this will give a promise - * for the extension's implementation, if one has been - * specified, or for the plain definition of the extension - * otherwise. The plain definition will also be given - * if the implementation fails to load for some reason. - * - * All key-value pairs from the extension definition - * will additionally be attached to any loaded implementation. - * - * @param {Extension} extension - * @memberof platform/framework.ExtensionResolver# - */ - resolve: function (extension) { - // Log that loading has begun - $log.info([ - "Resolving extension ", - extension.getLogName() - ].join("")); + // Log that loading has begun + $log.info([ + "Resolving extension ", + extension.getLogName() + ].join("")); - return extension.hasImplementation() ? - loadImplementation(extension) : - Promise.resolve(extension.getDefinition()); - } - }; - } + return extension.hasImplementation() ? + loadImplementation(extension) : + Promise.resolve(extension.getDefinition()); + }; return ExtensionResolver; } diff --git a/platform/framework/src/resolve/ImplementationLoader.js b/platform/framework/src/resolve/ImplementationLoader.js index b76c5035e8..c9f1ae8bc9 100644 --- a/platform/framework/src/resolve/ImplementationLoader.js +++ b/platform/framework/src/resolve/ImplementationLoader.js @@ -39,31 +39,27 @@ define( * @param {*} $log Angular's logging service */ function ImplementationLoader(require) { - function loadModule(path) { - return new Promise(function (fulfill, reject) { - require([path], fulfill, reject); - }); - } - - return { - /** - * Load an extension's implementation; or, equivalently, - * load an AMD module. This is fundamentally similar - * to a call to RequireJS, except that the result is - * wrapped in a promise. The promise will be fulfilled - * with the loaded module, or rejected with the error - * reported by Require. - * - * @method - * @memberof ImplementationLoader# - * @param {string} path the path to the module to load - * @returns {Promise} a promise for the specified module. - * @memberof platform/framework.ImplementationLoader# - */ - load: loadModule - }; + this.require = require; } + /** + * Load an extension's implementation; or, equivalently, + * load an AMD module. This is fundamentally similar + * to a call to RequireJS, except that the result is + * wrapped in a promise. The promise will be fulfilled + * with the loaded module, or rejected with the error + * reported by Require. + * + * @param {string} path the path to the module to load + * @returns {Promise} a promise for the specified module. + */ + ImplementationLoader.prototype.load = function loadModule(path) { + var require = this.require; + return new Promise(function (fulfill, reject) { + require([path], fulfill, reject); + }); + }; + return ImplementationLoader; } ); diff --git a/platform/framework/src/resolve/RequireConfigurator.js b/platform/framework/src/resolve/RequireConfigurator.js index 231a4c316f..f55ac559c1 100644 --- a/platform/framework/src/resolve/RequireConfigurator.js +++ b/platform/framework/src/resolve/RequireConfigurator.js @@ -35,79 +35,79 @@ define( * @param requirejs an instance of RequireJS */ function RequireConfigurator(requirejs) { - // Utility function to clone part of a bundle definition - function clone(obj) { - return JSON.parse(JSON.stringify(obj)); - } - - // Look up module configuration from the bundle definition. - // This will adjust paths to libraries as-needed. - function getConfiguration(bundle) { - var configuration = bundle.getConfiguration(); - - // Adjust paths to point to libraries - if (configuration.paths) { - // Don't modify the actual bundle definition... - configuration = clone(configuration); - // ...replace values in a clone instead. - Object.keys(configuration.paths).forEach(function (path) { - configuration.paths[path] = - bundle.getLibraryPath(configuration.paths[path]); - }); - } - - return configuration; - } - - // Build up paths and shim values from multiple bundles; - // this is sensitive to the value from baseConfiguration - // passed via reduce in buildConfiguration below, insofar - // as it assumes paths and shim will have initial empty values. - function mergeConfigurations(base, next) { - ["paths", "shim"].forEach(function (k) { - Object.keys(next[k] || {}).forEach(function (p) { - base[k][p] = next[k][p]; - }); - }); - return base; - } - - // Build a configuration object, to pass to requirejs.config, - // based on the defined configurations for all bundles. - // The paths and shim properties from all bundles will be - // merged to allow one requirejs.config call. - function buildConfiguration(bundles) { - // Provide an initial requirejs configuration... - var baseConfiguration = { - baseUrl: "", - paths: {}, - shim: {} - }, - // ...and pull out all bundle-specific parts - bundleConfigurations = bundles.map(getConfiguration); - - // Reduce this into one configuration object. - return bundleConfigurations.reduce( - mergeConfigurations, - baseConfiguration - ); - } - - return { - /** - * Configure RequireJS to utilize any path/shim definitions - * provided by these bundles. - * - * @param {Bundle[]} the bundles to include in this - * configuration - * @memberof platform/framework.RequireConfigurator# - */ - configure: function (bundles) { - return requirejs.config(buildConfiguration(bundles)); - } - }; + this.requirejs = requirejs; } + // Utility function to clone part of a bundle definition + function clone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + + // Look up module configuration from the bundle definition. + // This will adjust paths to libraries as-needed. + function getConfiguration(bundle) { + var configuration = bundle.getConfiguration(); + + // Adjust paths to point to libraries + if (configuration.paths) { + // Don't modify the actual bundle definition... + configuration = clone(configuration); + // ...replace values in a clone instead. + Object.keys(configuration.paths).forEach(function (path) { + configuration.paths[path] = + bundle.getLibraryPath(configuration.paths[path]); + }); + } + + return configuration; + } + + // Build up paths and shim values from multiple bundles; + // this is sensitive to the value from baseConfiguration + // passed via reduce in buildConfiguration below, insofar + // as it assumes paths and shim will have initial empty values. + function mergeConfigurations(base, next) { + ["paths", "shim"].forEach(function (k) { + Object.keys(next[k] || {}).forEach(function (p) { + base[k][p] = next[k][p]; + }); + }); + return base; + } + + // Build a configuration object, to pass to requirejs.config, + // based on the defined configurations for all bundles. + // The paths and shim properties from all bundles will be + // merged to allow one requirejs.config call. + function buildConfiguration(bundles) { + // Provide an initial requirejs configuration... + var baseConfiguration = { + baseUrl: "", + paths: {}, + shim: {} + }, + // ...and pull out all bundle-specific parts + bundleConfigurations = bundles.map(getConfiguration); + + // Reduce this into one configuration object. + return bundleConfigurations.reduce( + mergeConfigurations, + baseConfiguration + ); + } + + /** + * Configure RequireJS to utilize any path/shim definitions + * provided by these bundles. + * + * @param {Bundle[]} the bundles to include in this + * configuration + * @memberof platform/framework.RequireConfigurator# + */ + RequireConfigurator.prototype.configure = function (bundles) { + return this.requirejs.config(buildConfiguration(bundles)); + }; + return RequireConfigurator; }