Skip to main content

com.sabre.redapp.example3.web.tilewidgets Sample

There are two versions of this sample: com.sabre.redapp.example3.desktop.tilewidgets for desktop client and com.sabre.redapp.example3.web.tilewidgets for SR360 Web.

To create a web module (used by both versions) by using TypeScript, refer to the Web Module - Development Cycle chapter.

To see how to contribute a Tile Widget to a drawer and decision support bar with your Web Module, check out the web-src/tilewidgets-sabre-sdk-sample-tilewidgets (TypeScript) and com.sabre.dynamo.example.web.tilewidgets (Eclipse) samples.

Each TypeScript module must have a Main.ts class:

export class Main extends Module { // (1)
    init(): void {
        super.init(); // (2)
        registerService(SampleDrawerService); // (3)
        const dto = getService(DtoService);
        const drawerConfig = new LargeWidgetDrawerConfig(SampleDrawerTile, SampleDrawerView, {
            title: 'Sample Drawer Widget' // (4)
        });

        const tileWidgetConfig = new TileWidgetDrawerConfig(HelloWorldTile);
        getService(DrawerService).addConfig(['shopping-response'], tileWidgetConfig); // (5)

        getService(DrawerService).addConfig(['flight-segment-common', 'shopping-flight-segment'], drawerConfig); // (6)
    }
}
  1. Main class has to extend Module class.

  2. In init function you should call init function from super class.

  3. If you want to create any custom service and use it in your module you need to register it in init function.

  4. Here you can see how to add your own Drawer Tile and Modal View to the module.

  5. Here we add tile widget to decision support bar and the config is TileWidgetDrawerConfig and then add this config to DrawerService but the drawer group is 'shopping-response' in this case. This will be explained in detail below.

  6. Here we add our drawer config from point 4 to the DrawerService which is responsible for managing SR360 drawers and the drawer group is 'flight-segment-common' and 'shopping-flight-segment'. First of them adds to Air Availability, second one to Air Shopping.

In here you can specify where you want your drawer to contribute. You may choose:

  1. shopping-response: Decision Support Bar Drawer (see 1 on the screen below)

  2. shopping-flight-segment: Flight Segment Drawer in Shopping (see 2 on the screen below)

  3. flight-segment-common: Flight Details and Sell Confirmation Segment Drawers in Air Availability

  4. post-pricing-response-widget: Pricing Drawer after Save & Sell (see number 3 on the second screenshot)

  5. air-availability-details-sdk: Flight Details Segment in Air Availability

  6. air-availability-sell-confirmation-sdk: Sell Confirmation Segment in Air Availability

  7. car: Car Segment details Drawer in car search

image117
pricing tile

You can see how to create your own Tile Widget below:

import {Tile} from 'sabre-ngv-app/app/widgets/drawer/views/elements/Tile';
import {TileOptions} from 'sabre-ngv-app/app/widgets/drawer/views/elements/TileOptions';
import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
import {FlightSegment} from 'sabre-ngv-app/app/common/data/flight/FlightSegment';
import {WithoutFocusOnClick} from 'sabre-ngv-app/app/common/mixins/WithoutFocusOnClick';
import {Mixin} from 'sabre-ngv-core/decorators/classes/Mixin';
import {CssClass} from 'sabre-ngv-core/decorators/classes/view/CssClass';


@CssClass('com-sabre-redapp-example3-web-tilewidgets-web-module', { overwrite: false })
@Initial<TileOptions>({
    caption: 'Air Tile Sample'
})
@Mixin(WithoutFocusOnClick)
export class SampleDrawerTile extends Tile<FlightSegment> implements WithoutFocusOnClick {

    selfDrawerContextModelPropagated(cpa: FlightSegment): void {

        const segments: FlightSegment[] = cpa.getFlightSegmentsAsArray();

        segments.forEach((segment, key) => {
            console.log('Segment id: ' + segment.getSegmentId());
            console.log('Flight Number: ' + segment.getFlightNumber());
            console.log('Origin: ' + segment.getOriginIata());
            console.log('Destination: ' + segment.getDestinationIata())
        });

        let cssClass;

        const flightNumber = cpa.getFlightNumber();

        if(cpa.getFlightNumber() % 2) {
            cssClass = 'blue';
        } else {
            cssClass = 'grey';
        }

        let titleContent = `<span class='${cssClass}'> Flight Number: ${flightNumber}</span>`;
        this.setDataContent(titleContent);

    }
}

