Skip to main content

com.sabre.redapp.example3.web.handlebar Sample

The Handlebar sample was created to show how to use Backbone’s Model-View(-Controller) architecture with Handlebar’s template.

In module Main.ts class:

export class Main extends Module {


    init(): void { // (1)

        super.init();

        const dto = getService(DtoService);

        const xp = getService(ExtensionPointService);

        const sidepanelConfig = new RedAppSidePanelConfig([
            new RedAppSidePanelButton('Open handlebar view', 'btn btn-secondary side-panel-button', () => this.showView()),
        ]);

        xp.addConfig('redAppSidePanel', sidepanelConfig);


    }

    private showView(): void {

        let addRemarkModalOptions = {
            title: 'Modal with handlebar template',
            actions: [{
                className: 'app.common.views.Button',
                caption: 'Cancel',
                actionName: 'cancel',
                type: 'secondary'
            }, {
                className: 'app.common.views.Button',
                caption: 'Submit',
                actionName: 'get-pq',
                type: 'secondary'
            }]
        };

        getService(LayerService).showInModal(
            new BasicView({model: new BasicModel("Some message")}), // (2)
            addRemarkModalOptions, {display: 'areaView'}
        );
    }
}
  1. We created a new button and modal for a custom workflow in Main, see the related sample for further details.

  2. We created a new BasicModel that will be attached as a data source for BasicView.

Model

The Model handles domain data used by our RedApp.

In module BasicModel.ts class:

import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
import {AbstractModelOptions} from 'sabre-ngv-app/app/AbstractModelOptions';
import {AbstractModel} from "sabre-ngv-app/app/AbstractModel";


@Initial<AbstractModelOptions>({
    autoPropagateData: true         // (1)
})
export class BasicModel extends AbstractModel {  // (2)

    text: string;  // (3)
    inputDisabled: boolean;
    messages: string[];

    constructor(text: string) {
        super();
        this.text = text;
        this.inputDisabled = true;
        this.messages = [];
    }

    getText() : string {
        return this.text;
    }

    isInputDisabled(): boolean {
        return this.inputDisabled;
    }

    enableInput() {
        this.inputDisabled = false;
    }

    getMessages() : string[] {
        return this.messages;
    }

    addMessage(message : string) {
        console.log(`Message added ${message}`);
        this.messages.push(message);
    }

}
  1. All data (fields) will be propagated to view and will be accessible from there.

  2. Each model has to extend the AbstractModel class from SDK, be wary that some classes used by RedApps like EnhancedResponseData or AbstractAction already do extend AbstractModel themselves.

  3. State of model, constructor, getters, business logic. Basic class structure.

View

View connects data from the model with a .html template. Also handles any dom events.

const i18nService: I18nService = getService(I18nService); // (1)

@Initial<AbstractViewOptions>({
    templateOptions: {
        helpers: {
            t: i18nService.getScopedHelper('com-sabre-redapp-example3-web-handlebar-web-module/translations'), // (1)
            customHelper: (options) => 'Got -' + options +'- from my custom helper'
        }
    }
})
@Template('com-sabre-redapp-example3-web-handlebar-web-module:BasicView') // (2)
export class BasicView extends AbstractView<BasicModel> { // (3)

    constructor(options?: AbstractViewOptions) {
        super(options);
        super.addDomEvents({                            // (4)
            'click .enable-input': 'enableInput',
            'click .add-message': 'add',
            'click .encrypt': 'encrypt'
        })

    }

    enableInput() {
        super.$('.brand-input').prop('disabled', false); // (5)
        super.getModel().enableInput(); // (6)
    }

    add(event : JQueryEventObject) {
        console.log(event); // (7)
        const x: string = super.$('.brand-input').val();
        super.getModel().addMessage(x);
        super.rerender(); // (8)
    }

