UIZE JavaScript Framework

GUIDES Javascript Modules

1. Introduction

The UIZE JavaScript framework implements systems to facilitate modular programming, such as dependency resolution, dynamic module loading, and more.

In the UIZE JavaScript Framework, packages, class files, class extensions, and other kinds of code modules are declared using the Uize.module static method. This method allows for the declaration of dependencies and helps with dependency resolution. A builder parameter lets you specify a function that will build the module. This gives the module mechanism control over when the module is built, allowing it resolve all declared dependencies before attempting to build the module.

2. A Simple Example

In the following example, a subclass module is being declared.

EXAMPLE

Uize.module ({
  name:'Uize.Widget.MagView',
  required:[
    'Uize.Node',
    'Uize.Fade',
    'Uize.Widget.ImagePort'
  ],
  builder:function (_superclass) {
    'use strict';

    return _superclass.subclass ({
      alphastructor:function () {
        // ... ... ...
      },

      instanceMethods:{
        // ... ... ...
      },

      stateProperties:{
        // ... ... ...
      }
    });
  }
});

In the above example, the Uize.Widget.MagView module is being declared. Its name is specified by the name parameter. The modules that it immediately requires in order to be built are listed in the required parameter. And the function that is responsible for building and returning a reference to the module is specified by the builder parameter.

In this particular example, a subclass is being created. Uize.Widget.MagView is a subclass of Uize.Widget. The builder function receives a reference to the superclass, uses it to create a subclass, and then returns a reference to that subclass. The Uize.module method takes the result returned by the builder function and assigns it to the property MagView of the Uize.Widget host. In this example, the host is assumed to be the superclass, since there is no value specified for the optional superclass parameter (more on that later).

3. A Mechanism All Of Its Own

The module mechanism is distinct from - but supportive of - the class inheritance mechanism.

It is worth emphasizing that the module mechanism is primarily a means of declaring dependencies for modules of code, and also facilitates building of the module when dependencies are satisfied. The module mechanism is supportive of subclassing, rather than being required for subclassing. Moreover, modules can be used to declare code that is neither a subclass nor a package. The module mechanism should, therefore, be considered orthogonal to the class inheritance mechanism of the UIZE JavaScript Framework.

4. Uize.module

The Uize.module static method allows us to declare a module in the UIZE JavaScript Framework.

Modules can be of a number of key types, as discussed in the section Types of Modules. In a common case, a module declaration is used to declare a subclass.

SYNTAX

Uize.module ({
  name:moduleNameSTR,           // omit for anonymous modules
  superclass:superclassNameSTR, // omit for package modules, or if host is superclass
  required:modulesSTRorARRAY,   // omit when only host and superclass are required
  builder:builderFUNC           // omit for library modules and namespace modules
});

4.1. Parameters

To be technically correct, the Uize.module method strictly takes just one parameter that is an object.

This object should contain a number of properties, as shown in the syntax declaration. So all the "parameters" described below are actually properties of this one object bundle that the method accepts. For simplicity's sake, we will refer to these properties as parameters.

4.1.1. name

The name parameter lets you specify a name for the module.

[host].[guest]

The name of a module is comprised of two parts: the "host", and the "guest". The "host" is essentially an object that acts as a host to the "guest", which is a property that is an object reference to the module.

In the simple example of a module with the name 'Uize.Widget.MagView', the "host" is Uize.Widget and the "guest" is MagView. Essentially, the MagView property of the Uize.Widget module is a reference to the mag view module, which is fully dereferenced in code as Uize.Widget.MagView. The "host" for a module is always the object path contained in the module name, up until the last segment, and the "guest" is always the last segment.

NOTES

When no name is specified for a module, it is considered an Anonymous Module.
When the module name contains no "host" portion (as in the example MyNamespace), then the window object is taken as the host.

4.1.2. superclass

The optional superclass parameter lets you specify the name of a module that should be used as the superclass for the new module being declared.

This parameter is useful for when the superclass is not the same as the host. This occurs often if the host is a namespace for a module that may be a subclass of some module from some other namespace. When no superclass parameter is specified, the host is assumed to be the superclass. An object reference to the superclass is passed as the only parameter to the builder function specified by the builder parameter.

NOTES

This parameter is only applicable when declaring a Subclass Module and is meaningless when declaring modules of the other types discussed in the section Types of Modules.

4.1.3. required

The required parameter lets you specify a list of modules that are required in order for the new module being declared to function effectively. In a twist of irony, the required parameter is optional.

The value of the required parameter can be a string, being a comma separated list of module names, or it can be an array of strings that are module names.

For example, the following...

required:'Uize.Node,Uize.Color,Uize.Data,Uize.Fade'

...is equivalent to...

required:['Uize.Node','Uize.Color','Uize.Data','Uize.Fade']

Using the array form has the advantage of permitting prettiness, by allowing the required list to be spread across multiple lines, as follows...

required:[
  'Uize.Node',
  'Uize.Color',
  'Uize.Data',
  'Uize.Fade'
]

4.1.3.1. Don't Require Implicit Dependencies

It is redundant to require a module that is implicitly required.

