Section 10.3. Module Utilities


10.3. Module Utilities

This section presents an extended example (Example 10-5) of a module of module-related utilities. The Module.createNamespace( ) utility handles namespace creation and error checking. A module author might use it like this:

 // Create a namespace for our module Module.createNamespace("com.davidflanagan.Class"); // Now start populating the namespace com.davidflanagan.Class.define = function(data) { /* code here */ }; com.davidflanagan.Class.provides = function(o, c) { /* code here */ }; 

The Module.require( ) function checks for the presence of the specified version (or later) of a named module and throws an error if it does not exist. Use it like this:

 // This Complex module requires the Class module to be loaded first Module.require("com.davidflanagan.Class", 1.0); 

The Module.importSymbols( ) function simplifies the task of importing symbols into the global namespace or another specified namespace. Here are examples of its use:

 // Import the default set of Module symbols to the global namespace // One of these defualt symbols is importSymbols itself Module.importSymbols(Module); // Note we pass the namespace, not module name // Import the Complex class into the global namespace importSymbols(com.davidflanagan.Complex); // Import the com.davidflanagan.Class.define( ) method to a Class object var Class = {}; importSymbols(com.davidflanagan.Class, Class, "define"); 

Finally, the Module.registerInitializationFunction( ) allows a module to register a function of initialization code to be run at some later time.[*] When this function is used in client-side JavaScript, an event handler is automatically registered to invoke all initialization functions for all loaded modules when the document finishes loading. When used in other, nonclient-side contexts, the initialization functions are not automatically invoked, but can be explicitly invoked with Module.runInitializationFunctions( ).

[*] See also Example 17-6 for a similar initialization function registration utility.

The Module module is shown in Example 10-5. This example is a long one, but the code repays careful study. Documentation and full details of each utility function are found in the code.