Your class needs to extend the Tile class.

You can initialize the content of your tile in the selfDrawerContextModelPropagated function.

Now it is time to implement your own View which will be displayed when you click on the tile.

import { Bound } from 'sabre-ngv-core/decorators/methods/Bound';
import { CssClass } from 'sabre-ngv-core/decorators/classes/view/CssClass';
import { Template } from 'sabre-ngv-core/decorators/classes/view/Template';
import { AbstractView } from "sabre-ngv-app/app/AbstractView";
import { AbstractModel } from "sabre-ngv-app/app/AbstractModel";
import { FlightSegment } from "sabre-ngv-app/app/common/data/flight/FlightSegment";
import { getService } from "../Context";
import { SampleDrawerService } from '../services/SampleDrawerService';

@CssClass('com-sabre-redapp-example3-web-tilewidgets-web-module')
@Template('com-sabre-redapp-example3-web-tilewidgets-web-module:SampleDrawerView') // (1)
export class SampleDrawerView extends AbstractView<AbstractModel> { // (2)

    selfDrawerContextModelPropagated(cpa: FlightSegment) { // (3)
        let flightNumber = cpa.getFlightNumber();
        let sampleServiceResponse = getService(SampleDrawerService).sampleMethod('my parameter');
        const flattened: FlightSegment[] = cpa.getFlightSegmentsForAllConnections().flatMap(segment => segment);

        this.getModel().set('segmentsCount', flattened.length);
        this.getModel().set('flightNumber', { 'response': flightNumber });
        this.getModel().set('myServiceResponse', { 'response': sampleServiceResponse });
        this.render();
    }

    @Bound
    private updateModel(data: Object) {
        let response = JSON.parse(data.toString()).responseBody;

        console.log(response.success);
        console.log(response.errors);

        this.getModel().set('externalServiceResponse', { 'response': !_.isUndefined(response.payload.data) ? response.payload.data.substring(0, 500) : 'No Connection' }); // (4)
        this.render(); // (5)
    }

    @Bound
    private updateModel2(data: Object) {
        this.getModel().set('swsResponse2', { 'response': data.toString().substring(0, 1200) });
        this.render();
    }

}
  1. This is how to bind an HTML template to a view. In this case we bind SampleDrawerView.html from the ‘src/templates’ directory of our tilewidgets-sabre-sdk-sample-tileWidgets module.

  2. Your view should extend the AbstractView class.

  3. You can initialize your view using the selfDrawerContextModelPropagated function. Here you have access to data model " in this case FlightSegment (FlightConnection for Decision Support Bar). There is no public model for post-pricing-response-widget, so the selfDrawerContextModelPropagated should not take any arguments from a tile that contributes to that drawer.

  4. Here is how you can expose some data to the HTML template. In our case we expose flight number from FlightSegment data model. In our template it is available as flight in flightNumber:

  5. Do not forget to call the render function to render your view.