DON'T REQUIRE UIZE

This should be obvious, since the Uize module needs to already be defined in order to be able to call the Uize.module static method.

DON'T REQUIRE HOST

When declaring a named module, it is implicit that the host is required. So, for example, when declaring a module with the name 'Uize.Widget.MagView', it is implicit that the module Uize.Widget is required, and so that module does not need to be specified in a required list. The module could not be assigned as the property MagView on Uize.Widget, were the latter module not already defined.

DON'T REQUIRE SUPERCLASS

Similarly, for a module that declares a subclass where the superclass is different to the host and is specified in the optional superclass parameter, the module that is specified as being the superclass does not need to be specified in the required list. Its requirement is a given.

4.1.3.2. Require Only Immediate Dependencies

Require only what a module immediately or directly needs.

DON'T REQUIRE ON BEHALF OF REQUIRED MODULES

It is neither necessary nor appropriate to include in the required list modules that are not immediately required by the current module but that you know are required by other modules that the current module requires.

So, for example, if you're declaring a module that requires Uize.Widget.Bar.Slider, it is only necessary for the required list to include 'Uize.Widget.Bar.Slider'. It is not necessary - or appropriate - to include 'Uize.Widget.Bar', or any other modules that you know are needed by the Uize.Widget.Bar.Slider module. It is not the responsibility of your module to resolve all the dependencies of other modules. It is the module mechanism's responsibility to do that for you, with the help of good required lists in the modules you require.

So, leave it to the module declarations of other modules to require what they care about. It is the responsibility of the Uize.Widget.Bar.Slider module, for example, to have a Uize.module declaration that lists its immediate requirements. Implementations of the modules you use can change over time. The dependencies of modules of code should remain encapsulated within those modules of code.

DON'T REQUIRE DEFERRED DEPENDENCIES

If the implementation of a module will, at some point in the execution of its code, dynamically load additional modules - possibly by using the Uize.module method to wrap a deeply nested, Anonymous Module of code - it is not necessary to include those modules needed later in the required list. It is only necessary to require the modules that are immediately needed in order for the module's implementation to function correctly.

DO REQUIRE ALL IMMEDIATE DEPENDENCIES

The corollary to the rule of "don't require on behalf of required modules", is don't avoid requiring modules that you immediately use - just because you know that other modules you're using require them.

A prime example is the much used Uize.Node module. The Uize.Widget base class relies heavily upon the Uize.Node package in its implementation. Therefore, every widget that is a subclass of Uize.Widget is effectively requiring Uize.Node. However, even if you are declaring a module that is a subclass of Uize.Widget and the module's code directly accesses methods of Uize.Node, then the required list should include 'Uize.Node'.

It's a slippery slope when you start weaving too much knowledge about other module's implementations into your own implementation and building in questionable assumptions that may later break. For argument sake, the structure of the framework could change over time. In one scenario, a Uize.Node package may be kept for backwards compability while the Uize.Widget implementation may be migrated away from using it. You don't want assumptions about the implementation of the Uize.Widget base class to break all widgets implemented for the framework.

4.1.3.3. Avoid Circular Dependencies

Avoid circular dependencies between two or more modules.

Circular dependencies are the module equivalent of an infinite loop - they will cause modules to never load. Let's consider an example where a module MyNamespace.MyModule1 requires module MyNamespace.MyModule2, and where MyNamespace.MyModule2 in turn requires MyNamespace.MyModule1...

JAVASCRIPT MODULE FILE: MyNamespace.MyModule1.js

Uize.module ({
  name:'MyNamespace.MyModule1',
  required:'MyNamespace.MyModule2',
  builder:function () {
    // ...
  }
});

JAVASCRIPT MODULE FILE: MyNamespace.MyModule2.js

Uize.module ({
  name:'MyNamespace.MyModule2',
  required:'MyNamespace.MyModule1',
  builder:function () {
    // ...
  }
});

As you can see from the above two snippets of code, the two modules MyNamespace.MyModule1 and MyNamespace.MyModule2 require each other. Now, if we declare an anonymous module that requires MyNamespace.MyModule1, the code in its builder function will never execute...

Uize.require (
  'MyNamespace.MyModule1',
  function () {
    alert ('I will never be executed. How ever so terribly sad!!!');
  }
);

From a certain perspective, not being executed might be considered a desirable outcome. However, when it comes to your code that you would like to be executed, it is frustrating to be thwarted by a dreaded circular dependency.

It may seem obvious that circular dependencies are bad and should be avoided, but they can come up in quite an innocent way. Sometimes two closely coupled classes might use static methods from each other. And, were it not for the dependency resolution system, it might even be possible to load the two files without a problem if the static methods aren't needed during setup of the classes. However, the module loader mechanism doesn't appreciate this subtle nuance, so any module registered as being required in a module declaration will need to be loaded before the module is built.

The only real solution to this impasse is to factor out the common code that is needed by both modules into one or more separate modules that can be required by both of the modules that were previously requiring each other. Fortunately, as it turns out, the end result may produce better factored code and a cleaner architecture.

4.1.4. builder

The builder parameter lets you specify a function that will be responsible for building the module once all its dependencies have been satisfied.

