Add the Call Panel component to your application

The previous topic showed how to build a basic JET application and how to deploy it in your Fusion media toolbar.

Next, we need to show incoming call notifications, and buttons to accept or disconnect a call in the media toolbar application. To do this, you can build your logic to render UI elements based on your requirements. As part of the steps in this series, the UI elements required for showing the call notification, and buttons to accept and disconnect calls are encapsulated as a custom JET component.

States of the Call Panel component

The Call Panel component has three states:
  • Ringing: The Call Panel component is in the Ringing state when the agent receives an incoming call. The Call Panel component will also be in a ringing state when the agent starts an outbound call which isn't yet picked up by the customer. Your application. During this state, the Call Panel component has information about the incoming call such as contact name (which is retrieved from the Fusion application), IVR data, and 2 buttons for accepting or rejecting a call.
  • Accepted: The Call Panel component is in the Accepted state when the agent accepts an incoming call from the customer. The Call Panel component is also in an Accepted state when a customer accepts an outbound call started by an agent. During this state, the call-panel component will have the call hangup button and a call-in-progress animation in addition to the incoming call information.
  • Disconnected: The Call Panel component moves to the Disconnected state when an agent rejects an incoming call or disconnects an ongoing call.

Create a custom component in your application

  1. Open the root folder of your toolbar application in the terminal and run the command: ojet create component call-panel.

    This creates the call panel custom component in the src/ts/jet-composites folder.

    For more information, see Create an Oracle JET Web Component.

Update the component's files based on your requirements