    encrypt() {
        const toEncrypt: string = super.$('.encrypt-input').val();
        const passphrase: string = super.$('.passphrase-input').val();
        const encrypted: string = CryptoJS.AES.encrypt(toEncrypt, passphrase).toString(); // (9)
        super.getModel().setEncrypted(encrypted);
        super.rerender();
    }
}
  1. The initialization and assignment of two helpers - translation service and a custom one.

  2. Assign template called BasicView (templates/BasicView.html) to this view.

  3. Assigns BasicModel as a data model to use with this view.

  4. Assigns handlers for dom events, for example clicking on an item with enable-input class will call the enableInput method. Clicking on an item with the add-message class will call the add method.

  5. Using jQuery to change the html element’s property.

  6. Calling the method from the model.

  7. Accessing Event.

  8. Since the binding between the template and view/model is one-directional, we need to re-render the view so any changes in the model (adding an element to the displayed list) are visible.

  9. Using crypto-js library to encrypt value taken from one of the inputs.

Template

HTML templates use the Handlebar engine and are used by View to display data from the model.

<div class="com-sabre-redapp-example3-web-handlebar-web-module">
    <div class="dn-line-group">
        <div class="dn-line">
            String data from model, initialized during creation {{text}} <!-- <1> -->
        </div>

        <div>
            <label class="control-label">
                {{t 'SAMPLE_TRANS'}} <!-- <2> -->
            </label>

            <p>
                Disabled input, click Enable to activate. Enable/disable state is kept in model.
            </p>
            <button class="enable-input btn btn-secondary">Enable input</button>
            <input
                    type="text"
                    id="{{new-random 'brand-id'}}"
                    class="form-control brand-input"
                    aria-label="Message to add"
                    {{#if isInputDisabled}} disabled {{/if}} <!-- <3> -->
            />
            <div>
                Adds text from the input box to the string array and displays it below.
            </div>
            <button class="add-message btn btn-secondary">Add message</button>
        </div>

        <div>
            <ul>
                {{#each messages}} <!-- <4> -->
                <li>{{this}}</li>
                {{/each}}
            </ul>
        </div>

        <div class="handlebar-helper">
            Handlebar helper:
            {{customHelper 'Hi'}}
        </div>

        <div>
            <p>
                <label for="{{new-random 'encrypt-input'}}">Text to encrypt: </label>
                <input id="{{get-random 'encrypt-input'}}" type="text" class="form-control encrypt-input"/>
            </p>
            <p>
                <label for="{{new-random 'passphrase-input'}}">Passphrase: </label>
                <input id="{{get-random 'passphrase-input'}}" type="text" class="form-control passphrase-input"/>
            </p>
            <p>
                <button class="encrypt btn btn-secondary">Encrypt</button>
            </p>
            {{#if isEncrypted}}
            <label>Cipher output:</label>
            <p class="cipher-output">
                {{encrypted}}
            </p>
            {{/if}}
        </div>
    </div>
</div>
  1. {{ }} is used to access the related field in the model or call the model’s method. In this case, it will render the value of the text field.

  2. Using a translation, it will render a translation that is labeled as 'SAMPLE_TRANS'

  3. #IF can be used to conditionally display part of the template or, like such as this case, set a node property to one value or another. In this case, if the isInputDisabled method from BasicModel will return true then the input box will be disabled.

  4. #EACH can be used for iteration through an array of any types. Inside #each, {{this}} is related to an object of the array we iterate through. In this case, since the message is an array of strings, {{this}} will be a string. If it is an object, then {{this.x}} is used to access the x state of an object that the current iteration index points to.

Creating Eclipse plugin

Creating an Eclipse plugin for your new Red App is almost the same as it used to be. The only notable difference is that you will contribute to a different extension point. For a web module Red App it should be:

<extension point="com.sabre.edge.dynamo.web.module">
     <modules>
        <module id="sabre-sdk-sample-handlebar"/>
    </modules>
</extension>

com.sabre.edge.redapp.contactdetails.provider extension point is also required as in traditional Red Apps.