Except for in the case of an Anonymous Module (ie. where there is no value specified for the name parameter), the builder function should return a reference to the built module, so that the Uize.module method can assign this to the host. For anonymous modules, any returned value will simply be ignored. For a Subclass Module or an Extension Module, the builder function should expect to receive a parameter, which is a reference to the superclass or class being extended, respectively.

NOTES

When declaring a Subclass Module, your builder function does not need to assign the subclass as a property on the host - the Uize.module method will take of this for you.

5. Types of Modules

5.1. Subclass Module

A subclass module is a module of code intended to create a subclass.

When declaring a subclass module, the superclass can be specified using the optional superclass parameter. If no superclass is specified, then the "host" is assumed to be the superclass. The builder function should expect to receive one parameter, being an object reference to the superclass. The builder function should return an object reference to the subclass it creates, so this can be assigned to the "host".

SUBCLASS MODULE - EXAMPLE 1

Uize.module ({
  name:'Uize.Widget.MyWidgetClass',
  builder:function (_superclass) {
    'use strict';

    return _superclass.subclass ({
      alphastructor:function () {
        // ... ... ...
      },

      instanceMethods:{
        // ... ... ...
      },

      stateProperties:{
        // ... ... ...
      }
    });
  }
});

In the example above, the widget class Uize.Widget.MyWidgetClass is a subclass of Uize.Widget. It has a simple implementation and so doesn't have any requirements beyond the implicit requirement of the Uize.Widget superclass.

SUBCLASS MODULE - EXAMPLE 2

Uize.module ({
  name:'MyNamespace.MyWidgetClass',
  superclass:'Uize.Widget',
  builder:function (_superclass) {
    'use strict';

    return _superclass.subclass ({
      alphastructor:function () {
        // ... ... ...
      },

      instanceMethods:{
        // ... ... ...
      },

      stateProperties:{
        // ... ... ...
      }
    });
  }
});

In the example above, the class MyNamespace.MyWidgetClass is not a subclass of the module MyNamespace. Rather, MyNamespace is merely the "host" for the MyWidgetClass property, and MyNamespace.MyWidgetClass is a means of referencing the class in code. Therefore, the fact that MyNamespace.MyWidgetClass is a subclass of Uize.Widget needs to be declared explicitly using the optional superclass parameter.

SUBCLASS MODULE - EXAMPLE 3

Uize.module ({
  name:'MyNamespace.MyWidgetClass',
  superclass:'Uize.Widget',
  required:[
    'Uize.Node',
    'Uize.Fade'
  ],
  builder:function (_superclass) {
    'use strict';

    return _superclass.subclass ({
      alphastructor:function () {
        // ... ... ...
      },

      instanceMethods:{
        // ... ... ...
      },

      stateProperties:{
        // ... ... ...
      }
    });
  }
});

In the above example, the implementation of the MyNamespace.MyWidgetClass module has become more complex and it now requires the additional modules Uize.Node and Uize.Fade. Notice, however, that the modules MyNamespace and Uize.Widget (the host and superclass, respectively) are not in the required list, since their requirement is implicit.

5.2. Package Module

A package in the UIZE JavaScript Framework is often just a collection of helper static methods or data properties, and is not derived from a superclass.

So, when declaring packages, there is no meaning to the concept of superclass. This means that the optional superclass parameter would not be applicable. Additionally, the value that is passed as a parameter to the specified builder function would not be valuable, and so your builder function can simply have no parameters.

PACKAGE MODULE

Uize.module ({
  name:'Uize.Node',
  builder:function () {
    'use strict';

    return Uize.package ({
      /*
      ...STATIC PROPERTIES HERE...
      ...STATIC PROPERTIES HERE...
      ...STATIC PROPERTIES HERE...
      */
    });
  }
});

Because of certain aspects of the inheritance mechanism of the UIZE JavaScript Framework, packages should be function objects containing custom properties. To create the package function that is returned by the module's builder function, we call the Uize.package method. This method accepts a single object parameter that specifies the statics (methods and/or properties) for the package, and then returns a new anonymous function object with those statics copied into it.

5.3. Extension Module

Extension modules are modules of code that extend on the implementation of a class or package.

EXTENSION MODULE

Uize.module ({
  name:'Uize.Widget.Bar.Slider.xSkin',
  builder:function (_class) {
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
  }
});

In the above example, the module Uize.Widget.Bar.Slider.xSkin is extending the module Uize.Widget.Bar.Slider. The implementation of the module's builder function will use the _class parameter that is passed to it, which is a reference to the Uize.Widget.Bar.Slider class, and will assign new properties on it (or otherwise modify it).

Since no valus is returned by the builder function, the value function () {} (a dummy function) will be assigned to the xSkin property of the Uize.Widget.Bar.Slider module, resulting in the valid object path of Uize.Widget.Bar.Slider.xSkin. This will ensure that if the module is required and has already been built, then the Uize.module method will detect this, because the property xSkin will exist on the Uize.Widget.Bar.Slider object and dereferencing the object path Uize.Widget.Bar.Slider.xSkin will resolve to something defined, and the Uize.module method won't attempt to load the module.

