Skip to content

Generic demo and modules with ModuleView & BaseDemo

Valentin Rigolle edited this page May 15, 2019 · 2 revisions

Making generic demos and modules

Since the number of modules in the project has significantly increased, it would be useful to hav a generic Demo class which handles the module management (adding a module to the web page, showing / hiding it when necessary, cf. issue 25). That is the role of the two classes presented here.

BaseDemo

The goal of the BaseDemo class is to simplify the management of the module views. Instead of copy-pasting ~100 lines of code to create the 3D view, and then adding manually each module into the HTML page, you can instanciate a BaseDemo object. It represents a demo web page with no modules (expect temporal, which can hardly be dissociated from the 3D view). Module views can be added after the creation of the object with the method addModule. When called, this method will create a button in the HTML page to enable or disable the module view.

Note : What we call a "module view" is the graphical part of a module (ie. the HTML elements), rather than the core logic of the module. In some modules, the view and the logic are merged into a single class (sometimes called a controller), such as DocumentsController or GuidedTourController for instance. But for other modules (like DocToValidate), the view and the logic are dissociated into a View and a Service. The class BaseDemo only serves as a wrapper for the view (for instance, DocToValidateView), and cannot manage services.

Implementing a Demo with BaseDemo

Implementing a Demo can be done with few lines of code. We will se here how it's done. An example of implementation can be found under the folder examples/DemoBaseDemo.

Instanciating the object

The BaseDemo class must first be imported. It is located under the path src/Utils/BaseDemo/js/. The first lines of a demo script should look like this :

import { BaseDemo } from '../../src/Utils/BaseDemo/js/BaseDemo.js'

let baseDemo = new BaseDemo();

Initializing iTowns

The constructor has no effect, it will just initialize member variables. The initialization of the 3D view is done by the following line :

baseDemo.appendTo(document.body);

This line will append some HTML to the body of the DOM. It will also initialize iTowns related objects, which you can access as properties of the base demo :

baseDemo.view;  // itowns view (3d scene)
baseDemo.extent;  // itowns extent (city limits)
baseDemo.renderer; // The renderer provided by THREE.js
baseDemo.controls; // itowns' PlanarControls (camera controller)

Loading a config file and adding module views

To load a config file (which must be JSON file), BaseDemo provide an async method :

baseDemo.loadConfigFile('./Config.json');

The result will be stored as a member variable accessible through baseDemo.config. As the method is async, you can either await it or wait for the promise to resolve with then. After the configuration file is loaded, modules can be added to the demo. Note that the temporal module is already loaded.

