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.
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).
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.
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 });
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.
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. |
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 . |
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' ]
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.
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.
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. |
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.
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.
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 . |
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).
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.
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.
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'});
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.
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.
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.
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.
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 }); };
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.