NOTES

As a naming convention, the guest name of an extension is prefixed with a lowercase "x", but this is merely a convention and not a requirement.
As with a Package Module, there is no meaning to the concept of superclass and, therefore, the optional superclass parameter is not applicable.
For semantic reasons, the parameter taken by the builder function should be called something like _class in the case of extending a class, and _package in the case of extending a package. It would work, but not be semantically meaningful, to call the parameter _superclass.

5.4. Anonymous Module

Anonymous modules are modules where no name is specified.

They are useful when declaring isolated modules of code that have dependencies on other modules that may not already be defined by the time that the anonymous module declaration is encountered. Once the Uize.module method has resolved the anonymous module's dependencies, the module's builder function will be executed.

When not providing a name parameter value in the Uize.module method call, the result of the module's builder function will not be assigned to a host. Without a name for the module, there can be no host, so the value returned by the builder function is effectively ignored (ie. there is no point in returning a value).

5.4.1. Page Wiring

It is a common pattern to place page wiring code towards the bottom of the page. You could either ensure that all the modules needed by the page wiring code are already sourced in before the wiring code is encountered in the document, or you could place the wiring code inside an anonymous module declaration, as in the following example...

ANONYMOUS MODULE

Uize.module ({
  required:[
    'Uize.Node',
    'Uize.Widget.ImagePort',
    'Uize.Widget.Bar.Slider.xSkin',
    'Uize.Widget.Resizer.Marquee'
  ],
  builder:function () {
    /*
    ...PAGE WIRING CODE HERE...
    ...PAGE WIRING CODE HERE...
    ...PAGE WIRING CODE HERE...
    */
  }
});

In the above example, the page wiring code will only execute once the Uize.Node, Uize.Widget.ImagePort, Uize.Widget.Bar.Slider.xSkin, and Uize.Widget.Resizer.Marquee modules, along with all of their required modules, have been loaded in and built.

5.4.2. Deferred Loading

It is also possible to use the Uize.module method to declare an anonymous module of code that is deeply nested inside the main module's implementation. This module of code can then require modules that aren't required by the main module.

This technique is useful for sections of code that may only be encountered under unique and less common conditions, and where always requiring the modules that that code needs at the main module level would add unnecessary load in typical use cases. This approach can be used to defer the loading of modules until when they're truly needed. The drawback to this approach is that there might be some latency between a user interaction and a response to the user, but the overal load reduction for normal use may make this a compelling tradeoff.

5.5. Namespace Module

The Uize.module method can also be used to simply declare a namespace. This is useful when declaring modules under that namespace, where it is implicit that the namespace must exit.

EXAMPLE

Uize.module ({
  name:'MyNamespace.MyWidgetClass',
  superclass:'Uize.Widget',
  builder:function (_superclass) {
    'use strict';

    return _superclass.subclass ({
      alphastructor:function () {
        // ... ... ...
      },

      instanceMethods:{
        // ... ... ...
      },

      stateProperties:{
        // ... ... ...
      }
    });
  }
});

In the above example, it is implicit that MyNamespace must exist, so that the property MyWidgetClass can be assigned on it. Therefore, the module declaration will try to resolve this dependency, if MyNamespace is not already defined. The Uize.module method will try to load the module MyNamespace. If MyNamespace is intended to truly be just a namespace and have zero implementation "guts", then you can declare the namespace in a namespace module, as simply...

NAMESPACE MODULE

Uize.module ('MyNamespace');

Since only a module name is specified instead of a module definition object, no builder function is specified in this module declaration and the Uize.module method will simply assign function () {} (a dummy function) to the namespace. If the module name has no "host" portion (as in this example), then the "guest" (MyNamespace in this example) is assigned as a property on the window object. Namespaces can have a "host", however, as in the following example...

SUBNAMESPACE MODULE

Uize.module ('MyNamespace.MySubNamespace');

5.6. Library Module

In the UIZE JavaScript Framework, JavaScript libraries are bundles of JavaScript modules.

Think of a JavaScript module as like a book, where the various methods and properties of the module make up the "pages" of the book. A library is a collection of books. So, in this metaphor, a JavaScript library file is a collection of JavaScript modules, bundled together into a single file. JavaScript libraries offer a performance benefit for pages, by reducing the number of HTTP requests needed in order for the page to have all the JavaScript modules it needs - especially important for pages with rich client interactivity that may use dozens of different modules.

The source version of a JavaScript library lists the modules that should be included in the built version of the file, but doesn't include any actual code from those modules. The built version of a JavaScript library includes the scrunched versions of all the JavaScript modules that are listed in the source version of the library. JavaScript libraries are built by a special built script.

Now, a library module is a special kind of JavaScript library file that declares itself as a module by including a module declaration, in addition to the special library contents comment that lists the JavaScript modules that should be bundled in the built version of the file, as in the following example...

LIBRARY MODULE

/* Library Contents
  Uize.Class
  Uize.Node
  Uize.Fade
  Uize.Xml
  Uize.Tooltip
  Uize.Widget
  Uize.Widget.Page
  Uize.Widget.Tree
  Uize.Widget.Tree.List
  UizeSite
  UizeSite.Page
  UizeSite.Page.Doc
*/