baseDemo.loadConfigFile('./Config.json').then(() => {
    //Loading the 'about' module view
    const about = new udvcore.AboutWindow();
    baseDemo.addModule('About', 'about', about);

    /* probably other modules ... */
}

Two things are important here : first, it is your duty to create the module. You can only pass instanciated objects as parameter to the addModule method. Second, the method signature takes 3 parameters (actually it's 4, with an optional parameter). These parameters are :

Name Type Description
moduleName string A unique name for the module. It will be used for instance as text for the button responsible of enabling / disabling the module view.
moduleId string A unique string representing the identifier of the module. It cannot contains spaces, as it will be used to generate unique HTML ids for some elements.
moduleClass ModuleView or similar The class representing the module view. Please note that the BaseDemo's goal is to manage module views (visual interfaces for the modules), not the pure logic. moduleClass has to implement some methods in order to work, which we'll cover in the next section
type string Optional. Possible values are BaseDemo.MODULE_VIEW (which is the default value) and BaseDemo.AUTHENTICATION_MODULE. Tells the demo how to create visual elements to trigger the view. By default, it creates a new button for toggling the view in the side menu. The authentication module is handled differently because it will display some informations about the user (name, email, etc.) when logged in.

Module view requirements

In order for module views to be compatible with the BaseDemo class (ie. to pass them as arguments to the addModule method), they need to implements 3 specific methods along with a member field :

Method Name Parameters Description
enable Displays the view in the DOM and sends a ModuleView.EVENT_ENABLED event to the listeners. The implementation depends on the module. It can be done for instance by creating the object and inserting it into a parent element, or by changing a display: none of an hidden element to display: block.
disable Hides the view in the DOM and sends a ModuleView.EVENT_DISABLED event to the listeners. Like enable, the implementation depends on the module. However, enable and disable must be reversible. The user must be able to call these methods in an alternating way without changing the behavior of the object.
addEventListener event, action The module view is supposed to send events (as mentioned above) when enabling or disabling it, so the BaseDemo can be aware of the view changes. The module view should provide a way to subscribe to these events, and it is done by this method. event can be either ModuleView.EVENT_ENABLED or ModuleView.EVENT_DISABLED (both are of type string). action is a function that takes zero parameter.
Member Field Type Description
parentElement HTMLElement Represents the parent node of the module view. It is set by the BaseDemo and should not be manipulated otherwise (be can be read without problem). It can be used for instance if enable and disable are implemented so that the DOM elements are created and destroyed each time ; in this case the class need to know where to add the elements it creates.

Below is an example implementation of these methods, where enable and disable create the module view elements each time they're called :

constructor() {
    this.eventListeners = {}; // Used to register event listeners (not mandatory, you're free to do as you want)
    this.parentElement; // The parent element that will be set by the BaseDemo
}

// Displays the view and send an event
enable() {
    this.appendToElement(this.parentElement); //calls a method that creates the DOM elements
    this.sendEvent(ModuleView.EVENT_ENABLED);
}

// Closes the view and send an event
disable() {
    this.dispose(); //This methods destroys the DOM elements
    this.sendEvent(ModuleView.EVENT_DISABLED);
}

// Registers an event listener. This one accepts all events (which includes the two mandatory events)
addEventListener(event, action) {
    if (this.eventListeners[event]) {
        this.eventListeners[event].push(action);
    } else {
        this.eventListeners[event] = [
            action
        ];
    }
}

// Helper function to send events (not mandatory)
sendEvent(event) {
    let listeners = this.eventListeners[event];
    if (!!listeners) {
        for (let listener of listeners) {
            listener();
        }
    }
}

⚠️ The example shown above is not the recommended way of implementing the requirements for the module view. A better approach would be to inherit the ModuleView class and simply to implement the enableView and disableView methods. The process is detailled below, in the ModuleView section.

ModuleView

As said in the previous section, the module views should implement a few methods to be compatible with BaseDemo. A simple way to do this is to extend the ModuleView class which provides basic implementation for these methods.

Implementing a module view with ModuleView

The first step is to make sure your module view is a javascript class as defined in the ES6 specification. You can find information about the concept on this MDN page.

In some part of the code of your class, you should be able to add HTML elements to the DOM. A common pattern is to provide a appendToElement(htmlElement) method than can be called from outside the class. This way, event if you're not using a BaseDemo, your module can still be easily added to a web page.

Importing and extending the class

The module is located in the folder src/Utils/ModuleView. It exports one class, ModuleView, that we will extend :

import { ModuleView } from '../../Utils/ModuleView/ModuleView'; //relative path if we're in a src/Category/ModuleName folder

export class MyModuleView extends ModuleView {
    constructor() {
        super() // calling super() is mandatory for subclasses
    }

    /** rest of the code **/
}

ℹ️ You can find more informations about subclasses and extend on this page.

Overriding the required methods

Extending ModuleView will provide all the required methods mentioned in the previous section. The event listener system is already defined, there is nothing more to do for it to work. The enable and disable methods are also implemented and send the correct events when called, however they don't do anything else. That's because their behaviour is specific to each module, there is no generic way to display or close a module view. The next step is to specify how it should be done, and to do that we need to implement two methods, enableView and disableView.

// How to display the view ?
enableView() {
    this.appendToElement(this.parentElement); //calls a method that creates the DOM elements
}

// How to close the view ?
disableView() {
    this.dispose(); //This methods destroys the DOM elements
}

Note that we do not send the events at the end of the methods. That's because the implementation for enable and disable already does it. Be careful not to override enable and disable but only enableView and disableView in order to have the expected behaviour.