Example 10-5. A module of module-related utilities

 /**  * Module.js: module and namspace utilities  *  * This is a module of module-related utility functions that are  * compatible with JSAN-type modules.  * This module defines the namespace Module.  */ // Make sure we haven't already been loaded var Module; if (Module && (typeof Module != "object" || Module.NAME))     throw new Error("Namespace 'Module' already exists"); // Create our namespace Module = {}; // This is some metainformation about this namespace Module.NAME = "Module";    // The name of this namespace Module.VERSION = 0.1;      // The version of this namespace // This is the list of public symbols that we export from this namespace. // These are of interest to programers who use modules. Module.EXPORT = ["require", "importSymbols"]; // These are other symbols we are willing to export. They are ones normally // used only by module authors and are not typically imported. Module.EXPORT_OK = ["createNamespace", "isDefined",                     "registerInitializationFunction",                     "runInitializationFunctions",                     "modules", "globalNamespace"]; // Now start adding symbols to the namespace Module.globalNamespace = this;  // So we can always refer to the global scope Module.modules = { "Module": Module };  // Module name->namespace map. /**  * This function creates and returns a namespace object for the  * specified name and does useful error checking to ensure that the  * name does not conflict with any previously loaded module. It  * throws an error if the namespace already exists or if any of the  * property components of the namespace exist and are not objects.  *  * Sets a NAME property of the new namespace to its name.  * If the version argument is specified, set the VERSION property  * of the namespace.  *  * A mapping for the new namespace is added to the Module.modules object  */ Module.createNamespace = function(name, version) {     // Check name for validity.  It must exist, and must not begin or     // end with a period or contain two periods in a row.     if (!name) throw new Error("Module.createNamespace( ): name required");     if (name.charAt(0) == '.' ||         name.charAt(name.length-1) == '.' ||         name.indexOf("..") != -1)         throw new Error("Module.createNamespace( ): illegal name: " + name);     // Break the name at periods and create the object hierarchy we need     var parts = name.split('.');     // For each namespace component, either create an object or ensure that     // an object by that name already exists.     var container = Module.globalNamespace;     for(var i = 0; i < parts.length; i++) {         var part = parts[i];         // If there is no property of container with this name, create         // an empty object.         if (!container[part]) container[part] = {};         else if (typeof container[part] != "object") {             // If there is already a property, make sure it is an object             var n = parts.slice(0,i).join('.');             throw new Error(n + " already exists and is not an object");         }         container = container[part];     }     // The last container traversed above is the namespace we need.     var namespace = container;     // It is an error to define a namespace twice. It is okay if our     // namespace object already exists, but it must not already have a     // NAME property defined.     if (namespace.NAME) throw new Error("Module "+name+" is already defined");     // Initialize name and version fields of the namespace     namespace.NAME = name;     if (version) namespace.VERSION = version;     // Register this namespace in the map of all modules     Module.modules[name] = namespace;     // Return the namespace object to the caller     return namespace; } /**  * Test whether the module with the specified name has been defined.  * Returns true if it is defined and false otherwise.  */ Module.isDefined = function(name) {     return name in Module.modules; }; /**  * This function throws an error if the named module is not defined  * or if it is defined but its version is less than the specified version.  * If the namespace exists and has a suitable version, this function simply  * returns without doing anything. Use this function to cause a fatal  * error if the modules that your code requires are not present.  */ Module.require = function(name, version) {     if (!(name in Module.modules)) {         throw new Error("Module " + name + " is not defined");     }     // If no version was specified, there is nothing to check     if (!version) return;     var n = Module.modules[name];     // If the defined version is less than the required version or if     // the namespace does not declare any version, throw an error.     if (!n.VERSION || n.VERSION < version)     throw new Error("Module " + name + " has version " +                     n.VERSION + " but version " + version +                     " or greater is required."); }; /**  * This function imports symbols from a specified module.  By default, it  * imports them into the global namespace, but you may specify a different  * destination as the second argument.  *  * If no symbols are explicitly specified, the symbols in the EXPORT  * array of the module will be imported. If no such array is defined,  * and no EXPORT_OK is defined, all symbols from the module will be imported.  *  * To import an explicitly specified set of symbols, pass their names as  * arguments after the module and the optional destination namespace. If the  * modules defines an EXPORT or EXPORT_OK array, symbols will be imported  * only if they are listed in one of those arrays.  */ Module.importSymbols = function(from) {     // Make sure that the module is correctly specified. We expect the     // module's namespace object but will try with a string, too     if (typeof from == "string") from = Module.modules[from];     if (!from || typeof from != "object")         throw new Error("Module.importSymbols( ): " +                         "namespace object required");     // The source namespace may be followed by an optional destination     // namespace and the names of one or more symbols to import;     var to = Module.globalNamespace; // Default destination     var symbols = [];                // No symbols by default     var firstsymbol = 1;             // Index in arguments of first symbol name     // See if a destination namespace is specified     if (arguments.length > 1 && typeof arguments[1] == "object") {         if (arguments[1] != null) to = arguments[1];         firstsymbol = 2;     }     // Now get the list of specified symbols     for(var a = firstsymbol; a < arguments.length; a++)         symbols.push(arguments[a]);     // If we were not passed any symbols to import, import a set defined     // by the module, or just import all of them.     if (symbols.length == 0) {         // If the module defines an EXPORT array, import         // the symbols in that array.         if (from.EXPORT) {             for(var i = 0; i < from.EXPORT.length; i++) {                 var s = from.EXPORT[i];                 to[s] = from[s];             }             return;         }         // Otherwise if the modules does not define an EXPORT_OK array,         // just import everything in the module's namespace         else if (!from.EXPORT_OK) {             for(s in from) to[s] = from[s];             return;         }     }     // If we get here, we have an explicitly specified array of symbols     // to import. If the namespace defines EXPORT and/or EXPORT_OK arrays,     // ensure that each symbol is listed before importing it.     // Throw an error if a requested symbol does not exist or if     // it is not allowed to be exported.     var allowed;     if (from.EXPORT || from.EXPORT_OK) {         allowed = {};         // Copy allowed symbols from arrays to properties of an object.         // This allows us to test for an allowed symbol more efficiently.         if (from.EXPORT)             for(var i = 0; i < from.EXPORT.length; i++)                 allowed[from.EXPORT[i]] = true;         if (from.EXPORT_OK)             for(var i = 0; i < from.EXPORT_OK.length; i++)                 allowed[from.EXPORT_OK[i]] = true;     }     // Import the symbols     for(var i = 0; i < symbols.length; i++) {         var s = symbols[i];              // The name of the symbol to import         if (!(s in from))                // Make sure it exists             throw new Error("Module.importSymbols( ): symbol " + s +                             " is not defined");         if (allowed && !(s in allowed))  // Make sure it is a public symbol             throw new Error("Module.importSymbols( ): symbol " + s +                             " is not public and cannot be imported.");         to[s] = from[s];                 // Import it     } }; // Modules use this function to register one or more initialization functions Module.registerInitializationFunction = function(f) {     // Store the function in the array of initialization functions     Module._initfuncs.push(f);     // If we have not yet registered an onload event handler, do so now.     Module._registerEventHandler( ); } // A function to invoke all registered initialization functions. // In client-side JavaScript, this will automatically be called in // when the document finished loading. In other contexts, you must // call it explicitly. Module.runInitializationFunctions = function( ) {     // Run each initialization function, catching and ignoring exceptions     // so that a failure by one module does not prevent other modules     // from being initialized.     for(var i = 0; i < Module._initfuncs.length; i++) {         try { Module._initfuncs[i]( ); }         catch(e) { /* ignore exceptions */}     }     // Erase the array so the functions are never called more than once.     Module._initfuncs.length = 0; } // A private array holding initialization functions to invoke later Module._initfuncs = []; // If we are loaded into a web browser, this private function registers an // onload event handler to run the initialization functions for all loaded // modules. It does not allow itself to be called more than once. Module._registerEventHandler = function( ) {     var clientside =   // Check for well-known client-side properties         "window" in Module.globalNamespace &&         "navigator" in window;     if (clientside) {         if (window.addEventListener) {  // W3C DOM standard event registration             window.addEventListener("load", Module.runInitializationFunctions,                                     false);         }         else if (window.attachEvent) {  // IE5+ event registration             window.attachEvent("onload", Module.runInitializationFunctions);         }         else {             // IE4 and old browsers             // If the <body> defines an onload tag, this event listener             // will be overwritten and never get called.             window.onload = Module.runInitializationFunctions;         }     }     // The function overwrites itself with an empty function so it never     // gets called more than once.     Module._registerEventHandler = function( ) {}; } 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net