Uize.module ('UizeSite.Page.Doc.library');

The above example shows the contents of the source version of the library module UizeSite.Page.Doc.library.js. Notice how all that the module declaration does is to define the namespace for the library. When a JavaScript library file includes a module declaration that declares a namespace for the library file, then that library file can be required as a module, and requiring it will result in it being dynamically loaded in by the module loader mechanism - if it is not already loaded. Using this approach, the library module can simply be added to the required list of the anonymous module declaration that sets up the page widget instance in the page.

For a detailed discussion of JavaScript library files and how they can be used to improve the performance of Web pages, consult the guide JavaScript Libraries.

5.7. Alias Module

Alias modules are modules that map to other modules and that can be used for providing backwards compatibility when a class hierarchy changes.

There will come a time - when working on large enough a codebase - where a section of the overal class hierarchy might change as a result of code refactoring. In such cases, it might be desirable to provide backwards compatibility to the code that is using the classes / modules that are being moved or renamed. This approach makes it easier to contemplate and to manage otherwise disruptive structural changes in a class hierarchy.

Let's consider the example where a module is to be renamed. In our example, the module MyNamespace.OldClassName will change its name to MyNamespace.NewClassName. Problem is, there is already tons of code that is using the MyNamespace.OldClassName class - some of which may not even be in your power to update. To manage this change, and to ensure that the code using MyNamespace.OldClassName doesn't break without finding and changing all references to the old name, a temporary alias module can be declared as follows...

ALIAS MODULE

Uize.module ({
  name:'MyNamespace.OldClassName',
  required:'MyNamespace.NewClassName',
  builder:function () {return MyNamespace.NewClassName}
});

In this declaration for the now defunct MyNamespace.OldClassName module, the shiny new MyNamespace.NewClassName module is declared as a required module. So, old code that may still be requiring and using the MyNamespace.OldClassName module will be indirectly requiring the newer MyNamespace.NewClassName module. Once the MyNamespace.NewClassName module has been loaded, the builder function of the alias module will execute. All it does is return a reference to the newer MyNamespace.NewClassName module. So, requiring and creating an instance of the MyNamespace.OldClassName class is effectively the same as requiring and creating an instance of the MyNamespace.NewClassName class.

NOTES

You can use the same trick with a Package Module or an Extension Module.
For backwards compatibility, old code can continue to use alias modules, but using them will add additional links in the dependency chain, and this will have a performance cost if modules are loaded in discretely. So, bottom line, it would be better to migrate code away from using alias modules as soon as is possible.

5.8. Data Module

Data modules are modules that only define data structures, so that such data structures can be used by other modules.

5.8.1. Benefits of Data Modules

Data modules are a convenient means of representing data that is to be used by JavaScript code, offering the following key benefits...

Can be Required - As modules, data modules can be required by other types of modules and can, therefore, be loaded by the module loader mechanism, which can obviate the need to load data through XHR requests, providing one way of delivering data for cross-site scripting (XSS) while also allowing data to be loaded from local files by Web pages that are run locally.
Can Require - As modules, data modules can require other data modules, allowing more complex data structures to be built up using shared subsets of data that are defined in other data modules (see Composite Data Module). Furthermore, data modules can use other modules for programmatic generation of data, or for the expanding of compressed data.
Can be Generated Dynamically - Data modules can be generated dynamically by server side code (see Dynamically Generated Data Module), allowing the module loader mechanism to be used as an alternative to traditional approaches to XSS (Cross-Site Scripting) data requests.

5.8.2. One Implementation Approach

The general concept of a data module does not dictate any specific interface for such modules, but one simple approach is to make the data module a function that returns a reference to the data object, with caching so that subsequent calls don't create fresh objects.

Consider the following skeleton for such a data module...

SKELETON

Uize.module ({
  name:'MyNamespace.MyDataModule',
  builder:function () {
    'use strict';

    var _cachedData; // cached copy of data object, so it doesn't get created over and over

    return function (_getCopy) {
      if (_cachedData && !_getCopy) return _cachedData;

      var _data = {
        // ... ... ...
        // DATA OBJECT
        // ... ... ...
      };
      return _getCopy ? _data : (_cachedData = _data);
    };
  }
});

The above skeleton can be used as a template for creating data modules. This example defines the data module MyNamespace.MyDataModule. The builder returns a function, which is then assigned as the MyDataModule property of the object MyNamespace. This means that the MyNamespace.MyDataModule module is actually a function that returns a data object and should, therefore, be used in the form MyNamespace.MyDataModule ().

Now, in order to avoid contructing and returning a fresh data object each time this function is called, the function implements a simple caching mechanism, using the _cachedData variable that is defined in its enclosing scope. If this variable has a non-null value at the time that the MyNamespace.MyDataModule function is called, then this value is simply returned. Otherwise, or if the value true is specified for the optional _getCopy parameter, then the data object is constructed and cached as necessary. As mentioned, an optional _getCopy parameter allows fresh copies of the object to be made when calling the MyNamespace.MyDataModule function, in case using a shared copy is not desired.

