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:

  1. Application.initialize()
  2. ModuleManager._initialize() firing module.initialized event
  3. Application.registerScreens()
  4. ModuleManager._registerScreens() firing module.screensRegistered event
  5. Application.registerWidgets()
  6. ModuleManager._registerWidgets() firing module.widgetsRegistered event
  7. Application.beforeShow()
  8. ModuleManager._beforeShow() firing module.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:

  1. dynamicModule.initialize()
  2. dynamicModule.registerScreens()
  3. dynamicModule.registerWidgets()
  4. dynamicModule.beforeShow() firing module.ready event