OJET: Inter-Module communication in TypeScript Template

In this post I bring another example on how to Communicate Modules using the Oracle JET - TypeScript Template based on the Oracle JET Cookbook: InterModule Communication

From now and so on will create multiple examples using TypeScript for those who are not familiar with it and wants to turn the Cookbook examples into TypeScript :).

The example (ojet-typescript-list-detail) can be found as usual in my GitHub repository:
https://github.com/DanielMerchan/ojet-examples

This example is a bit different compared to the Cookbook example where the modules are not running at the same time within a Module.

This example is built as described in the following images:


  • Customers module acts as the Wrapper Module. It coordinates when to load the list and detail modules.
  • Customers module makes use also of signals npm module for communication between Customers-List, Customers-Detail.

Explanation of the Example

  • customers.js is the coordinator responsible module for:
    • initializes and controls the Customers Data Model.
    • Send the necessary data to list and detail module by ViewModel params.
  • customers.js uses signals npm module to send and listen the following events
    • customerSelectedSignal: When a customer is selected in the list module, the customerId is send to customers module to load the detail module passing the customerId selected.
    • backToListSignal: Within the detail module, there is a button to navigate back to the list module. The detail module sends a signal (fires an event) to notify the customers module to navigate back to the list module.
customers.js

import * as ko from "knockout";
import { ojModule } from 'ojs/ojmodule-element';
import Utils = require('../Utils');
import Router = require('ojs/ojrouter');
import signals = require("signals");
import "ojs/ojknockout";
import Customer = require('../model/Customer');

/**
 * CustomersViewModel module which uses two auxiliar modules for list / detail visualization of Customers
 * @author Daniel Merchan Garcia
 * @version 6.2.0
 */
class CustomersViewModel {

    // Attributes
    router: Router;
    custChildRouter: Router;
    moduleConfig: KnockoutObservable<ojModule['config']>;
    customerSelectedSignal: signals.Signal;
    backToListSignal: signals.Signal;
    static customers: Array<Customer>;
    currentChildSelection: KnockoutObservable<string>;

    // Static Methods
    static initializeCustomers() {
        CustomersViewModel.customers = [{ id: 0, name: 'Paco', age: 22 }, { id: 1, name: 'Eva', age: 30 }];
    }

    /**
     * Default Constructor of the Customers View Model
     * It is used as a container / wrapper of the List / Detail Modules
     */
    constructor() {
        let self = this;
        // Router
        self.router = Router.rootInstance;
        // Parameters to be passed to the child modules
        self.customerSelectedSignal = new signals.Signal();
        self.backToListSignal = new signals.Signal();
        // Default Module Configuration
        let defaultConfig: ojModule['config'] = { view: [], viewModel: Object, cleanupMode: "onDisconnect" };
        self.moduleConfig = ko.observable(defaultConfig);
        // Build default Child Module Configuration based on the Child Router State
        self.custChildRouter = self.router.getCurrentChildRouter() as Router;
        self.loadCustomerModule(self.custChildRouter.moduleConfig.name());

        
        // Signals Listeners
        // Listen when a customer has been selected in the 'list' module
        self.customerSelectedSignal.add(customerId => {
            self.custChildRouter.go(`detail/${customerId[0]}`).then(status => {
                if (status.hasChanged) {
                    self.loadCustomerModule(self.custChildRouter.moduleConfig.name());
                }

            });
        });

        // Listen when 'back to list' is clicked in a 'detail' module
        self.backToListSignal.add(() => {
            self.custChildRouter.go(`list`).then(status => {
                if (status.hasChanged) {
                    self.loadCustomerModule(self.custChildRouter.moduleConfig.name());
                }
            });
        })
    }

    // Custom Functions
    private loadCustomerModule(name: string): void {
        let data = {};
        if (name === 'detail') {
            const mc = this.custChildRouter.observableModuleConfig();
            const customerId = mc.params['ojRouter']['parameters']['id']();
            data = { 'backToListSignal': this.backToListSignal, 'customers': CustomersViewModel.customers, 'customerId': customerId };
        } else {
            data = { 'customerSelectedSignal': this.customerSelectedSignal, 'customers': CustomersViewModel.customers };
        }
        Utils.resolveViewAndViewModel(this.router.moduleConfig.name() + '/' + name, this.moduleConfig, 'none', data);
    }

    /*
     * Optional ViewModel method invoked after the View is inserted into the
     * document DOM.  The application can put logic that requires the DOM being
     * attached here. 
     * This method might be called multiple times - after the View is created 
     * and inserted into the DOM and after the View is reconnected 
     * after being disconnected.
     */
    connected(): void {
        // Implement if needed
    };

    /**
     * Optional ViewModel method invoked after the View is disconnected from the DOM.
     */
    disconnected(): void {
        let self = this;
        self.customerSelectedSignal.removeAll();
        self.backToListSignal.removeAll();
    };