To see a real world example of this approach in use, take a look at the UizeSite.TestData.Photos data module that is used by the UIZE Web site.

5.8.3. Benefits of Deferred Construction

Deferring the construction of a data object, as shown in the section One Implementation Approach, offers benefits in deferred use of memory as well as deferred use of CPU.

5.8.3.1. Deferred Use of Memory

A key benefit of this approach, or any approach where the data object is accessed by a method call, is that the mere loading of the data module does not incur the cost of the data object being "exploded" into memory.

Instead, memory for storing the data structure can be allocated at the time that the data is first requested by calling the accessor. If it is never requested, because specific application state does not cause the data to be needed, then the memory needed to represent the data internally can remain available for other purposes.

5.8.3.2. Deferred Use of CPU

In the same way that this approach offers the benefit of deferred use of memory, this approach can also defer the CPU usage associated with building the data structure in memory.

This deferral becomes even more pronounced if the data structure is large and complex, or if building the data object requires any programmatic / algorithmic construction, programmatic expanding of compressed data, or the cloning of data subsets obtained from other data modules (see Composite Data Module). Instead, the performance impact of this construction can be deferred until when (and if) the data is truly needed.

A good demonstration of this principle can be found in the UizeSite.SiteMap module that defines the contents tree of the UIZE Web site. This module performs various types of programmatic data construction in order to reduce the module's file size. This file size reduction comes at the expense of slightly increased CPU usage, but the construction of the site contents tree is deferred until the user activates the site's contents tree menu, so this cost does not hurt the initial load time for pages that use the site menu control.

5.8.4. Warming up the Engine

Even with the approach where the data object is only built in memory when first requested, you can always still warm up the engine, so to speak, by calling the accessor early on in the code of a module that requires a data module.

EXAMPLE

Uize.module ({
  required:'MyNamespace.MyDataModule',
  builder:function () {
    'use strict';

    MyNamespace.MyDataModule ();  // causes data object to be constructed and cached

    // DO THE REST OF THE STUFF
    // ... ... ... ... ... ...
    // ... ... ... ... ... ...
    // ... ... ... ... ... ...
  }
});

In the above example, an anonymous module is being declared that requires the MyNamespace.MyDataModule data module that we defined in the skeleton shown in the section One Implementation Approach. The code inside the builder function for this anonymous module calls the MyNamespace.MyDataModule function immediately in order to cause the MyNamespace.MyDataModule data module to construct its data object. That way, the object will be ready when later code tries to access it. Notice how the MyNamespace.MyDataModule () statement is not assigning the result to any variable. The assumption is that later code was already accessing the data object using a MyNamespace.MyDataModule function call, and the early call is only intended to invoke pre-construction of the object in memory.

This technique is more relevant in cases where there might be a greater delay in construction for large data structures, and where a one time delay in some later code is actually undesirable.

5.8.5. Other Implementation Approaches

There are a number of other ways one could go about implementing data modules, as discussed below...

5.8.5.1. Subclass of Data Object Base Class

A data module could actually be a special type of subclass module, where the data module implements a subclass of some data object base class.

In such an approach, the data object base class could provide static accessor methods that are inherited by subclasses. The base class may not be used for its instance features, and it may not be anticipated that instances would be created of the data object subclass. Alternatively, it may be intended that instances be created of a data object, and instance methods inherited from the base class could then provide ways to query or otherwise work with the data structure.

5.8.5.2. Instance of Data Object Class

A data module could define a static property that is a reference to an instance of some data object class, or the module itself could be a reference to an instance of a data object class.

In such a case, instance methods of the data object class could provide ways to query or otherwise work with the data structure set for an instance of the class.

5.8.5.3. Package With Static Methods

A data module could simply be a package, with either a static property or an accessor method for accessing the data object, along with additional convenience querying methods.

This approach is similar to the approach described in the section One Implementation Approach, but introduces the idea of offering additional static methods for working with the data object defined in the module. Such methods could be specifically tailored to the type of data that the module defines, such that they're not likely to be offered in some other shared module.

5.8.6. Dynamically Generated Data Module

There's no saying that data modules need to be static files - they can be dynamically generated.

Data modules can be dynamically generated from data stored in some other data format, such as CSV (Comma Separated Values), SimpleData, XML, etc. Furthermore, data modules can be generated in a number of different ways. For example, they can be generated dynamically by the server in response to an HTTP request. Alternatively, they can be generated by JavaScript Build Scripts as part of a Web site build process. A good example of this is the UizeSite.ModulesTree module that defines the tree of all the JavaScript modules contained inside the modules folder of the UIZE Web site.

5.8.7. Composite Data Module

Because data modules can require other data modules, this opens up the possibility of creating data modules that build up more complex data structures by using shared data subsets defined in other data modules.

In the example below, three data modules are being declared. The MyNamespace.EngineeringEmployees data module defines an array of employee information records for members of an engineering department. Similarly, the MyNamespace.FinanceEmployees data module defines an array of employee information records for members of a finance department. Finally, the MyNamespace.AllEmployees data modules combines the data from both the MyNamespace.EngineeringEmployees and MyNamespace.FinanceEmployees data modules in order to define an array of employee information records for all employees.