To update the component's code based on your requirements, see the following files:
  • To update the src/ts/jet-composites/call-panel-view.html file:
    <div class="oj-sm-margin-4x oj-flex oj-sm-justify-content-center">
        <div class="oj-flex oj-bg-neutral-170 oj-sm-padding-6x call-panel oj-color-invert">
            <div
                    class="oj-sm-12 oj-flex oj-sm-flex-direction-column oj-sm-align-items-center oj-sm-justify-content-space-between">
                <div class="oj-sm-margin-4x-top oj-flex oj-sm-flex-direction-column oj-sm-align-items-center top-panel">
                    <div class="oj-flex-item oj-typography-heading-xs oj-sm-margin-5x-bottom">
                        <!-- Shows the text Incoming Call / Calling in the component based on the call direction -->
                        <oj-bind-if test="[[callContext().direction === 'inbound']]">
                            Incoming Call
                        </oj-bind-if>
                        <oj-bind-if test="[[callContext().direction === 'outbound']]">
                            Calling
                        </oj-bind-if>
                    </div>
     
                    <!-- Adds a phone icon as avatar in the component -->
                    <oj-c-avatar
                            class="oj-avatar-8xl"
                            role="img"
                            icon-class="oj-ux-ico-call-incoming"
                            background="green"></oj-c-avatar>
                    <br/><br/>
                    <div id="progressBarContainer" class="oj-flex-item">
                        <oj-bind-if test="[[callContext().state === 'ACCEPTED']]">
                            <!-- Show a call in progress animation during the ACCEPTED state -->
                            <oj-c-progress-bar
                                    id="progressBar"
                                    aria-label="basic progress bar"
                                    value="-1"></oj-c-progress-bar>
                        </oj-bind-if>
                    </div>
     
                </div>
                <!-- Show caller information -->
                <div class="oj-sm-flex oj-sm-flex-direction-column call-details">
                    <div class="oj-flex-item oj-typography-heading-md oj-sm-margin-2x-vertical">
                        <oj-bind-text value="[[callContext().callerName]]"></oj-bind-text>
                    </div>
                    <div class="oj-flex-item oj-typography-subheading-xs">
                        <oj-bind-text value="[[callContext().phonenumber]]"></oj-bind-text>
                    </div>
                    <table class="ivr-data-table oj-typography-body-sm oj-sm-margin-4x oj-helper-text-align-left">
                        <oj-bind-for-each data="[[ivrDataProvider]]">
                            <template>
                                <tr>
                                    <td><oj-bind-text value="[[$current.data.key]]"></oj-bind-text></td>
                                    <td>:</td>
                                    <td><oj-bind-text value="[[$current.data.value]]"></oj-bind-text></td>
                                </tr>
                            </template>
                        </oj-bind-for-each>
                    </table>
                </div>
                <div class="oj-flex oj-sm-justify-content-center oj-sm-margin-2x-vertical">
                    <oj-bind-if test="[[callContext().state === 'ACCEPTED']]">
                        <oj-c-button id="hold" display="icons" label="hold">
                            <span slot="startIcon" class="oj-ux-ico-pause-circle"></span>
                        </oj-c-button>
                    </oj-bind-if>
     
                </div>
                <div class="oj-flex oj-sm-justify-content-center">
                    <!-- Show call accept and disconnect buttons in the component -->
                    <oj-bind-if test="[[callContext().state === 'RINGING' && callContext().direction === 'inbound']]">
                        <!-- Show call accept button only during inbound call RINGING state -->
                        <oj-c-button id="acceptCall" display="icons" label="Accept" chroming="borderless"
                                     on-oj-action="[[ acceptClicked ]]"
                                     class="oj-sm-margin-2x-horizontal">
                            <span slot="startIcon"
                                  class="oj-ux-ico-call oj-sm-padding-4x oj-sm-padding-10x-horizontal call-accept"></span>
                        </oj-c-button>
                    </oj-bind-if>
                    <oj-c-button id="declineCall" display="icons" label="Decline" chroming="danger"
                                 on-oj-action="[[ disconnectClicked ]]"
                                 class="oj-sm-margin-2x-horizontal">
                        <span slot="startIcon" class="oj-ux-ico-call-end oj-sm-padding-4x oj-sm-padding-10x-horizontal"></span>
                    </oj-c-button>
                </div>
            </div>
        </div>
    </div>
  • To update the src/ts/jet-composites/call-panel-viewModel.ts file:
    "use strict";
     
    import * as ko from "knockout";
    import Context = require("ojs/ojcontext");
    import Composite = require("ojs/ojcomposite");
    import ArrayDataProvider = require('ojs/ojarraydataprovider');
    import "oj-c/button";
    import "ojs/ojknockout";
    import "oj-c/avatar";
    import "oj-c/progress-bar";
     
    interface CallContext {
        phonenumber: string;
        callerName: string;
        direction: string;
        eventId: string;
        ivrData: { [key: string]: string };
        state: string;
    }
     
    export default class ViewModel implements Composite.ViewModel<Composite.PropertiesType> {
        busyResolve: (() => void);
        composite: Element;
        properties: Composite.PropertiesType;
        callContext: ko.Observable<CallContext>;
        ivrDataProvider: ArrayDataProvider<string, { [key: string]: string }>;
     
        constructor(context: Composite.ViewModelContext<Composite.PropertiesType>) {
            //At the start of your viewModel constructor
            const elementContext: Context = Context.getContext(context.element);
            const busyContext: Context.BusyContext = elementContext.getBusyContext();
            const options = { "description": "Web Component Startup - Waiting for data" };
            this.busyResolve = busyContext.addBusyState(options);
     
            this.composite = context.element;
     
            // Properties passed to the component
            this.properties = context.properties;
     
            this.callContext = ko.observable(this.properties.callContext);
     
            this.ivrDataProvider = new ArrayDataProvider(this.parseIvrData(), {
                keyAttributes: 'key'
            });
     
            //Once all startup and async activities have finished, relocate if there are any async activities
            this.busyResolve();
        }
     
        parseIvrData(): any {
            let data: any[] = [];
            if (this.callContext()?.ivrData) {
                data = Object.keys(this.callContext().ivrData).map((key: string) => {
                    return {
                        key: key,
                        value: this.callContext().ivrData[key]
                    }
                });
            }
            const ivrData: any = ko.observableArray(data);
            return ivrData;
        }
     
        public acceptClicked: (event: any) => void = (event:  any): void => {
            // Logic to fire and event to the container that the accept button is clicked
            const formattedEvent: object = {bubbles: true, cancelable: false, detail: {}};
            const acceptButtonClickedEvent: CustomEvent = new CustomEvent('acceptButtonClicked', formattedEvent);
            this.composite.dispatchEvent(acceptButtonClickedEvent);
        }
     
        public disconnectClicked: (event: any) => void = (event:  any): void => {
            // Logic to fire and event to the container that the disconecct button is clicked.
            // If the call is in ACCEPTED state, the value 'HANGUP' is passed as the event payload to the container,
            // Otherwise, the value 'REJECT' is passed as the event payload
            const disconnectionState: string = (this.callContext().state === 'ACCEPTED') ? 'WRAPUP' : 'REJECT';
            const formattedEvent: object = {bubbles: true, cancelable: false, detail: { disconnectionState }};
            const disconnectButtonClickedEvent: CustomEvent = new CustomEvent('disconnectButtonClicked', formattedEvent);
            this.composite.dispatchEvent(disconnectButtonClickedEvent);
        }
     
        //Lifecycle methods - implement if necessary
     
        activated(context: Composite.ViewModelContext<Composite.PropertiesType>): Promise<any> | void {
     
        };
     
        connected(context: Composite.ViewModelContext<Composite.PropertiesType>): void {
     
        };
     
        bindingsApplied(context: Composite.ViewModelContext<Composite.PropertiesType>): void {
     
        };
     
        propertyChanged(context: Composite.PropertyChangedContext<Composite.PropertiesType>): void {
            if (context.property === 'callContext') {
                // Update callContext observable based on the propertyChange
                this.callContext({...this.callContext(), state: context.value.state});
            }
        };
     
        disconnected(element: Element): void {
     
        };
    };
  • To update the src/ts/jet-composites/call-panel-styles.css file:
    call-panel:not(.oj-complete) {
        visibility: hidden;
    }
     
    call-panel[hidden] {
        display: none;
    }
     
    .call-panel {
        min-height: 450px;
        min-width: 300px;
        border-radius: 10px;
    }
     
    .top-panel {
        color: #dfdfdf;
    }
     
    .call-accept {
        background-color: #28b328;
    }
     
    .call-accept:hover {
        background-color: #239b23;
    }
     
    .call-details {
        text-align: center;
        color: #ffffff;
    }
     
    .ivr-data-table {
        color: #c4c4c4;
    }
     
    #progressBarContainer {
        width: 100%;
    }
  • To update the src/ts/jet-composites/component.json file:
    {
      "name": "call-panel",
      "version": "1.0.0",
      "jetVersion": "^15.1.0",
      "displayName": "A user friendly, translatable name of the component.",
      "description": "A translatable high-level description for the component.",
      "properties": {
        "callContext": {
          "description": "The Call context details.",
          "displayName": "Call Context",
          "type": "object"
        }
      },
      "methods": {},
      "events": {
        "acceptButtonClicked": {
          "displayName": "acceptButtonClicked",
          "description": "Will be called when accept button is clicked.",
          "bubbles": true,
          "cancelable": true,
          "detail": {
          }
        },
        "disconnectButtonClicked": {
          "displayName": "disconnectButtonClicked",
          "description": "Will be called when disconnect button is clicked.",
          "bubbles": true,
          "cancelable": true,
          "detail": {
          }
        }
      },
      "slots": {}
    }

References

OJET custom components