All about modules

1. Quick Overview

In the UIZE 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

EXAMPLE

Uize.module ({
  name:'Uize.Widget.MagView',
  required:[
    'Uize.Node',
    'Uize.Fade',
    'Uize.Widget.ImagePort'
  ],
  builder:function (_superclass) {
    /*** Object Constructor ***/
      var
        _class = _superclass.subclass (
          /*
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          */
        ),
        _classPrototype = _class.prototype
      ;
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
    return _class;
  }
});

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

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 Framework.

4. Uize.module

The Uize.module static method allows you to declare a module in the UIZE 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

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

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.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".

EXAMPLE 1

Uize.module ({
  name:'Uize.Widget.MyWidgetClass',
  builder:function (_superclass) {
    /*** Object Constructor ***/
      var
        _class = _superclass.subclass (
          /*
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          */
        ),
        _classPrototype = _class.prototype
      ;
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
    return _class;
  }
});

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.

EXAMPLE 2

Uize.module ({
  name:'MyNamespace.MyWidgetClass',
  superclass:'Uize.Widget',
  builder:function (_superclass) {
    /*** Object Constructor ***/
      var
        _class = _superclass.subclass (
          /*
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          */
        ),
        _classPrototype = _class.prototype
      ;
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
    return _class;
  }
});

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.

EXAMPLE 3

Uize.module ({
  name:'MyNamespace.MyWidgetClass',
  superclass:'Uize.Widget',
  required:[
    'Uize.Node',
    'Uize.Fade'
  ],
  builder:function (_superclass) {
    /*** Object Constructor ***/
      var
        _class = _superclass.subclass (
          /*
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          */
        ),
        _classPrototype = _class.prototype
      ;
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
    return _class;
  }
});

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 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.

EXAMPLE

Uize.module ({
  name:'Uize.Node',
  builder:function () {
    var _package = function () {};
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
    return _package;
  }
});

IMPORTANT

In the example above, notice how the package object being built by the builder function is initialized as an anonymous function. For the benefit of the inheritance mechanism of the UIZE Framework, the package should be built upon a function and not an object. Making the package an object would result in deep cloning in certain cases of subclassing, and this is not desirable for performance reasons.

5.3. Extension Module

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

EXAMPLE

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...

EXAMPLE

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) {
    /*** Object Constructor ***/
      var
        _class = _superclass.subclass (
          /*
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          ...SOME CODE HERE...
          */
        ),
        _classPrototype = _class.prototype
      ;
    /*
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    ...SOME CODE HERE...
    */
    return _class;
  }
});

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...

Uize.module ({name:'MyNamespace'});

Since no builder function is specified in this module declaration, 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...

Uize.module ({name:'MyNamespace.MySubNamespace'});
5.6. Library Module

In the event that you have multiple pages or code modules that you know all require the same common set of modules, you can declare a "library" module that itself only declares a required list and does nothing else, and then all the aforementioned pages or code modules can simply require that library module.

EXAMPLE

Uize.module ({
  name:'MyNamespace.MySiteSectionLibrary',
  required:[
    'Uize.Widget.MagView',
    'Uize.Widget.Bar.Slider.xSkin',
    'Uize.Widget.Options.Tabbed'
  ]
});

In this example, all modules that require MyNamespace.MySiteSectionLibrary will effectively require Uize.Widget.MagView, Uize.Widget.Bar.Slider.xSkin, and Uize.Widget.Options.Tabbed - even though the MyNamespace.MySiteSectionLibrary module doesn't declare its own builder function. The module does nothing but require other modules. When the builder parameter is not specified, the Uize.module method will assign function () {} (a dummy function) to the module name path (ie. MyNamespace.MySiteSectionLibrary is set to function () {} in this example), once all required modules are built.

6. Dynamically Loading Modules
6.1. Uize.moduleLoader

The Uize.moduleLoader static property lets you specify a 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

A default module loader is specified for the Uize.moduleLoader static property. This module loader makes use of a technique of dynamically adding script nodes to 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 library is loaded. This is accomplished by scanning through the document's DOM to find a script node that references the "Uize.js" JavaScript library. 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 class 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 Framework's JavaScript files are located.

6.4. Custom Module Loader

In order to define your own module loader, you can override the default value of the Uize.moduleLoader static property. 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.

EXAMPLE

Uize.moduleLoader = function (_moduleToLoad,_callback) {
  _this._commObject.request ({
    url:
      _this.get ('env').service + 'getjs?' +
      _serializeParams ({filename:_moduleToLoad + '.js'}),
    returnType:'json',
    requestMethod:'GET',
    callback:_callback
  });
};
6.5. Pre-loaded Modules

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.