EXAMPLE

Uize.module ('MyNamespace');  // declare MyNamespace namespace


// declare module with data records for engineering employees

Uize.module ({
  name:'MyNamespace.EngineeringEmployees',
  builder:function () {
    'use strict';

    var _cachedData;

    return function (_getCopy) {
      if (_cachedData && !_getCopy) return _cachedData;

      var _data = [
        {firstName:'John',lastName:'Wilkey',department:'engineering'},
        {firstName:'Nick',lastName:'Arendsen',department:'engineering'},
        {firstName:'Mark',lastName:'Strathley',department:'engineering'}
      ];
      return _getCopy ? _data : (_cachedData = _data);
    };
  }
});


// declare module with data records for finance employees

Uize.module ({
  name:'MyNamespace.FinanceEmployees',
  builder:function () {
    'use strict';

    var _cachedData;

    return function (_getCopy) {
      if (_cachedData && !_getCopy) return _cachedData;

      var _data = [
        {firstName:'Marie',lastName:'Stevenson',department:'finance'},
        {firstName:'Craig',lastName:'Pollack',department:'finance'}
      ];
      return _getCopy ? _data : (_cachedData = _data);
    };
  }
});


// declare module that combines data for engineering and finance employees

Uize.module ({
  name:'MyNamespace.AllEmployees',
  required:[
    'MyNamespace.EngineeringEmployees',
    'MyNamespace.FinanceEmployees'
  ],
  builder:function () {
    'use strict';

    var _cachedData;

    return function (_getCopy) {
      if (_cachedData && !_getCopy) return _cachedData;

      var _data = [].concat (
        MyNamespace.EngineeringEmployees (true),
        MyNamespace.FinanceEmployees (true)
      );
      return _getCopy ? _data : (_cachedData = _data);
    };
  }
});


// declare anonymous module that requires all employees module and alerts its contents

Uize.module ({
  required:[
    'MyNamespace.AllEmployees',
    'Uize.Json'
  ],
  builder:function () {
    alert (Uize.Json.to (MyNamespace.AllEmployees ()));
  }
});

Looking at the code, notice how each of the three data modules follows the convention described in the section One Implementation Approach. The accessor for each data module supports the optional _getCopy parameter. This parameter is being used in the accessor function for the MyNamespace.AllEmployees module, where the value true is being specified in order to ensure that the data structure defined in this module does not share references to the data objects from the other two modules.

Avoiding shared references may or may not be an important consideration, depending on how the data is intended to be used. In this particular approach, modifying a data object returned by the MyNamespace.FinanceEmployees accessor would not affect the data object returned by the MyNamespace.AllEmployees accessor, because the accessor function in the MyNamespace.AllEmployees module guarantees that it has discrete copies of the data defined by the MyNamespace.EngineeringEmployees and MyNamespace.FinanceEmployees modules.

For further reference, a good example of a composite data module is the UizeSite.SiteMap module that defines the contents tree of the UIZE Web site. This module makes use of the UizeSite.ModulesTree dynamically generated data module that defines the tree of all the JavaScript modules contained inside the modules folder of the UIZE Web site.

6. Dynamically Loading Modules

6.1. Uize.moduleLoader

The Uize.moduleLoader static method can be overridden, allowing you to provide your own custom module loader.

This is useful if you plan to use a Web service accessed through AJAX to load JavaScript modules.

6.2. Default Module Loader

The Uize.moduleLoader static method has a default implementation.

The default module loader implementation supports dynamic loading of JavaScript modules using a technique that involves insertion of script tags into the document.

6.3. Uize.moduleUrlTemplate

The default module loader relies on the value of the Uize.moduleUrlTemplate static property to know where to locate the JavaScript files that define required modules.

