Skip Navigation

com.sabre.redapp.example3.desktop.template Sample

This example demonstrates how to add:

  1. Callback structure to any provided command flow and to apply HTML template to the command response.

  2. Call Java Service from JavaScript by registering service using Workflow Extension Point API with service id.

  3. Open Details Container (UI Modal) and render form components inside the modal and define action buttons.

Pre-requisite:

Flow is already exposed by SR360 and allowed to be used by RedApp. In this example ’'Example ExtPoint’' is used.

Flow Details:

’'Example ExtPoint’' is a simple flow provided in SR360 for testing purposes. Find the the flow structure below.

sample flow
Figure 1. Example flow with extension point

This flow executes the callback service provided by RedApp and encloses the structured data in command response. Flow has id=’'com.sabre.edge.dynamo.extpoint_flow’' and exposed extension point has id=’'Example ExtPoint’'.

Development Steps:

  1. Create a RedApp using development tools.

  2. Create POJO class/classes with Jaxb mappings. This can be done in two ways as described below

    • Create POJO classes first and add JAXB Mapping later as shown in this example. Also create ObjectFactory.java class in the same package where POJO classes are present.

    • (or) If you have XSD which describes the structure, use XJC java tool to generate JAXB mappings. Refer to Generating JAXB classes from XSD.

      Example code for Flight.java
      package com.sabre.redapp.example3.desktop.template.data;
      
      import java.io.Serializable;
      import javax.xml.bind.annotation.XmlAccessType;
      import javax.xml.bind.annotation.XmlAccessorType;
      import javax.xml.bind.annotation.XmlElement;
      import javax.xml.bind.annotation.XmlRootElement;
      import javax.xml.bind.annotation.XmlType;
      
      /**
       * Java class for Flight complex type.
       */
      @XmlAccessorType(XmlAccessType.FIELD)
      @XmlType(name = "Flight",
                      namespace = "http://redapp.sabre.com/example3/desktop/template/data",  // (1)
                      propOrder = {"airline", "number"})
      @XmlRootElement(name = "Flight",
                      namespace = "http://redapp.sabre.com/example3/desktop/template/data") // (1)
      public class Flight implements Serializable
      {
          private static final long serialVersionUID = 1L;
      
          @XmlElement(name = "airline", required = true)
          private String airline;
      
          @XmlElement(name = "number", required = true)
          private int number;
      
          /**
           * Gets the value of the airline property.
           *
           * @return possible object is {@link String }
           *
           */
          public String getAirline()
          {
              return airline;
          }
      
          /**
           * Sets the value of the airline property.
           *
           * @param value allowed object is {@link String }
           *
           */
          public void setAirline(String value)
          {
              this.airline = value;
          }
      
          /**
           * Gets the value of the number property.
           *
           * @return possible object is int
           *
           */
          public int getNumber()
          {
              return number;
          }
      
          /**
           * Sets the value of the number property.
           *
           * @param value allowed object is int
           *
           */
          public void setNumber(int value)
          {
              this.number = value;
          }
      }
      1. Namespace is required in order to provide mapping information to JAXB transformer (Refer step 3.)

        Example code for SampleFlights.java
        package com.sabre.redapp.example3.desktop.template.data;
        
        import java.io.Serializable;
        import java.util.ArrayList;
        import java.util.List;
        import javax.xml.bind.annotation.XmlAccessType;
        import javax.xml.bind.annotation.XmlAccessorType;
        import javax.xml.bind.annotation.XmlElement;
        import javax.xml.bind.annotation.XmlRootElement;
        import javax.xml.bind.annotation.XmlType;
        
        /**
         * Java class for SampleFlights complex type.
         */
        @XmlAccessorType(XmlAccessType.FIELD)
        @XmlType(name = "SampleFlights",
                        namespace = "http://redapp.sabre.com/example3/desktop/template/data", // (1)
                        propOrder = {"flights"})
        @XmlRootElement(name = "SampleFlights",
                        namespace = "http://redapp.sabre.com/example3/desktop/template/data") // (1)
        public class SampleFlights implements Serializable
        {
            private static final long serialVersionUID = 1L;
        
            @XmlElement(name = "flights", required = true)
            private List<Flight> flights;
        
            /**
             * Gets the value of the flights property.
             *
             * @return flight {@link Flight }
             *
             */
            public List<Flight> getFlights()
            {
                if(flights == null)
                {
                    flights = new ArrayList<>();
                }
                return flights;
            }
        }
      2. Namespace is required in order to provide mapping information to JAXB transformer (Refer step 3.)

    • Do not alter or change ObjectFactory.java file. Find the code for ObjectFactory.java below.

      Example code for ObjectFactory.java
      package com.sabre.redapp.example3.desktop.template.data;
      
      import javax.xml.bind.annotation.XmlRegistry;
      
      /**
       * This object contains factory methods for each Java content interface and Java
       * element interface generated in the
       * com.sabre.redapp.example.web.template.data package.
       */
      @XmlRegistry
      public class ObjectFactory
      {
      
          /**
           * Create a new ObjectFactory that can be used to create new instances of
           * schema derived classes for package:
           * com.sabre.redapp.example.workflow.extension.data
           *
           */
          public ObjectFactory()
          {
          }
      
          /**
           * Create an instance of {@link Flight }
           * @return instance of {@link Flight }
           */
          public Flight createFlight()
          {
              return new Flight();
          }
      
          /**
           * Create an instance of {@link SampleFlights }
           * @return instance of {@link SampleFlights }
           */
          public SampleFlights createSampleFlightStruct()
          {
              return new SampleFlights();
          }
      }
  3. Provide JAXB mapping to the Transformer which is responsible to convert Java object to JSON. Create transformer.properties file in src directory with package and namespace details as shown below.

    File transformer.properties must contain valid package mapping – prefix pkg and valid namespace mapping – prefix ns. Both names must be the same and mustn’t contain characters outside of set [0-9a-ZA-Z_].

    Example code for transformer.properties
    pkg.com_sabre_redapp_example_desktop_template=com.sabre.redapp.example3.desktop.template.data// (1)
    ns.com_sabre_redapp_example_desktop_template=http://redapp.sabre.com/example3/desktop/template/data//(2)
    1. Package name should be same where JAXB mapping classes are provided. Key should be in the format of key.<plugin-name>.

    2. Namespace value should be same as the one that is provided in JAXB mapping classes. Key should be in the format of ns.<plugin-name>.

  4. Create Flow Extension Callback Service to provide input data to Extension Point. It will need interface for the service and its implementation class. See the below sample code.

    Example code for TemplateFlowExtPointService.java
    package com.sabre.redapp.example3.desktop.template.service;
    
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointCommand;
    
    /**
     * Service displaying sample flights
     */
    public interface TemplateFlowExtPointService
    {
        /**
         * Prepares data model to display.
         *
         * @param extPointCommand {@link FlowExtPointCommand} flow
         * @return {@link FlowExtPointCommand} flow
         */
        FlowExtPointCommand execute(FlowExtPointCommand extPointCommand);
    }
    Example code for TemplateFlowExtPointServiceImpl.java
    package com.sabre.redapp.example3.desktop.template.service.impl;
    
    import com.sabre.redapp.example3.web.template.data.Flight;
    import com.sabre.redapp.example3.web.template.data.SampleFlights;
    import com.sabre.redapp.example3.web.template.service.TemplateFlowExtPointService;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointCommand;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointDataOperation;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointResponse;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointResponseWrapper;
    
    /**
     * Service displaying sample flights
     */
    public class TemplateFlowExtPointServiceImpl implements TemplateFlowExtPointService
    {
        /**
         * {@inheritDoc}
         **/
        @Override
        public FlowExtPointCommand execute(FlowExtPointCommand flowExtPointCommand)
        {
    
            SampleFlights flights = new SampleFlights(); //(1)
            Flight flight1 = new Flight();
            flight1.setAirline("Test Airline1");
            flight1.setNumber(1234);
            flights.getFlights().add(flight1);
    
            Flight flight2 = new Flight();
            flight2.setAirline("Test Airline2");
            flight2.setNumber(3456);
            flights.getFlights().add(flight2);
    
            Flight flight3 = new Flight();
            flight3.setAirline("Test Airline3");
            flight3.setNumber(7890);
            flights.getFlights().add(flight3);
    
            FlowExtPointResponse response = new FlowExtPointResponse();
            response.setStructure(flights); // (2)
    
            FlowExtPointResponseWrapper responseWrapper = new FlowExtPointResponseWrapper();
            responseWrapper.setOperation(FlowExtPointDataOperation.ADD);
            responseWrapper.setResponse(response); // (3)
            flowExtPointCommand.getResponses().add(responseWrapper); // (4)
            return flowExtPointCommand;
        }
    }
    1. Create custom data model object and fill it with data.

    2. Add data model to response as structure.

    3. Add response to wrapper response wrapper

    4. Add wrapper to command responses

  5. Create xml file with service definition. This file should be placed in OSG-INF directory.

    This file must contain:

    • Unique name

    • Full path to class implementation

    • Full path to interface which will provide service functionalities.

    Refer below for sample code.

    See the below sample code for TemplateFlowExtService.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="com.sabre.redapp.example3.desktop.template.service.TemplateFlowExtPointService">
       <implementation class="com.sabre.redapp.example3.desktop.template.service.impl.TemplateFlowExtPointServiceImpl"/>
       <service>
          <provide interface="com.sabre.redapp.example3.desktop.template.service.TemplateFlowExtPointService"/>
       </service>
    </scr:component>
  6. Update MANIFEST.MF file with dependencies and other configurations.

    Example code for MANIFEST.MF
    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Web Template Sample
    Bundle-SymbolicName: com.sabre.redapp.example3.desktop.template;singleton:=true
    Bundle-Version: 1.0.0.qualifier
    Bundle-Vendor: Sabre Inc.
    Service-Component: OSGI-INF/TemplateFlowExtPointService.xml // (3)
    Eclipse-RegisterBuddy: com.sabre.edge.dynamo.transformer // (1)
    Require-Bundle: org.eclipse.core.runtime,
     com.sabre.edge.dynamo.transformer, // (1)
     com.sabre.edge.dynamo.ext.cdm;bundle-version="3.4.25", // (2)
     com.sabre.edge.redapp.contactdetails;bundle-version="1.0.0"
    Bundle-RequiredExecutionEnvironment: JavaSE-1.8
    Bundle-ActivationPolicy: lazy
    1. Add com.sabre.edge.dynamo.transformer to both Require-Bundle and Eclipse-RegistryBuddy. This is required for to pre-load JAXB Mappings. Add any other require bundle from target platform that are required for compilation.

    2. Add com.sabre.edge.dynamo.ext.cdm to Require-Bundle.

    3. Add callback service definition OSGI-INF/TemplateFlowExtensionPointService.xml to Service-Component.

  7. Update plugin.xml with extension details

    Example code for plugin.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <?eclipse version="3.4"?>
    <plugin>
        <extension point="com.sabre.edge.cf.redapp"/>
    
        <extension
             point="com.sabre.edge.redapp.contactdetails.provider">
          <contactDetails  // (1)
    			company="Sabre Inc."
    			contactName="SRW Developers"
    			email="redappssupport@sabre.com"
    			phoneNumber="123-456-789"
    			website="http://www.sabre.com">
          </contactDetails>
        </extension>
    	<extension
    		 point="com.sabre.edge.dynamo.flow.flowextpoint.registry">
    		<flowExtensionPoint  // (2)
    			callbackService="com.sabre.redapp.example3.web.template.service.TemplateFlowExtPointService:execute"
    			flowId="com.sabre.edge.dynamo.extpoint_flow"
    			extensionPointId="ExampleExtPoint">
    		</flowExtensionPoint>
    	</extension>
    
    	<extension point="com.sabre.edge.dynamo.web.module">
    		<modules>
    			<module id="com-sabre-redapp-example-desktop-template"/> // (3)
    		</modules>
    	</extension>
    
    </plugin>
    1. Provide the provider contact details.

    2. Declare service which will be used with specified extension point.

    3. The web module contribution.

  8. Let’s see how to create a web application template to handle structured response rendered by command Extension Point. Refer to Getting Started.

  9. Once TypeScript webapp module is created, follow the steps below.

  10. Update Main.ts

    Example code for Main.ts
    import {Module} from 'sabre-ngv-core/modules/Module';
    import {DtoService} from 'sabre-ngv-app/app/services/impl/DtoService';
    import {DrawerService} from 'sabre-ngv-app/app/services/impl/DrawerService';
    import {SampleService} from './services/SampleService';
    import {SampleFlightsModel} from './models/SampleFlightsModel';
    import {SampleFlightsView} from './views/SampleFlightsView';
    import {FlightAction} from './views/FlightAction';
    import {getService} from './Context';
    import {registerService} from './Context';
    
    export class Main extends Module {
        init(): void {
    
            super.init();
    
    		registerService(SampleService); // (1)
    
            const dto = getService(DtoService);
            dto.registerDataModel(
                '[d.Structure][o.ExtensionPoint_Summary][com_sabre_redapp_example_desktop_template.SampleFlights]', // (2)
                SampleFlightsModel
            );
            dto.registerDataView(SampleFlightsModel, SampleFlightsView); // (3)
    
            const drawer = getService(DrawerService); // (4)
            drawer.addConfig(['sample-flight'], { // (5)
            	details: [
    	        	{
    	        		caption: 'Airline Name',
    	        		print: '{{#with drawer-context-model}}' +
    	        					{{airline}} +
    	        				'{{/with}}'
    	        	},
    	        	{
    	        		caption: 'Airline Number',
    	        		print: '{{#with drawer-context-model}}' +
    	        					{{number}} +
    	        				'{{/with}}'
    	        	}
            	]
            });
    
    		drawer.addConfig('sample-flight', {
    			actions: [
    				{
    					class: FlightAction // (6)
    				}
    			]
    		});
        }
    }
    1. Service registration.

    2. Register data model SampleFlightsModel with response structure path added in extension point. com_sabre_redapp_example_web_template is the name space provide in the example. This should be same as namespace provided in transformer.properties. Refer to step 3 for more details. SampleFlightsModel is the JAXB mapping for SampleFlightsModel.java class. This code registers SampleFlightsModel to the response rendered in this example.

    3. Register SampleFlightDetailsModel data model to SampleFlightsView view.

    4. Create instance of drawer service.

    5. Add drawer template to the sample model ’sample-flight’. Drawer-context-model is the model returned to the specific response row.

    6. Add action to the sample model ’sample-flight’

  11. Create SampleFlightsModel.ts

    Example code for SampleFlightsModel.ts
    import {EnhancedResponseData} from 'sabre-ngv-app/app/common/data/dto/EnhancedResponseData';
    import {DataOptions} from 'sabre-ngv-app/app/common/data/dto/DataOptions';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {AbstractModelOptions} from 'sabre-ngv-app/app/AbstractModelOptions';
    import {JsonObject} from 'sabre-ngv-app/_types';
    import {FlightModel} from './FlightModel';
    
    @Initial<DataOptions>({
        dataRoot: '[d.Structure][o.ExtensionPoint_Summary][com_sabre_redapp_example_desktop_template.SampleFlights][0]' // (1)
    })
    
    @Initial<AbstractModelOptions>({
        autoPropagateData: true,
        nonLazyMembers: [
            'flights'
        ]
    })
    
    export class SampleFlightsModel extends EnhancedResponseData {
    
        getFlights() {
            return this.fromRoot().get<Array<JsonObject>>('[flights]').value().map(function(item: JsonObject){ // (3)
            	return new FlightModel(item);
            });
        }
    
    }
    1. Register data root to use fromRoot() method on top of the root declared in this section.

    2. Note that model TypeScript class is extended EnhancedResponseData which handles structured data response for most of the commands.

    3. TypeScript method to return the model structure from response which will be handed over to view.

  12. Create FlightModel.ts

    Example code for FlightModel.ts
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {AbstractModel} from 'sabre-ngv-app/app/AbstractModel';
    import {AbstractModelOptions} from 'sabre-ngv-app/app/AbstractModelOptions';
    import {JsonObject} from 'sabre-ngv-app/_types';
    
    @Initial<AbstractModelOptions>({
        autoPropagateData: true,
        nonLazyMembers: [
            'airline',
            'number'
        ]
    })
    
    export class FlightModel extends AbstractModel { // (1)
    
        constructor(public flight: JsonObject) {
    		super(flight);
        }
    
        // (2)
        getAirline(): string {
            return <string>this.get('airline');
        }
    
        getNumber(): string {
            return <string>this.get('number');
        }
    
        getDescription(): string {
            return 'This is sample flight description';
        }
    }
    1. Flight model extends AbstractModel and initializes the model with data provided in constructor.

    2. Method returns flight attributes.

  13. Create SampleFlightsView.ts

    Example code for SampleFlightsView.ts
    import {AbstractView} from 'sabre-ngv-app/app/AbstractView';
    import {Mixin} from 'sabre-ngv-core/decorators/classes/Mixin';
    import {WithModeRelatedWidget} from 'sabre-ngv-app/app/common/mixins/WithModeRelatedWidget';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {WithModeRelatedWidgetOptions} from 'sabre-ngv-app/app/common/mixins/WithModeRelatedWidgetOptions';
    import {SampleFlightsModel} from '../models/SampleFlightsModel';
    import {FlightExpert} from './FlightExpert';
    import {FlightNovice} from './FlightNovice';
    import {IModeRelatedWidgetsMap} from 'sabre-ngv-app/app/common/mixins/IModeRelatedWidgetsMap';
    
    
    @Mixin(WithModeRelatedWidget)
    @Initial<WithModeRelatedWidgetOptions>({ // (2)
        modeRelatedWidgets: {
            'expert-output-mode': FlightExpert,
            'novice-output-mode': FlightNovice
        }
    })
    
    export class SampleFlightsView extends AbstractView<SampleFlightsModel> implements WithModeRelatedWidget<FlightExpert, FlightNovice> { // (1)
    
        modeRelatedWidgets: IModeRelatedWidgetsMap<FlightExpert, FlightNovice>;
    }
    1. In this example view simply extends AbstractView and implements WithModeRelatedWidget. This offers developer to provide view implementation for both Classic Style and Graphical Style modes.

    2. Provide initial options for expert-output-mode and novice-output-mode with respective view implementations.

  14. Create Classic Style mode view and template

    Example code for FlightExpert.ts
    import {Mixin} from 'sabre-ngv-core/decorators/classes/Mixin';
    import {Template} from 'sabre-ngv-core/decorators/classes/view/Template';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {WithHighlightableChildren} from 'sabre-ngv-app/app/common/mixins/WithHighlightableChildren';
    import {ListView} from 'sabre-ngv-app/app/widgets/container/ListView';
    import {ListViewOptions} from 'sabre-ngv-app/app/widgets/container/ListViewOptions';
    import {FlightExpertRow} from './FlightExpertRow';
    import {SampleFlightsModel} from '../models/SampleFlightsModel';
    
    @Mixin(WithHighlightableChildren)
    @Template('com-sabre-redapp-example-web-template:FlightExpert') // (3)
    @Initial<ListViewOptions>({
    	itemsProperty: 'model.flights' // (2)
        itemDescriptor: {
            'class': FlightExpertRow
        }
    })
    
    export class FlightExpert extends ListView<SampleFlightsModel, FlightExpertRow> { // (1)
    
    }
    1. in this example FlightExpert view extends ListView to show the flight models in rows.

    2. List view initialization with data list and view to handle each item in list

    3. Handlebar template mapping

      Example code for FlightExpert.html
      <div class="dn-line-group">
          <div class="dn-line">
              Sample Flight Information
          </div>
      </div>
      <div class="ui-container-items"><div>
  15. Classic Style mode row view

    Example code for FlightExpertRow.ts
    import {AbstractView} from 'sabre-ngv-app/app/AbstractView';
    import {AbstractViewOptions} from 'sabre-ngv-app/app/AbstractViewOptions';
    import {Mixin} from 'com_sabre_ngv_core/decorators/classes/Mixin'
    import {Template} from 'sabre-ngv-core/decorators/classes/view/Template';
    import {CssClass} from 'sabre-ngv-core/decorators/classes/view/CssClass';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {ViewDescriptor} from 'sabre-ngv-app/_types';
    import {WithDrawer} from 'sabre-ngv-app/app/widgets/drawer/mixins/WithDrawer';
    import {WithDrawerOptions} from 'sabre-ngv-app/app/widgets/drawer/mixins/WithDrawerOptions';
    import {FlightModel} from '../models/FlightModel';
    import {FlightDrawer} from './FlightDrawer';
    
    @Mixin(WithDrawer)
    @Initial<WithDrawerOptions>({ // (2)
        drawerDescriptor: FlightDrawer
    })
    @Template('com-sabre-redapp-example-desktop-template:FlightExpertRow')
    @CssClass('flight-row flight-expert-row')
    export class FlightExpertRow extends AbstractView<FlightModel> implements WithDrawer<FlightDrawer> { // (1)
    
        drawerDescriptor: ViewDescriptor<FlightDrawer>;
        openDrawer: () => void; // (3)
        closeDrawer: () => void;
        toggleDrawer: () => void;
        getDrawer: () => FlightDrawer;
    
        constructor(options?: AbstractViewOptions) {
            super(options);
    
        }
    }
    1. FlightExpertRow view extends AbstractView of type Flight and implements WithDrawer

    2. Drawer initialization parameters

    3. Drawer methods implementations. openDrawer, closeDrawer and clopenDrawer are void in this examples and implementations can be provided according to needs.

      Example code for FlightExpertRow.html
      <div class="dn-line-group">
      	<div class="dn-middle-group-box response-container">
      	    <div class="dn-line response-row">
      	        <div class="col-cpa-1">
      	        	<span>
      	        		{{airline}} // (1)
      	        	</span>
      	    	</div>
      	    	<div class="col-cpa-2"></div>
      	    	<div class="col-cpa-3">
      	        	<span class="js-booking-classes booking-classes">
      	        		{{number}} // (1)
      	        	</span>
      	    	</div>
      	    </div>
          </div>
          <div class="dn-right-group-box">
      	    <div class="dn-zombie-ghost drawer-arrow">
      	    	<i class="fa fa-chevron-down"></i>
      	    <div>
          <div>
      </div>
    4. airline and number are properties of Flight model.

  16. Create Graphical Style mode view and template

    Example code for FlightNovice.ts
    import {Mixin} from 'sabre-ngv-core/decorators/classes/Mixin';
    import {Template} from 'sabre-ngv-core/decorators/classes/view/Template';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {WithHighlightableChildren} from 'sabre-ngv-app/app/common/mixins/WithHighlightableChildren';
    import {ListView} from 'sabre-ngv-app/app/widgets/container/ListView';
    import {ListViewOptions} from 'sabre-ngv-app/app/widgets/container/ListViewOptions';
    import {FlightNoviceRow} from './FlightNoviceRow';
    import {SampleFlightsModel} from '../models/SampleFlightsModel';
    
    @Mixin(WithHighlightableChildren)
    @Template('com-sabre-redapp-example-desktop-template:FlightNovice') // (3)
    @Initial<ListViewOptions>({
    	itemsProperty: 'model.flights' // (2)
        itemDescriptor: {
            'class': FlightNoviceRow
        }
    })
    
    export class FlightNovice extends ListView<SampleFlightsModel, FlightNoviceRow> { // (1)
    
    }
    1. in this example FlightNovice view extends ListView to show the flight models in rows.

    2. List view initialization with data list and view to handle each item in list

    3. Handlebar template mapping

      Example code for FlightNovice.html
      <div class="dn-line-group">
          <div class="dn-line">
              Sample Flight Information
          </div>
      </div>
      <div class="ui-container-items"><div>
  17. Graphical Style mode row view

    Example code for FlightNoviceRow.ts
    import {AbstractView} from 'sabre-ngv-app/app/AbstractView';
    import {AbstractViewOptions} from 'sabre-ngv-app/app/AbstractViewOptions';
    import {Mixin} from 'com_sabre_ngv_core/decorators/classes/Mixin'
    import {Template} from 'sabre-ngv-core/decorators/classes/view/Template';
    import {CssClass} from 'sabre-ngv-core/decorators/classes/view/CssClass';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {ViewDescriptor} from 'sabre-ngv-app/_types';
    import {WithDrawer} from 'sabre-ngv-app/app/widgets/drawer/mixins/WithDrawer';
    import {WithDrawerOptions} from 'sabre-ngv-app/app/widgets/drawer/mixins/WithDrawerOptions';
    import {FlightModel} from '../models/FlightModel';
    import {FlightDrawer} from './FlightDrawer';
    
    @Mixin(WithDrawer)
    @Initial<WithDrawerOptions>({ // (2)
        drawerDescriptor: FlightDrawer
    })
    @Template('com-sabre-redapp-example-desktop-template:FlightNoviceRow')
    @CssClass('flight-row flight-novice-row')
    export class FlightNoviceRow extends AbstractView<FlightModel> implements WithDrawer<FlightDrawer> { // (1)
    
        drawerDescriptor: ViewDescriptor<FlightDrawer>;
        openDrawer: () => void; // (3)
        closeDrawer: () => void;
        toggleDrawer: () => void;
        getDrawer: () => FlightDrawer;
    
        constructor(options?: AbstractViewOptions) {
            super(options);
    
        }
    }
    1. FlightNoviceRow view extends AbstractView of type Flight and implements WithDrawer

    2. Drawer initialization parameters

    3. Drawer methods implementations. openDrawer, closeDrawer and clopenDrawer are void in this examples and implementations can be provided according to needs.

      Example code for FlightNoviceRow.html
      <div class="dn-line-group">
      	<div class="dn-middle-group-box response-container">
      	    <div class="dn-line response-row">
      	        <div class="col-cpa-1">
      	        	<span>
      	        		{{airline}} // (1)
      	        	</span>
      	    	</div>
      	    	<div class="col-cpa-2"></div>
      	    	<div class="col-cpa-3">
      	        	<span class="js-booking-classes booking-classes">
      	        		{{number}} // (1)
      	        	</span>
      	    	</div>
      	    </div>
          </div>
          <div class="dn-right-group-box">
      	    <div class="dn-zombie-ghost drawer-arrow">
      	    	<i class="fa fa-chevron-down"></i>
      	    <div>
          <div>
      </div>
    4. airline and number are properties of Flight model.

  18. Drawer

    Example code for FlightDrawer.ts
    import {Drawer} from 'sabre-ngv-app/app/widgets/drawer/views/Drawer';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {DrawerOptions} from 'sabre-ngv-app/app/widgets/drawer/views/DrawerOptions';
    import {FlightModel} from './models/FlightModel';
    import {AbstractViewOptions} from 'sabre-ngv-app/app/AbstractViewOptions';
    
    @Initial<DrawerOptions>({ // (2)
        drawerGroups: ['sample-flight']
    })
    export class FlightDrawer extends Drawer<FlightModel> { //(1)
    
        initialize(options?: AbstractViewOptions): void {
    
            super.initialize(options);
    
        }
    }
    1. FlightDrawer extends Drawer of type Flight.

    2. Drawer initialization group name. Same model name should be used to add any configurations to the Drawer. Refer to Main.ts.

  19. Action Template

    Example code for FlightAction.ts
    import {AbstractAction} from 'sabre-ngv-app/app/common/views/AbstractAction';
    import {AbstractViewOptions} from 'sabre-ngv-app/app/AbstractViewOptions';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {Template} from 'sabre-ngv-core/decorators/classes/view/Template';
    import {CssClass} from 'sabre-ngv-core/decorators/classes/view/CssClass';
    import {getService} from '../Context';
    import {I18nService} from 'sabre-ngv-app/app/services/impl/I18nService';
    import {FlightModel} from '../models/FlightModel';
    import {SampleService} from '../services/SampleService';
    
    const i18nService: I18nService = getService(I18nService);
    
    @Initial<AbstractActionOptions>({
        caption: 'Sample Action',
        actionName: 'action', (2)
        type: 'primary',
        templateOptions: {
        	helpers: {
        		_t: i18nService.getScopedHelper('com-sabre-redapp-example-desktop-template/translations')
        	}
        }
    })
    
    @Template('com-sabre-redapp-example-web-template:FlightAction') // (3)
    @CssClass('flight-sell-action')
    export class FlightAction extends AbstractAction<FlightModel> { // (1)
    
        selfAction() {
    
            getService(SampleService).executeCommand();
        }
    }
    1. FlightAction extends AbstractAction of type Flight.

    2. Bind a method. Target method will be same as actionName with prefix self, in this case it is selfAction.

    3. Handlebar template for the Sample Action.

    4. Translations are loaded using i18nService. Below is the screenshot of translations.json file located under src/code/i18n/en_US/ in sample modules. en_US is the internalization code US English.

      Example code for translations.json
      {
        "SAMPLE_ACTION": "Sample Action"
      }
      Note
      Keys names in translations map must be provided with upper cases and underscores. Values from translations map are used for internationalization. Translations from en_US will be used in case when no other translation is provided.
      Example code for FlightAction.html
      <div class="button-col col">
      	<button class="sample-action-button btn btn-success">{{_t "SAMPLE_ACTION"}}</button>
      </div>
    5. _t is the TypeScript member defined in FlightAction.ts.

  20. Sample Service

    Example code for ISampleService.ts
    export interface ISampleService {
    
        executeCommand(): void;
    }
    Example code for SampleService.ts
    import {AbstractService} from 'sabre-ngv-app/app/services/impl/AbstractService';
    import {ISampleService} from './SampleService';
    import {cf} from '../Context';
    
    export class SampleService extends AbstractService implements ISampleService { // (1)
    
    	static SERVICE_NAME = 'com-sabre-redapp-example3-desktop-template-web-module-SampleService'; // (2)
    
        executeCommand() {
            //send sample command
    
            cf('*A').send(); // (3)
        }
    }
    1. SampleService extends AbstractService and implements ISampleService.

    2. Declare variable SERVICE_NAME with service name.

    3. executeCommand invoking command framework with command ’*A’. This

  21. Add any custom css in styles.less file located under styles directory

  22. Sample plugin structure.

    See below for the directory structure of a complete sample plugin.
    $ tree com.sabre.redapp.example3.desktop.template
    com.sabre.redapp.example3.desktop.template/
    ├── build.properties
    ├── META-INF
    │   └── MANIFEST.MF
    ├── OSGI-INF
    │   └── TemplateFlowExtPointService.xml
    ├── plugin.xml
    ├── pom.xml
    ├── redapp.xml
    ├── src
    │   ├── com
    │   │   └── sabre
    │   │       └── redapp
    │   │           └── example3
    │   │               └── web
    │   │                   └── template
    │   │                       ├── data
    │   │                       │   ├── Flight.java
    │   │                       │   ├── ObjectFactory.java
    │   │                       │   └── SampleFlights.java
    │   │                       └── service
    │   │                           ├── impl
    │   │                           │   └── TemplateFlowExtPointServiceImpl.java
    │   │                           └── TemplateFlowExtPointService.java
    │   └── transformer.properties
    └── web-src
        └── com-sabre-redapp-example-desktop-template
            ├── concierge.conf.js
            ├── package.json
    		└── src
                ├── assets
                │   └── sabre-logo.png
                ├── code
                │   ├── Context.ts
                │   ├── index.ts
                │   ├── Main.ts
                │   ├── models
                │   │   ├── FlightModel.ts
                │   │   └── SampleFlightsModel.ts
                │   ├── services
                │   │   ├── ISampleService.ts
                │   │   └── SampleService.ts
                │   └── views
                │       ├── FlightAction.ts
                │       ├── FlightDrawer.ts
                │       ├── FlightExpert.ts
                │       ├── FlightExpertRow.ts
                │       ├── FlightNovice.ts
                │       ├── FlightNoviceRow.ts
                │       └── SampleFlightsView.ts
                ├── guides
                │   └── README.md
                ├── i18n
                │   └── en_US
                │       └── translations.json
                ├── manifest.json
                ├── styles
                │   └── styles.less
                └── templates
                    ├── FlightAction.html
                    ├── FlightExpert.html
                    ├── FlightExpertRow.html
                    ├── FlightNovice.html
                    └── FlightNoviceRow.html
  23. After completing the above steps compile and build type script web module and copy the compiled script files from build/dist to the web/<modulename>/ directory.