{{#with flightNumber}}
<div class="myDiv">
    Flight number: {{response}}
</div>
{{/with}}
<br>
<div>
    Total segments count: {{segmentsCount}}
</div>
<br>

Adding a button to a drawer’s button list view

Custom button on drawer button list

Custom button on drawer button list

In case you need to add a button to drawer buttons view, changes similar to these described below must be done in your code.

Create action definition and add actions to drawer config in Main.ts:

import {SampleDrawerListAction} from './views/SampleDrawerListAction'; // (1)
import {AbstractAction} from 'sabre-ngv-app/app/common/views/AbstractAction'; // (2)
import {AbstractActionOptions} from 'sabre-ngv-app/app/common/views/AbstractActionOptions';
import {Descriptor} from 'sabre-ngv-app/_types';

export class Main extends Module {
    init(): void {
        super.init();
        registerService(SampleDrawerService);
        const dto = getService(DtoService);

		const swsServiceAsyncAction : Descriptor<AbstractAction, AbstractActionOptions> = {
               caption: 'Call sws service async',
               actionName: 'sws-service-async-response',
               type: 'secondary',
               className: 'app.common.views.Button'
        }

        const drawerConfig = new LargeWidgetDrawerConfig(SampleDrawerTile, SampleDrawerView, {
            title: 'Sample Drawer Widget',
			actions: [swsServiceAsyncAction]
        });

        const tileWidgetConfig = new TileWidgetDrawerConfig(HelloWorldTile);
        getService(DrawerService).addConfig(['shopping-response'], tileWidgetConfig);

        getService(DrawerService).addConfig(['flight-segment-common', 'shopping-flight-segment'], drawerConfig);

		getService(DrawerService).addConfig(['post-pricing-response-widget'], new TileWidgetDrawerConfig(PricingDrawerTile));

		const sampleDrawerListAction : Descriptor<AbstractAction, AbstractActionOptions> = {  // (3)
            class: SampleDrawerListAction  // (4)
        }

        getService(DrawerService).addConfig(['flight-segment-common', 'shopping-flight-segment'], {  // (5)
            actions: [sampleDrawerListAction]  // (6)
        });

        const carSampleDrawerListAction : Descriptor<AbstractAction, AbstractActionOptions> = {
            class: CarSampleDrawerListAction
        };

        const carActionsConfig = new ActionsDrawerConfig([carSampleDrawerListAction], -800);

        getService(DrawerService).addConfig(['car'], carActionsConfig);
    }
}
  1. Import class with a custom button definition.

  2. Import classes required for a custom action definition. Those classes are: AbstractAction, AbstractActionOptions, Descriptor

  3. Declare a custom action.

  4. Provide custom button class to action definition options

  5. Add configuration with action to the drawer group. In this example the drawer group is flight-segment-common.

  6. Add a list of custom actions to the drawer group configuration.

Create custom action button class to be used in the drawer button list:

import {Button} from 'sabre-ngv-app/app/common/views/Button';    // (1)
import {Initial} from 'sabre-ngv-core/decorators/classes/Initial';
import {AbstractActionOptions} from 'sabre-ngv-app/app/common/views/AbstractActionOptions';
import {LayerService} from 'sabre-ngv-core/services/LayerService';
import {SampleDrawerListActionView} from './SampleDrawerListActionView';
import {getService} from '../Context';
import {FlightSegment} from 'sabre-ngv-app/app/common/data/flight/FlightSegment';


@Initial<AbstractActionOptions>({   // (2)
    caption: 'Sample Drawer List Action', // (3)
    type: 'secondary'
})
export class SampleDrawerListAction extends Button { // (4)

    selfDrawerContextModelPropagated(cpa: FlightSegment) { // (5)
        this.getModel().set('cpa', cpa);  // (6)
    }

    selfAction() {  // (7)
        getService(LayerService).showInModal(  // (8)
            new SampleDrawerListActionView({model: this.getModel().get('cpa')}),
            { title: 'Sample drawer list action', display: 'areaView' });
    }
}
  1. Import required classes to define a custom button with action.

  2. Define action options.

  3. Provide button caption in action options.

  4. Custom action button has to extend the Button class.

  5. Implement the selfDrawerContextModelPropagated method to be automatically called to propagate drawer model for the widget. Declare the specific type used by the drawer as an argument of the method - in this case it is FlightSegment.

  6. Add the drawer data model to the button data model under the chosen key name. In this example the key name is cpa.

  7. Define the selfAction method. It is the default action method and it will be called when user clicks on the button.

  8. Define the logic to be executed when the button is clicked - in this sample a simple pop-up will be shown. The pop-up is initialized with drawer data model.

Create custom pop-up to demonstrate result of button action:

import {AbstractView} from "sabre-ngv-app/app/AbstractView"; // (1)
import {AbstractViewOptions} from "sabre-ngv-app/app/AbstractViewOptions";
import {Template} from "sabre-ngv-core/decorators/classes/view/Template";
import {FlightSegment} from 'sabre-ngv-app/app/common/data/flight/FlightSegment'; // (2)

@Template('tilewidgets-sabre-sdk-sample-tilewidgets:SampleDrawerListActionView') // (3)
export class SampleDrawerListActionView extends AbstractView<FlightSegment> {  // (4)

    constructor(options?: AbstractViewOptions) {  // (5)
        super(options);
    }

    initialize() {   // (6)
        super.initialize(); // (7)

        this.getModel().set('flightNumber', {'response': this.getModel().getFlightNumber()}); // (8)
        this.render(); // (9)
	}
}
  1. Import classes required for the popup. Those classes are: AbstractView, AbstractViewOptions, Template.

  2. Import data model class. In this example it is FlightSegment.

  3. Bind an HTML template to a view. In this case we bind SampleDrawerListActionView.html from ‘src/templates’ directory of our tilewidgets-sabre-sdk-sample-tileWidgets module.

  4. Example popup has to extend the AbstractView class with the FlightSegment data model.

  5. Constructor with options. Options argument has to be AbstractViewOptions type.

  6. Define the initialize method which initializes view.

  7. Parent initialize method call.

  8. Add flight number the to popup data model under the flightNumber key.

  9. Call the render method to redraw the popup.

Html template for a custom popup:

<div>
    <p>This is Red App Drawer List Action View!</p>
</div>
<br>
{{#with flightNumber}}
<div class="myDiv">
    Flight number: {{response}}
</div>
{{/with}}

React Air Availability Tiles

Another way to create tiles in air availability is to use react component.

  1. Create a tile.

To create a tile you need a tile content (React component), tile title and optionally action that will be performed when the user clicks on the tile.

React component, that will be used as tile content:

import * as React from 'react';
import { PublicAirAvailabilityData } from 'sabre-ngv-airAvailability/PublicAirAvailabilityData';

export const airAvailTile = (data: PublicAirAvailabilityData) : React.ReactElement => <div className={'sdk-air-av-custom-tile-content'}>
    Flight Numbers:
    <ol>
    {
        data.flightSegments.map((segment, index) => <li key={index}>{segment.MarketingAirline.FlightNumber}</li>)
    }
    </ol>
</div>;

Action that will be performed when user selects the tile, in this we just print all data into the javascript console.

const action = (data: PublicAirAvailabilityData) => {
    console.log('Public tile data: ', data);
}

Both tile content and action are provided with a set of data related to the selected flight.

export interface PublicAirAvailabilityData {
    flightSegments: [{
        ArrivalDateTime: string,
        BookingClassAvail:  [
            {
                Availability: string,
                ResBookDesigCode: string
            }
        ],
        /** Shows if arrival date is different from requested one, difference in days */
        ChangeArrivalDateIndicator: string,
        /** Shows if arrival date is different from departure date, difference in days */
        ChangeDayIndicator: string,
        /** Shows if departure date is different from requested departure date, difference in days */
        ChangeDepartureDateIndicator: string,
        DepartureDateTime: string
        DestinationLocation: {
            ChangedAirport: boolean,
            EncodeDecodeElement: {
                City: string,
                Code: string,
                FullyDecoded: string,
                SimplyDecoded: string
            }
        },
        DisclosureAirline: {
            EncodeDecodeElement: {
                Code: string,
                FullyDecoded: string,
                SimplyDecoded: string
            },
            Text: string[]
        }
        DisplayRPH: string,
        ETicket: boolean,
        ElapsedTime: number,
        Equipment: {
            EncodeDecodeElement: {
                Code: string,
                FullyDecoded: string,
                SimplyDecoded: string
            }
        }
        FlightDetails:
        {
            Canceled: boolean,
            Text: string[],
            TotalTravelTime: number
        },
        Meal: [
            {
                EncodeDecodeElement: {
                    Code: string,
                    FullyDecoded: string,
                    SimplyDecoded: string
                }
            }
        ]
        OriginDestinationOptionRPH: number
        OriginLocation: {
            ChangedAirport: boolean,
            EncodeDecodeElement: {
                City: string,
                Code: string,
                FullyDecoded: string,
                SimplyDecoded: string
            }
        },
        RPH: number,
        SmokingAllowed: boolean,
        StopQuantity: number,
        MarketingAirline: {
            FlightNumber: string,
            ParticipationLevel: string
            EncodeDecodeElement: {
                Code: string,
                FullyDecoded: string,
                SimplyDecoded: string
            }
        }
    }],
    rowRPH: number
}
  1. Next add above components to the search drawer.

To do it, use the PublicAirAvailabilityService service and call createAirAvailabilitySearchTile method to add the tile to search result drawer or createAirAvailabilitySellConfirmationTile to add tile to Sell Confirmation Drawer.

const serv : PublicAirAvailabilityService = getService(PublicAirAvailabilityService);

serv.createAirAvailabilitySearchTile(airAvailTile, action , 'Air Availability Tile Added By SDK');

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 a different extension point you will contribute to. For web module Red App it should be:

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

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