The value of this property should be a string containing the token [#modulePath], which will be replaced with a path that is derived from the name of the module to be loaded.

EXAMPLE

Uize.moduleUrlTemplate = 'http://www.somedomain.com/js/[#modulePath]';

In the above example, if the module Uize.Widget.MagView needed to be loaded, the module path of "Uize.Widget.MagView.js" would be derived from the module name and would be substituted for the [#modulePath] token, to produce the URL "http://www.somedomain.com/js/Uize.Widget.MagView.js".

When the Uize module is initially loaded, the value of Uize.moduleUrlTemplate static property will be set to a path from which the Uize.js file is loaded. This is accomplished by scanning through the document's DOM to find a script node that references the "Uize.js" JavaScript file. This approach usually works well and it is, therefore, usually not necessary to set a value for this property in order for the default module loader to work.

There might be cases, however, when the code for the Uize base module is included into a single JavaScript library file that is not named "Uize.js". In such cases, it will be necessary to set a value for the Uize.moduleUrlTemplate static property that is consistent with your server environment and where the UIZE JavaScript Framework's JavaScript files are located.

6.4. Custom Module Loader

You can override the default implementation of the Uize.moduleLoader static method in order to define your own module loader.

The value of this property should be a function. The function should expect to receive two parameters, being the name of the module to load, and a function that should be called once the module's code has been loaded. The callback function expects to receive one parameter, being the module's code as a string value.

6.5. Different Kinds of Module Loaders

The module loader mechanism is flexible enough to allow modules to be loaded and dependencies resolved in a variety of different ways and in a variety of different contexts, such as the browser, WSH (Windows Script Host), and even exotic environments that support JavaScript like Adobe Photoshop.

6.5.1. Script Tag Module Loader (built-in default)

A script tag module loader is the default module loader built into the Uize base module.

This module loader mechanism makes use of the browser's behavior of automatically loading external JavaScript files whenever script tags linking to external JavaScript files are added to the document's DOM.

EXAMPLE

Uize.moduleLoader = function (_moduleToLoad,_callback) {
  _callback ();
  var _scriptNode = document.createElement ('script');
  _scriptNode.src =
    Uize.moduleUrlTemplate.replace ('[#modulePath]',_moduleToLoad + '.js')
  ;
  document.body.appendChild (_scriptNode);
};

In the above example, the callback is called immediately with no code, since a script tag will be inserted into the document and the browser will, itself, load and execute the code. Upon the callback being called, the module loader mechanism recognizes that the module is not defined after the moduleLoader is executed and assumes that it must wait for the module - and all its dependencies - to load asynchronously.

6.5.2. Ajax Module Loader

An Ajax based module loader can be defined to allow modules to be loaded through a Web service of your choosing.

EXAMPLE

Uize.moduleLoader = function (_moduleToLoad,_callback) {
  m._commObject.request ({
    url:[m.get ('env').service + 'getjs',{filename:_moduleToLoad + '.js'}],
    returnType:'json',
    requestMethod:'GET',
    callback:_callback
  });
};

6.5.3. Windows Script Host Module Loader

Build scripts written for the WSH (Windows Script Host) context can define a module loader that uses the Scripting.FileSystemObject ActiveX control to load modules.

The many build scripts used by the uize.com Web site make use of a module loader that works in the WSH context and that allows the build scripts to declare and have their dependencies resolved in just the same way as you would expect when writing code for the Web browser context.

EXAMPLE

var _fileSystemObject = new ActiveXObject ('Scripting.FileSystemObject');
function moduleLoader (_moduleToLoad,_callback) {
  var
    _moduleToLoadFile =
      _fileSystemObject.OpenTextFile ('site-source/js/' + _moduleToLoad + '.js',1),
    _moduleToLoadCode = _moduleToLoadFile.ReadAll ()
  ;
  _moduleToLoadFile.Close ();
  _callback (_moduleToLoadCode);
}
moduleLoader ('Uize',function (_moduleToLoadCode) {eval (_moduleToLoadCode)});
Uize.moduleLoader = moduleLoader;

The module loader is implemented inside the build script. When the build script is executed, Uize is not yet defined. So, you find yourself in somewhat of a Catch-22 situation. You can't rely on a module loader defined in the Uize module to load in the Uize module, since it's not yet loaded. So, to break this impasse we define a moduleLoader function inside the build script, use this function to load the Uize module, eval the loaded Uize module's code, and then finally declare the function as the module loader by overriding the Uize.moduleLoader static method. From here on, modules can be declared using the Uize.module static method, and the module loader mechanism is now set up to resolve all dependencies and load modules as needed using the Scripting.FileSystemObject ActiveX control.

6.5.4. Adobe Photoshop Module Loader

It is even possible to declare a module loader in Adobe Photoshop's JavaScript based scripting environment (.jsx files), which has its own facility for reading in files.

EXAMPLE

function moduleLoader (_moduleToLoad,_callback) {
  var _moduleToLoadFile = new File ('/C/uize/site-source/js/' + _moduleToLoad + '.js');
  _moduleToLoadFile.open ('r');
  var _moduleToLoadCode = _moduleToLoadFile.read ();
  _moduleToLoadFile.close ();
  _callback (_moduleToLoadCode);
}
moduleLoader ('Uize',function (_moduleToLoadCode) {eval (_moduleToLoadCode)});
Uize.moduleLoader = moduleLoader;

The approach for the Adobe Photoshop context is somewhat similar to the approach for the Windows Script Host Module Loader, where the moduleLoader function is first used to load the Uize module to get the ball rolling, and then this function is used to override the Uize.moduleLoader static method. You just have to get the fire started, so to speak. Once the Uize.moduleLoader method is overridden, then the script running inside Photoshop can require and use modules of the UIZE JavaScript Framework in just the same way as you would expect when writing code for the Web browser context.

6.6. Pre-loaded Modules

It is safe to require modules without worrying about whether or not those modules have already been loaded.

When relying upon the dynamic loading of modules, one does not need to worry about whether modules are already defined or loaded by some other code by the time your module declaration is encountered. The Uize.module method does the sensible thing of not attempting to load modules that have already been loaded. Therefore, one should always feel confident to use module declarations and declare required modules appropriately. For performance reasons, you can always choose to pre-load modules with explicit script tags in a page, so that they are already loaded by the time a module declaration that might require them is encountered.