Execution Steps:

  1. Include sample plugin in Eclipse run configurations.

  2. Make sure start level and auto start values are updated as stated in step 8 in Development Steps.

  3. Run the application.

  4. Login to application using EPR, password and PCC.

  5. Execute the command ’SAMPLE://EXTPOINT’.

  6. Screenshot of the output below. Click on each row to see the drawer details.

  7. Action button label is rendered from the translations.json

    image

  8. Click on the SampleAction button to execute *A command. This will return ’NO DATA’

    image

How to Call Java Service from JavaScript:

  1. The same sample plugin has been extended to demonstrate this feature. In com.sabre.redapp.example3.desktop.template plugin there is another callback service class introduced.

  2. Create call back service interface and implementation. ActionDataExtensionPointService.java and ActionDataExtensionPointCallbackService.java has been created to provide input data to Extension Point.

    Example code for ActionDataExtensionPointService.java
    package com.sabre.redapp.example3.web.template.service;
    
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointCommand;
    
    /**
     * Service displaying selected flight
     */
    public interface ActionDataExtensionPointService
    {
        /**
         * Prepares data model to display.
         *
         * @param extPointCommand {@link FlowExtPointCommand} flow
         * @return {@link FlowExtPointCommand} flow
         */
        FlowExtPointCommand execute(FlowExtPointCommand extPointCommand);
    }
    Example code for ActionDataExtensionPointService.java
    package com.sabre.redapp.example3.web.template.service.impl;
    
    import java.util.Optional;
    import com.sabre.redapp.example3.web.template.data.Flight;
    import com.sabre.redapp.example3.web.template.data.SampleFlights;
    import com.sabre.redapp.example3.web.template.service.ActionDataExtensionPointService;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.utils.FlowExtPointCommandUtils;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointCommand;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointDataOperation;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointRequestWrapper;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointResponse;
    import com.sabre.stl.pos.srw.nextgen.flow.ext.v2.FlowExtPointResponseWrapper;
    
    /**
     * Service displaying selected flight
     */
    public class ActionDataExtensionPointServiceImpl implements ActionDataExtensionPointService
    {
        /**
         * {@inheritDoc}
         **/
        @Override
        public FlowExtPointCommand execute(FlowExtPointCommand flowExtPointCommand)
        {
    
            Optional <Flight> flightRq =
                FlowExtPointCommandUtils.getRequestWrapper(flowExtPointCommand, Flight.class) //(1)
                    .map(FlowExtPointRequestWrapper::getRequest)
                    .map(Flight.class::cast);
    
            if (flightRq.isPresent())
            {
                SampleFlights flights = new SampleFlights(); // (2)
                Flight flight = new Flight();
                flight.setAirline("Selected Airline");
                flight.setNumber(flightRq.get().getNumber());
                flights.getFlights().add(flight);
    
                FlowExtPointResponse response = new FlowExtPointResponse();
                response.setStructure(flights);
    
                FlowExtPointResponseWrapper responseWrapper = new FlowExtPointResponseWrapper(); // (3)
                responseWrapper.setOperation(FlowExtPointDataOperation.ADD);
                responseWrapper.setResponse(response);  // (4)
                flowExtPointCommand.getResponses().add(responseWrapper); // (5)
            }
    
            return flowExtPointCommand;
        }
    }
    1. Retrieve data from request.

    2. Prepare data for response.

    3. Create response wrapper

    4. Add data to wrapper

    5. Add wrapper to response

  3. Create xml file with service definition. This file should be placed in OSG-INF directory.

    Example code for ActionDataExtensionPointService.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="com.sabre.redapp.example3.desktop.template.service.ActionDataExtensionPointService">
       <implementation class="com.sabre.redapp.example3.desktop.template.service.impl.ActionDataExtensionPointServiceImpl"/>
       <service>
          <provide interface="com.sabre.redapp.example3.desktop.template.service.ActionDataExtensionPointService"/>
       </service>
    </scr:component>
  4. Update MANIFEST.MF file with new service in Service-Component section.

    Example code for ActionDataExtensionPointService.xml
    Service-Component: OSGI-INF/TemplateFlowExtPointService.xml,
     OSGI-INF/ActionDataExtensionPointService.xml
  5. Provide Callback registration for RedApp Flow Extension Point Task in plugin.xml. The redapp flow id is dynamo.api.executor and extension point id is execute.

    Example code for ActionDataExtensionPointService.xml
    	<extension
    		 point="com.sabre.edge.dynamo.flow.flowextpoint.registry">
    		<flowExtensionPoint
    			callbackService="com.sabre.redapp.example3.desktop.template.service.TemplateFlowExtPointService:execute"
    			flowId="com.sabre.edge.dynamo.extpoint_flow"
    			extensionPointId="ExampleExtPoint">
    		</flowExtensionPoint>
    
    		<flowExtensionPoint
    			callbackService="com.sabre.redapp.example3.desktop.template.service.ActionDataExtensionPointService:execute"
    			extensionPointId="execute"
    			flowId="dynamo.api.executor">
    		</flowExtensionPoint>
       </extension>
  6. In this sample we are trying to call java service from javascript when we click on Sample Action button in drawer. In SampleService.ts we will send sample command by passing the command name and service id as shown below.

    Format: NGV://command_name/registered_service_class_name:method

    Example: cf('NGV://REDAPP/SERVICE/com.sabre.redapp.example3.desktop.template.ActionDataExtensionPointService:execute')

    Example code for SampleService.ts
    import {AbstractService} from 'sabre-ngv-app/app/services/impl/AbstractService';
    import {ISampleService} from './SampleService';
    import {cf} from '../Context';
    
    export class SampleService extends AbstractService implements ISampleService { // (1)
    
    	static SERVICE_NAME = 'com-sabre-redapp-example3-desktop-template-web-module-SampleService'; // (2)
    
        executeCommand() {
            //send sample command
    
            cf('NGV://REDAPP/SERVICE/com.sabre.redapp.example3.desktop.template.ActionDataExtensionPointService:execute')
    			.setLocalPreference('uiBlocking', true) // (4)
    			.setLocalPreference('silentRequest', true) // (5)
    			.addRequestDataObject({
                    'com_sabre_redapp_example_web_template.Flight': flight // (6)
                })
    			.send();
        }
    }
    1. SampleService extends AbstractService and implements ISampleService

    2. Initialize special static variable SERVICE_NAME with service name.

    3. Execute command invoking sample backend service ActionDataExtensionPointService

    4. Set property to block UI until it recieve response from backend.

    5. Set property to not display response in dynamo. Returned model will be handled by FlightsDetailView

    6. Add data to request. In this case it is current flight.

  7. Here is the response we get after sending the above command when clicked on Sample Action button.

    image