    /**
     * Optional ViewModel method invoked after transition to the new View is complete.
     * That includes any possible animation between the old and the new View.
     */
    transitionCompleted(): void {
        // Implement if needed
    };
}

// Initialize static content
CustomersViewModel.initializeCustomers();

export = CustomersViewModel;


customers/list.js
import signals = require('signals');
import Customer = require('../../model/Customer');
import ArrayDataProvider = require('ojs/ojarraydataprovider');
import { ojListView } from 'ojs/ojlistview';

import 'ojs/ojlistview';

/**
 * ViewModel for the List Module wrapped by the Customer Module
 * @author Daniel Merchan Garcia
 * @version 6.2.0
 */
class CustomersListViewModel {

    customerSelectedSignal: signals.Signal;
    customers: Array<Customer>;
    customersArrayDataProvider: ArrayDataProvider<Array<Customer>,object>;
    onSelectCustomer: ojListView<string,object>['onSelectionChanged'];

    /**
     * Constructor
     * Takes the customersList as parameters. 
     * For Demo purposes, all customers is loaded in a demo JSON Array.
     * 
     * @constructs CustomersDetailViewModel
     * 
     * @fires CustomersViewModel#backToListSignal
     * @param {any} params Parameters sent by the Customer Wrapper Module
     * 
     */
    constructor(params: any) {
        let self = this;
        self.customerSelectedSignal = params.customerSelectedSignal;
        self.customers = params.customers;
        self.customersArrayDataProvider = new ArrayDataProvider(self.customers, {keyAttributes: 'id'});
        self.onSelectCustomer = event => {
            self.customerSelectedSignal.dispatch(event.detail.value);
        }
    }

    /**
     * Optional ViewModel method invoked after the View is inserted into the
     * document DOM.  The application can put logic that requires the DOM being
     * attached here. 
     * This method might be called multiple times - after the View is created 
     * and inserted into the DOM and after the View is reconnected 
     * after being disconnected.
     */
    connected(): void {
        // Implement if needed
    };

    /**
     * Optional ViewModel method invoked after the View is disconnected from the DOM.
     */
    disconnected(): void {
        // Implement if needed
    };

    /**
     * Optional ViewModel method invoked after transition to the new View is complete.
     * That includes any possible animation between the old and the new View.
     */
    transitionCompleted(): void {
        // Implement if needed
    };
}
export = CustomersListViewModel;

customers/detail.js
import { ojButton } from 'ojs/ojbutton';
import 'ojs/ojbutton';
import 'ojs/ojtoolbar';
import 'ojs/ojlabel';
import signals = require('signals');
import Customer = require('src/ts/model/Customer');
import * as ko from "knockout";

/**
 * ViewModel for the Detail Module wrapped by the Customer Module
 * @author Daniel Merchan Garcia
 * @version 6.2.0
 */
class CustomersDetailViewModel {

    // Attributes
    backButtonAction: ojButton['onOjAction'];
    backToListSignal: signals.Signal;
    customerSelected: KnockoutObservable<Customer>;

    /**
     * Constructor
     * Takes the customerId and the customersList as parameters. 
     * For Demo purposes, we use a set of Data loaded directly in memory.
     * 
     * @constructs CustomersDetailViewModel
     * 
     * @fires CustomersViewModel#backToListSignal
     * @param {any} params Parameters sent by the Customer Wrapper Module
     * 
     */
    constructor(params: any) {
        let self = this;
        self.backToListSignal = params.backToListSignal;
        const customers = params.customers;
        const customerId = params.customerId;
        self.customerSelected =  ko.observable(customers[customerId]);
        self.backButtonAction = (_event: ojButton.ojAction) => {
            this.backToListSignal.dispatch();
        }
    }
    /**
     * Optional ViewModel method invoked after the View is inserted into the
     * document DOM.  The application can put logic that requires the DOM being
     * attached here. 
     * This method might be called multiple times - after the View is created 
     * and inserted into the DOM and after the View is reconnected 
     * after being disconnected.
     */
    connected(): void {
        // Implement if needed
    };

    /**
     * Optional ViewModel method invoked after the View is disconnected from the DOM.
     */
    disconnected(): void {
        // Implement if needed
    };

    /**
     * Optional ViewModel method invoked after transition to the new View is complete.
     * That includes any possible animation between the old and the new View.
     */
    transitionCompleted(): void {
        // Implement if needed
    };
}
export = CustomersDetailViewModel;

Comments

  1. I just want to thank you for sharing your information and your site or blog this is simple but nice Information I’ve ever seen i like it i learn something today. BPMN

    ReplyDelete
  2. KnockoutObservable is throwing compile time error

    ReplyDelete

Post a Comment

Popular posts from this blog

OJET: Build and Deploy in an Application Server

OJET: Select All options using only Checkboxset