Modular development principles in bpm'online
Glossary Item Box
Types of bpm’online modules
All client functions in bpm’online can be broken down into the following groups:
- Base libraries
- Core
- Sandbox
- Client modules
Base libraries
Base libraries are third-party JavaScript libraries used in the application. The RequireJS library is used as the module loader. The ExtJS framework functions are used in the configuration logic for working with interface controls. JQuery, Backbone and other frameworks are used as well. All third-party JavaScript libraries are placed in the Terrasoft.WebApp\Resources\ui folder of the application.
Core
The main purpose of the bpm’online client core is to provide a unified interface for interaction of all other client parts of the system. The core provides API for accessing base client libraries, defines the sandbox contents for modules, provides access to system enumerations and constants, implements client mechanism for working with data, etc. The core does not work directly with the system modules. It is only aware of the primary application module (ViewModule), which loads all remaining modules.
To access the core functions used in the custom client logic, a module must import the terrasoft module as a dependency.
Sandbox
A module is an isolated programming unit. It is not aware of other system modules except for the names of the modules from which it depends. A special object called the sandbox is designed for interaction between the modules.
The sandbox provides the two key mechanisms for interaction between the modules in the system:
- A mechanism for message exchange between the modules. Modules can communicate with each other only through messages. If module needs to inform other modules that its status has changed, it publishes a message using the sandbox.publish() method of the sandbox. If a module needs to receive messages about status changes of other modules, it must subscribe to those messages. The subscription is done through calling the sandbox.subscribe() sandbox method.
- Loading modules “on-demand” into the specified area of the application (for visual modules). In the process of implementing custom business logic, you many need to load dynamically the modules that have not been declared as dependencies. These modules can be loaded in the process of the module declaration, in the define() function. The sandbox.load() sandbox method is used for this.
To enable interaction with other modules, a module must import the sandbox module as dependency.
Client modules
Client modules are separate functional blocks that are loaded and run on-demand, according to the AMD technology. All custom functions are implemented in client modules. Despite several functional differences, all bpm'online client modules have similar structure that matches the module description format in AMD. For more information about client modules, please see the “Client Modules” article.
The “ext-base”, “terrasoft” and “sandbox” modules
Bpm’online contains modules that are used in most client modules of a configuration. These are the ext-base module of the ExtJs framework functions, the terrasoft module of the Terrasoft objects and name spaces, and the sandbox module that implements the mechanism for message exchange between modules. These modules can be accessed in the following way:
// Module definition and getting dependency module links. define("ExampleModule", ["ext-base", "terrasoft", "sandbox"], // Ext — link to the object that grants access to the ExtJs features. // Terrasoft — link to the object that grants access to the system variables, core variables, etc. // sanbox — used for message exchange between modules. function (Ext, Terrasoft, sandbox) { });
Specifying base modules in the ["ext-base", "terrasoft", "sandbox"] dependencies is not required. After creating module’s class object, the Ext, Terrasoft and sanbox modules will be available as object properties: this.Ext, this.Terrasoft, this.sanbox.
Declaring module class The Ext.define() method
One of the more important ExtJs javascript framework functions in bpm’online is class declaration. The define() method of the global Ext object is used for this. An example of declaring a class with this method:
// Terrasoft.configuration.ExampleClass — class name with // name space compliance. Ext.define("Terrasoft.configuration.ExampleClass", { // Shortened class name. alternateClassName: "Terrasoft.ExampleClass", // Name of the class from which inheritance is made. extend: "Ext.String", // Block for declaring static properties and methods. static: { // Example of a static property. myStaticProperty: true, // Example of a static method. getMyStaticProperty: function () { // Example of access to a static property. return Terrasoft.ExampleClass.myStaticProperty; } }, // Example of a dynamic property. myProperty: 12, // Example of a class dynamic method. getMyProperty: function () { return this.myProperty; } });
Examples of various options for creating class instances:
// Creating a class instance by full name. var exampleObject = Ext.create("Terrasoft.configuration.ExampleClass"); // Creating a class instance by a shortened name (alias). var exampleObject = Ext.create("Terrasoft.ExampleClass"); // Creating a class instance with the specified properties. var exampleObject = Ext.create("Terrasoft.ExampleClass", { // Overriding object property from 12 to 20. myProperty: 20, // Defining a new method for the current class instance. myNewMethod: function () { return this.getMyProperty() * 2; } });
Inheriting a module class
In a simple implementation of a module, its contents is either a simple object with a set of methods and properties, or a constructor function that the module must return to a function that is called after its loading.
define("ModuleExample", [], function () { // Example of a module that returns a simple object. return { init: function () { // The method will be called on module initialization, // but the module contents will not get into the DOM. } } }); define("ModuleExample", [], function () { // Example of a module that returns a constructor function. return function () { this.init = function () { // The method will be called on module initialization, // but the module contents will not get into the DOM. } } });
Such simple module cannot add its view to the Document Object Model (DOM), unless you explicitly implement the render() method, which would return a view instance and insert it to the DOM. The logic for calling the render() method in a module object is covered on the application core level. The destroy() method is not implemented in such module as well. If the module is visual, i.e., it contains the render() method, then it will be impossible to unload the view from the DOM, unless the unloading logic is implemented in the destroy() method.
In most cases, the module class should be inherited from Terrasoft.configuration.BaseModule or Terrasoft.configuration.BaseSchemaModule, where the following methods are already implemented:
- Init() – a method for module initialization. Initializes the properties of class object and subscribes to messages.
- render() – a method for rendering the module view in the DOM. Returns a view. Accepts a single renderTo argument, which is the element where the module object view will be inserted.
- Destroy() – a method that deletes a module view, view model, unsubscribes from messages and destroys the module class object.
Below is an example of a simple module class inherited from "Terrasoft.BaseModule". This module adds a button to the DOM. Clicking the button will display a text message and then the button is deleted from the DOM.
define("ModuleExample", [], function () { Ext.define("Terrasoft.configuration.ModuleExample", { // Short class name. alternateClassName: "Terrasoft.ModuleExample", // The class from which the inheritance is done. extend: "Terrasoft.BaseModule", // Reguired property. If it is not defined, an error will be generated on the // "Terrasoft.core.BaseObject" level, since the class is inherited from "Terrasoft.BaseModule". Ext: null, // Reguired property. If it is not defined, an error will be generated on the // "Terrasoft.core.BaseObject"level, since the class is inherited from "Terrasoft.BaseModule". sandbox: null, // Reguired property. If it is not defined, an error will be generated on the // "Terrasoft.core.BaseObject"level, since the class is inherited from "Terrasoft.BaseModule". Terrasoft: null, // View model. viewModel: null, // View. A button is used as an example. view: null, // If the init() method is not implemented in this class, // then, when an instance of the current class is created, // the init() method of the parent class Terrasoft.BaseModule will be called. init: function () { // Executes the logic of the init() method of the parent class. this.callParent(arguments); this.initViewModel(); }, // Initializes a view model. initViewModel: function () { // Saving module class context // for accessing it from the view model. var self = this; // Creating a view model. this.viewModel = Ext.create("Terrasoft.BaseViewModel", { values: { // Button caption. captionBtn: "Click Me" }, methods: { // Button click handler. onClickBtn: function () { var captionBtn = this.get("captionBtn"); alert(captionBtn + " button was pressed"); // Calls a method for unloading the view and view model, // which results in deleting the button from the DOM. self.destroy(); } } }); }, // Creates a view (button), // binds it to the view model and inserts in the DOM. render: function (renderTo) { // A button is created as a view. this.view = this.Ext.create("Terrasoft.Button", { // Container where the button will be placed. renderTo: renderTo, // The id HTML attribute. id: "example-btn", // Class name. className: "Terrasoft.Button", // Button caption. caption: { // Binds the button caption // with the captionBtn property of the view model. bindTo: "captionBtn" }, // Handler method for the button click event. click: { // Binds the button click handler // to the onClickBtn() method of the view model. bindTo: "onClickBtn" }, // Button style. Available styles are defined in the enumeration. // Terrasoft.controls.ButtonEnums.style. style: this.Terrasoft.controls.ButtonEnums.style.GREEN }); // Binds the view and the view model. this.view.bind(this.viewModel); // Gets the view that will be inserted in the DOM. return this.view; }, // Deletes unused objects. destroy: function () { // Destroys the view, which results in deleting the button from the DOM. this.view.destroy(); // Deletes the unused view model. this.viewModel.destroy(); } }); // Gets module object. return Terrasoft.ModuleExample; });
NOTE
Adding the button using the ViewModel schema is described in the "How to add a button to a section", "How to add a button to an edit page in the new record add mode" и "How to add the button on the edit page in the combined mode" articles.
Synchronous and asynchronous module initialization
There are two ways to initialize a module class instance: synchronously and asynchronously.
Synchronous initialization
A module is initialized synchronously if the isAsync: true property (of the configuration object that is passed as a parameter of the loadModule() method) is not specified at its loading. For example, if the following is executed:
this.sandbox.loadModule([moduleName])
...Then the module class methods will be loaded synchronously. The init() method will be called first, then the render() method will be immediately executed.
Below is an example of a synchronously initialized module.
define("ModuleExample", [], function () { Ext.define("Terrasoft.configuration.ModuleExample", { alternateClassName: "Terrasoft.ModuleExample", Ext: null, sandbox: null, Terrasoft: null, init: function () { // This is executed first upon module initialization. }, render: function (renderTo) { // This is executed on the module initialization, right after the init method. } }); });
Asynchronous initialization
A module is initialized asynchronously if the isAsync: true property (of the configuration object that is passed as a parameter of the loadModule() method) is specified at its loading. For example, if the following is executed:
this.sandbox.loadModule([moduleName], { isAsync: true })
In this case, a single parameter will be passed to the init() method: a callback function with the current module context. When calling the callback function, the render() method of the loaded module is called. The view will be added to the DOM only after the render() method is executed.
Below is an example of an asynchronously initialized module.
define("ModuleExample", [], function () { Ext.define("Terrasoft.configuration.ModuleExample", { alternateClassName: "Terrasoft.ModuleExample", Ext: null, sandbox: null, Terrasoft: null, // This is executed first upon module initialization. init: function (callback) { setTimeout(callback, 2000); }, render: function (renderTo) { // The method is executed after a 2 second delay. // The delay is specified in the setTimeout() function argument, in the init() method. } }); }); });
Chain of modules
Sometimes a model must be shown in the view of other model. For example, the SelectData page for selecting a lookup value must be displayed to set a value in a certain field on the current page. In this case, the current page module must not be unloaded, and the lookup selection page view must be displayed in its container. To implement this, use module chains.
To start building a chain, add the keepAlive property in the configuration object of the loaded module. For example, a lookup selection module selectDataModule must be called from the current page module CardModule. To do this, the following code must be executed:
sandbox.loadModule("selectDataModule", { // Id of the loaded module view. id: "selectDataModule_id", // The view that will be added to the current page container. renderTo: "cardModuleContainer", // Specifies that the current module must not be unloaded. keepAlive: true });
After the code is executed, a module chain will be created, consisting of the current page module and the lookup selection page module. Clicking the [Add new record] button from the current selectData page module will open a new page and add another module to the chain. This way you can add any number of module instances to a chain. Active module (the one that is currently displayed on the page) is always the last element in the chain. If an intermediate element in the chain is set as active, then all elements that are located after it will be destroyed. Use the loadModule function to activate a chain element and pass the module Id as its parameter:
sandbox.loadModule("someModule", { id: "someModuleId" });
The core will destroy all chain elements after the specified one and will execute standard module loading logic (call the init() and render() methods). The render() method will be passed to the container where the previous active module was placed. All modules in the chain can work (receive and send messages, save data, etc.) as before.
If the keepAlive is not added to the configuration object (or added with the keepAlive: false value) on loading of the loadModule() method, then the module chain will be destroyed.