Open Details View (UI Modal):

As part of this same sample (com.sabre.redapp.example3.web.template) we will show how to open details view, a basically UI modal, using LayerService and how to render form after the modal opens and define actions buttons in the Details View.

  1. Create new view for flight details

    Example code for FlightDetailsView.ts
    import {AbstractView} from "sabre-ngv-app/app/AbstractView";
    import {AbstractViewOptions} from "sabre-ngv-app/app/AbstractViewOptions";
    import {Template} from "sabre-ngv-core/decorators/classes/view/Template";
    import {FlightModel} from '../models/FlightModel';
    
    @Template('com-sabre-redapp-example-desktop-template:FlightDetailsView')
    export class FlightDetailsView extends AbstractView<FlightModel> {
    
        constructor(options?: AbstractViewOptions) {
            super(options);
    
        }
    
        initialize() {
            super.initialize();
    
            this.getModel().set('flight', {'number': this.getModel().getNumber(), 'airline': this.getModel().getAirline()}); // (1)
            this.render();
        }
    }
    1. Set up model values for template

      Example code for FlightDetailsView.html
      <div><p>This is Red App Template Flight Details View!</p>
      </div>
      <br>
      {{#with flight}} // (1)
      <div class="myDiv">
          Airline: {{airline}} // (2)
      </div>
      <div class="myDiv">
          Flight number: {{number}} // (2)
      </div>
      {{/with}}
    2. Access data from model

    3. Read properties

  2. Create new action for flight details view

    Example code for FlightAction.ts
    import {AbstractAction} from 'sabre-ngv-app/app/common/views/AbstractAction';
    import {AbstractActionOptions} from 'sabre-ngv-app/app/common/views/AbstractActionOptions';
    import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
    import {Template} from 'sabre-ngv-core/decorators/classes/view/Template';
    import {CssClass} from 'sabre-ngv-core/decorators/classes/view/CssClass';
    import {getService} from '../Context';
    import {I18nService} from 'sabre-ngv-app/app/services/impl/I18nService';
    import {FlightModel} from '../models/FlightModel';
    import {LayerService} from 'sabre-ngv-core/services/LayerService';
    import {FlightDetailsView} from '../views/FlightDetailsView';
    
    const i18nService: I18nService = getService(I18nService);
    
    @Initial<AbstractActionOptions>({
        caption: 'Details Action',
        actionName: 'action',
        type: 'secondary',
        templateOptions: {
        	helpers: {
        		_t: i18nService.getScopedHelper('com-sabre-redapp-example-desktop-template/translations')
        	}
        }
    })
    @Template('com-sabre-redapp-example-web-template:FlightDetailsAction')
    @CssClass('flight-sell-action')
    export class FlightDetailsAction extends AbstractAction<FlightModel> {
    
        selfAction() {
    
            let flight : FlightModel = this.getModel().get('sample-flight');
    
            getService(LayerService).showInModal( // (1)
                new FlightDetailsView({model: flight}), // (2)
                { title: 'Flight details View', display: 'areaView' });
        }
    }
    1. Use LayerService to show view

    2. Pass model to view

      Example code for FlightDetailsAction.html
      <div class="button-col col">
      	<button class="sample-action-button btn btn-success">{{_t "FLIGHT_DETAILS_ACTION"}}</button> // (1)
      </div>
    3. Button name will be taken from translations.json

      Example code for translations.json
      {
        "SAMPLE_ACTION": "Sample Action",
        "FLIGHT_DETAILS_ACTION": "Flight details" // (1)
      }
    4. Add new translation

  3. Add new action to drawer in Main.ts

    Example code for FlightAction.ts
    import {Module} from 'sabre-ngv-core/modules/Module';
    import {DtoService} from 'sabre-ngv-app/app/services/impl/DtoService';
    import {DrawerService} from 'sabre-ngv-app/app/services/impl/DrawerService';
    import {SampleService} from './services/SampleService';
    import {SampleFlightsModel} from './models/SampleFlightsModel';
    import {SampleFlightsView} from './views/SampleFlightsView';
    import {FlightAction} from './views/FlightAction';
    import {getService} from './Context';
    import {registerService} from './Context';
    import {FlightDetailsAction} from './views/FlightDetailsAction';
    
    export class Main extends Module {
        init(): void {
    
            super.init();
    
    		registerService(SampleService);
    
            const dto = getService(DtoService);
            dto.registerDataModel(
                '[d.Structure][o.ExtensionPoint_Summary][com_sabre_redapp_example_desktop_template.SampleFlights]',
                SampleFlightsModel
            );
            dto.registerDataView(SampleFlightsModel, SampleFlightsView);
    
            const drawer = getService(DrawerService);
            drawer.addConfig(['sample-flight'], {
            	details: [
    	        	{
    	        		caption: 'Airline Name',
    	        		print: '{{#with drawer-context-model}}' +
    	        					'{{airline}}' +
    	        				'{{/with}}'
    	        	},
    	        	{
    	        		caption: 'Airline Number',
    	        		print: '{{#with drawer-context-model}}' +
    	        					'{{number}}' +
    	        				'{{/with}}'
    	        	}
            	]
            });
    
    		drawer.addConfig('sample-flight', {
    			actions: [
    				{
    					class: FlightAction
    				},
                    {
                        class: FlightDetailsAction // (1)
                    },
    			]
    		});
        }
    }
    1. Add new detail action to drawer

Execution Steps:

  • Include sample plugin in Eclipse run configurations.

  • Run the application.

  • Login to application using EPR, password and PCC.

  • Execute the command ’SAMPLE://EXTPOINT’.

  • Screenshot of the output below. Click on each row to see the drawer details.

    web template flight details action button
  • Click Flight Details button to open details view.

    web template flight details action view