Modularization
Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality.
Dana comes with a full-featured modularization system to create independent modules with the possibility to:
- declare and register new routes
- add/overwrite style, i18n, configuration
- add behavior to a component
Declaration
Modules are declared into a directory named modules
. By default, the modules
directory is at the same level as the app
one.
A module has its own subdirectory named moduleName
, that must contains a scripts
directory where you should have a class file named <moduleName>Module.js
and a class file named <moduleName>Routes.js
.
Example
The home
module is defined in its own modules/home
directory; in these example, it declares a screen, a widget and a language bundle.
scripts/
βββ app/
βββ modules/
βββ i18n/
β βββ LocaleStringEnUS.json
βββ scripts/
βββ HomeRoutes.js
βββ HomeModule.js
βββ screens/
β βββ HomeScreen.js
βββ widgets/
βββ HomeWidget.js
The <moduleName>Module.js
is a class that must inherits from AbstractModule
, and that is similar to the Application
.
import $AbstractModule from '@AbstractModule';
/**
* The home module
*
* @name HomeModule
* @class
* @extends AbstractModule
* @module
*/
export default $AbstractModule.declare("HomeModule", {
properties: {
exports: [$HomeRoutes]
},
methods: {
/**
* Extension point that is called right after the module has been initialized.
* You should rarely need to implement it.
*/
initialize: function() {},
/**
* Extension point that is called after the declared screens have been registered on router.
* Right place to register additional screens impossible to declare statically
* (dynamic registration)
*/
registerScreens: function () {},
/**
* Extension point that is called after the declared widgets have been registered on widgetManager.
* Right place to register additional widgets impossible to declare statically
* (dynamic registration)
*/
registerWidgets: function () {},
/**
* Extension point that is called when the module is ready to be shown.
* The first screen is not yet shown.
* Right place to initialize services, player, get data, ...
*
*/
beforeShow: function() {}
}
});
The <moduleName>Routes.js
is a class that must inherits from $Routes
, and that is similar to the AppRoutes
.
import $Routes from "@Routes";
import $HomeScreen from "@HomeScreen";
/**
*
* @name HomeRoutes
* @class
* @extends Routes
*/
export default $Routes.declare("HomeRoutes", {
/**
* @route home
*/
methods: /** @lends HomeRoutes.prototype */{
home: function (options) {
return $HomeScreen.display(options);
}
}
});
Add i18n keys
The bundles of a module is specified in the folder of the module. Every bundle files are merged by Dana tooling.
scripts/
βββ app/
βββ modules/
βββ i18n/
βββ LocaleStringEnUS.json
Export shared object
A module can export objects that can be retrieved with the getSharedObject(type)
method of the ModuleManager.
Objects that need to be exported must be declared as a Trait
and listed into the exports
property of the module.
// HomeMenuItem
export default $Trait.declare("HomeMenuItem", {
properties: {
type: "menuItem",
label: "Home"
}
});
// HomeModule
export default $AbstractModule.declare("HomeModule", {
properties: {
exports: [$HomeMenuItem]
}
});
// MenuScreen (application)
export default $Screen.declare("MenuScreen", {
methods: {
children: { "menu": "mainMenu" },
prepareData: function() {
return new Promise(function(resolve) {
var items = $ModuleManager.getInstance().getSharedObject("menuItem").map(function ($MenuItem) {
return $MenuItem.create();
});
this.menu.setItems(items);
resolve({menuItems: items});
}.bind(this)
}
}
})
Discovery
The tooling is responsible to discover the modules that are declared into the modules
directory.
Required modules should be declared in the app.config.json
file under your profile:
{
"default": {
"base":{
"name": "Html5",
"vendors": ["html5"],
"modules": ["home"]
}
}
The modules
key is an array containing the list of the modules needed for the profile. In other words, it is possible to include or exclude entire part of the application (module), depending on the profile built.
Registration
The Application
is responsible to register the modules discovered by the tooling.
It reads the modules
configuration at startup and make sure to register the modules to the ModuleManager
.
This manager offers a register
method to register module dynamically, and an unregister
method to un-register a module dynamically.
Loading
Static
Modules are loaded by the ModuleManager
, which itself is driven by the Application
. A module offer the same extensions points than an application.
The lifecycle is as follows:
Application.initialize()
ModuleManager._initialize()
firingmodule.initialized
eventApplication.registerScreens()
ModuleManager._registerScreens()
firingmodule.screensRegistered
eventApplication.registerWidgets()
ModuleManager._registerWidgets()
firingmodule.widgetsRegistered
eventApplication.beforeShow()
ModuleManager._beforeShow()
firingmodule.ready
event
For each module enabled, moduleManager
executes the four extensions points. If a module fails to be loaded, it is not loaded but the application lifecycle is not broken (ie, the application loads normally).
Dynamic
When a module is registered dynamically, the following chain is executed:
dynamicModule.initialize()
dynamicModule.registerScreens()
dynamicModule.registerWidgets()
dynamicModule.beforeShow()
firingmodule.ready
event