Desktop and UI Extensibility

Get Involved. Join the Conversation.

Topic

    Shiv Tenneti
    Workspace Extensions: Building Extensions: Contact Lookup...
    Topic posted June 20, 2017 by Shiv TennetiBronze Medal: 1,250+ Points 
    2466 Views, 9 Comments
    Title:
    Workspace Extensions: Building Extensions: Contact Lookup Toolbar There are various events and...
    Content:

    Workspace Extensions: Building Extensions: Contact Lookup Toolbar

    There are various events and functions available in the extension framework that will allow you to create feature packed extensions for the BUI desktop. In this article, we will be exploring some these capabilities as we build a simple contact lookup tool inside an extension bar, leveraging some of the events and functions available in the extension framework.

    You will learn:

    1. How to create an extensionbar & render html inside the extensionbar
    2. Use GlobalContext to get info
    3. Invoke the Connect REST API’s
    4. Pop workspaces, set workspace fields from an extension
    5. Pop a report: set report filters
    6. Events such as Find and Focus

    While the lookup tool by itself is very basic in its functionality, the idea here is to use the various capabilities available in the extension api to build a phone number lookup tool. This is similar to a CTI application toolbar, only here the phone number is manually entered into a textbox. As you build your extensions, we hope that you will be able to reference the code snippets from this article to help you along.

    Setup

    Before we being, let’s set up the following directory structure to organize the files that will be used for the toolbar.

    <top_level_dir>

    • init.html : This is the init file that will contain code to load the extension bar
    • <sub_dir>
      • toolbar.html : This file will have the UI elements, api calls and all related UI actions and logic

    Now let’s add the code for the extension bar.

    ExtensionBar

    The init.html file will contain the code for registering and loading the Extension Bar as shown below.

    <html>
    <head>
    <script type="text/javascript" src="<path_to_your_jquery_lib>/jquery.min.js"></script>  
    <script type="text/javascript" src="https://<sitename>/AgentWeb/module/extensibility/js/client/core/extension_loader.js"></script>
    <script>
    ORACLE_SERVICE_CLOUD.extension_loader.load("PhoneNumber_Lookup_Extension" , "1")
    .then(function(extensionProvider)
    {
        extensionProvider.registerUserInterfaceExtension(function(IUserInterfaceContext)
      {
          IUserInterfaceContext.getExtensionBarContext().then(function(IExtensionBarContext)     
         {
         IExtensionBarContext.setDockable(true)
          IExtensionBarContext.getExtensionBarItem('id').then(function(IExtensionBarItem)  
         {
             IExtensionBarItem.setContentUrl('/<sub_dir>/toolbar.html');
             IExtensionBarItem.render();
           });
        });
      });
    });
    </script>
    </head>
    </html>
    

    Figure 1 - init.html

    The Extension Bar has a few properties that will determine the behavior when rendered in BUI. In init.html, we’re setting the setDockable property to true which will allow the extension bar to be docked to the left, right, or top of the desktop.

    Next, in the IExtensionBarItem, we’re using the setContentUrl function to render the html code inside the extension bar. In this example, we’re loading the toolbar.html. This function (setContentUrl) can be used to load an external application inside the BUI desktop.

    ExtensionBar UI

    Now that we have the extension bar code, we will add the following UI elements and some css in the toolbar.html file: 

    • Textbox: Phone Number input 
    • Contact Lookup Button: When this button is clicked, we do a phone number lookup and perform either a workspace or report pop
    • Check box: If the “Create Incident” option is checked, then an incident is created and workspace is opened

     

    <html>
    <head>
    <style>
      .tab{
       padding:2px;
       border-style:outset;
      }
      .tab:active {
       border-style:inset;
      }
      .tab:hover > .focus {
       color: blue;
      }
      sup:hover{
       color:red
      }
      .focus{
       display:inline;
      }
    </style>
    
    </head>
    <body>
    <div id="cti-container" >
      <div>
       <table>
        <tr>
         <td><b>Incoming Call:</b> <input type="text" id="phoneTxt" /></td>
         <td><b><button id="acceptBtn">Contact Lookup</button></td>
         <td><input type="checkbox" id="createInc" name="createInc">Create Incident</td>
        </td>
       </table>
      </div>
    </div>
    <div id="cti-calls">
      <b>Ongoing Calls:</b><ul style="list-style:none;display:flex" id="calls">
      </ul>
    </div>
    </body>
    </html>
    

    Figure 2 - toolbar.html: HTML UI Components

    GlobalContext Data

    Next we are going to make use of the IGlobalContext function to retrieve info such as the interface REST url, the accountID for the logged in agent, and the session token that will be used in the REST API authentication. Using the session token allows us to not have to hardcode any user credentials and is the preferred approach for api authentication for extensions. We will collect the GlobalContext data when the extension has loaded into the BUI Agent Desktop.

     

    var workspaceRecordMap = {};
    $(document).ready(function() {
     var restUrl = "";
     var acctID = "";
     var sessionID = "";
     var phoneNum = "";
     var result = "";
     var createIncident = false;
     var globalContext = null;
     var extensionProvider = null;
    
     //grab globalcontext data
     ORACLE_SERVICE_CLOUD.extension_loader.load("PhoneNumber_Lookup_Extension ", 
    "1").then(function (sdk) {
      extensionProvider = sdk;
      sdk.getGlobalContext().then(function(gc){
       globalContext = gc;
       restUrl = globalContext.getInterfaceServiceUrl("REST");
       acctID = globalContext.getAccountId().toString();
       globalContext.getSessionToken().then(
       function(sessionToken){
        sessionID = sessionToken;
        console.log("URL: " + restUrl);
        console.log("Acct: " + acctID);
        console.log("Session: " + sessionID);
       }
      );
     });
    });
    

    Figure 3 - toolbar.html: GlobalContext Data

    Next, we add a click handler on the contact lookup button to read the phone number from the textbox and initiate the phone number lookup.

    We’re going to pass the phone number into a contactLookup function and then process the results returned by that function. There are 3 possibilities from the phone lookup: 

    1. If a contact match is found, the workspace is popped for editing
    2. If there is no match, a new contact record is created and the office phone field is set to the value provided in the text box. 
      • For the above two cases if the “Create Incident” option is checked, an incident is created using the Connect REST API and the incident workspace is opened for editing
    3. If there are multiple contacts found, then a report is popped to show all the contacts found. 
    $("#acceptBtn").click(function() {      
    createIncident = $('#createInc').is(':checked');
    
    if($("#phoneTxt").val() == "") 
    {
      alert("Please enter a phone number!");
      return;
    }
    else {
      phoneNum = $("#phoneTxt").val();
      var count = "";
    
      //lookup contact      
      contactLookup(restUrl, sessionID, phoneNum).done(function(jsonData){
    
       result = jsonData;
       count = jsonData.items[0].count;
    
      });
    
      // actions
      switch(count) {
       case 0:      //Not contact found, so create the contact
        if(!createIncident){
         openContactRecord(0,phoneNum);
        } else {
         globalContext.getSessionToken().then(
          function(sessionToken){
           createContactRecord(restUrl, 
    sessionToken,phoneNum).done(
            function(contactJsonObj){       
             createIncidentRecord(restUrl, 
    sessionToken,contactJsonObj.id).done(
              function(incidentJsonObj){
       openWorkspaceRecord('Incident',incidentJsonObj.id,phoneNum);
            }
           );
          }
         );
        }
       );
      }
      break;
     case 1: //Contact match found, open the workspace for editing
      if(!createIncident){
       openContactRecord(result.items[0].rows[0][0],phoneNum);
      } else {
       globalContext.getSessionToken().then(
        function(sessionToken){
         createIncidentRecord(restUrl, 
    sessionToken,result.items[0].rows[0][0]).done(
          function(incidentJsonObj){
    
      openWorkspaceRecord('Incident',incidentJsonObj.id,phoneNum);
          }
         );
        }
       );
      }
      break;
     default:       //multiple contacts found, so open a report     
      openReport();
      break;
     }
    }
    });
    

    Figure 4 - toolbar.html: Lookup Button Click handler

    Connect REST API

    Next, we will invoke the Connect REST api for the lookup and record create operations.

    contactLookup(): This function takes 3 arguments, the restURL, sessionID needed for authentication and the phoneNumber needed for the lookup. We will make an ajax call with a roql query to look up the phone number and return the results.

    createIncidentRecord(): This function takes the restURL, the sessionID and the contactID as the input arguments to create a new incident.

    createContactRecord(): This function  takes the restURL, the sessionID and the phoneNumber as the input arguments to create a new contact. The phone number entered in the textbox is set as the office phone number for the contact.

    function contactLookup(restUrl, sessionID, phoneNumber)
    {
      console.log(restUrl + ' |-| ' + sessionID + ' |-| ' + phoneNumber);
      var ajaxUrl = restUrl + "/connect/v1.3/queryResults/?query=SELECT id FROM 
    contacts WHERE phones.phonelist.number='"+ phoneNumber + "'";
      return $.ajax({
       type: "GET",
       async: false,
       url: ajaxUrl,
       beforeSend: function (xhr) { xhr.setRequestHeader("Authorization
    "Session " + sessionID); }
      });
    }
    function createIncidentRecord(restUrl, sessionToken,contactId){
      return $.ajax({
      url: restUrl + '/connect/v1.3/incidents',
      method: "POST",
      headers:{'Authorization':'Session '+sessionToken},
      data: JSON.stringify({
       primaryContact:
       {
        id: parseInt(contactId)
       },
       subject: "CTI Incident"
       }),
      dataType: "json"
     });
    }
    function createContactRecord(restUrl, sessionToken,phoneNumber){
      return $.ajax({
       url: restUrl + '/connect/v1.3/contacts',
       method: "POST",
       headers:{'Authorization':'Session '+sessionToken},
       data: JSON.stringify({
        name : {
         first: 'unknown',
         last: 'contact'
        },
        phones :[
        {
         number: phoneNumber,
         phoneType: {
          id:1
         }
        }
        ]
       }),
      dataType: "json"
     });
    }
    

    Figure 5 - toolbar.html: API function calls

    Screenpop: Workspace

    Now we add the logic to open workspaces on the Agent Desktop. We will use the following functions provided in the Extensibility Framework to perform the screen pop operations.

    WorkspaceRecord.createWorkspaceRecord(): Open a new workspace record. We will need to provide the object type for the workspace that needs to be opened (Contact, Incident, etc.).

    WorkspaceRecord.editWorkspaceRecord(): Open an existing record for editing. We will need to provide the object type for the workspace that needs to be opened (Contact, Incident, etc.) and the recordID.

    We will add two functions to handle the workspace screeenpops using the api functions described above. 

    • First we create the openContactRecord function which takes the contactID and the phone number as input arguments. This will either open either a new contact workspace or the existing contact record for editing.

    To open a new contact workspace, we will use the createWorkspaceRecord function  and then the updateField method to set the office phone value on the workspace. 

    • Next, we add the openWorkspaceRecord function to open the Incident workspace.

    In the callback, we’re keeping track of the workspaces that are opened on the Desktop. We will later use the FindandFocus event to toggle between all the open workspaces.

    //open contact workspace
    function openContactRecord(recordID,phoneNum)
    {
      console.log("Opening record ID : " + recordID);
      ORACLE_SERVICE_CLOUD.extension_loader.load("MSE_CTI_MediaBar" , "1")
      .then(function(extensionProvider)   {
       extensionProvider.registerWorkspaceExtension(function(WorkspaceRecord) {
        if(recordID === 0) { //new record
         WorkspaceRecord.createWorkspaceRecord('Contact', 
    function(workspaceRecord){
          callback(workspaceRecord,phoneNum);
          //set phone field
          WorkspaceRecord.updateField('Contact.PhOffice', phoneNum);
         }); 
        }
        else {
         WorkspaceRecord.editWorkspaceRecord('Contact', recordID, 
    function(workspaceRecord){
          callback(workspaceRecord,phoneNum);
         });
        }
       });
      });
      function callback(workspaceRecord,phoneNum)
      {
       addWorkspaceButton(workspaceRecord,phoneNum);        
      }
    }
    
    //pop incident workspace
    function openWorkspaceRecord(workspaceType,incidentId,phoneNum){
      extensionProvider.registerWorkspaceExtension(
      function(workspaceRecord){
      workspaceRecord.editWorkspaceRecord(workspaceType,incidentId,function(workspaceRecord){
      callback(workspaceRecord,phoneNum);
     });
     });
    }
    

    Screenpop: Report

    Finally, we have to handle the case where multiple contact records found for the phone number.  So we will need to display all the matching contact records and to do this we will pop a report. The Extensibility Framework provides the ability to interact with Analytics. We can open a report on the desktop from an extension and also set filters for the report.

    In the code below, we’re using the analyticsContext.createReport() function, which takes the report id as the argument and we’re also setting the filter on the report. In this case, the phone number will be set as the filter value.

    function openReport() {
     ORACLE_SERVICE_CLOUD.extension_loader.load('contactSearchExtension').then(
     function(sdk){
      sdk.registerAnalyticsExtension(function(analyticsContext){
      // contact report created with phone filter added to the list
      analyticsContext.createReport().then(
       function(extensionReport){
        var filterList = extensionReport.getReportFilters().getFilterList();
        filterList[0].setValue(phoneNum);
        extensionReport.executeReport();
       });
      });
     });
    }
    });//Close document.ready()
    

    Events

    Last, we’re going to use the findAndFocus and closeEditor events. When we handle the workspace screenpop, the callback function is storing the phone number in an array. We’re using this array to construct a UI list element, with the phone number as the label and a Close button.

    We’re adding a click action on the phone number label. This allows for when there are multiple workspaces opened after a phone number lookup - using the findAndFocus function will bring focus onto that workspace. Similarly, the Close button will use the closeEditor event to close the workspace.

     

    function addWorkspaceButton(workspaceRecord,phoneNum){
      workspaceRecordMap[workspaceRecord.getContextId()] = workspaceRecord;
      workspaceRecord.addRecordClosingListener(function(workspaceRecordEventParam){
       $("#"+workspaceRecord.getContextId()).remove();
      });
      $("#calls").append("<li class='tab' id='"+workspaceRecord.getContextId()+"' 
    onclick='findAndFocus(\""+workspaceRecord.getContextId()+"\")'><div 
    class='focus'>"+phoneNum+"</div><sup><i class='fa fa-window-close' 
    onclick='stopEvent();return 
    closeCurrentWorkspace(\""+workspaceRecord.getContextId()+"\")'></i></sup></li>");
    }
    function closeCurrentWorkspace(contextId){
    workspaceRecordMap[contextId].closeEditor().then(
      function(){
       $("#"+contextId).remove();
        workspaceRecordMap[contextId] = null;
       });
       return false;
    }
    function findAndFocus(contextId){       
     workspaceRecordMap[contextId].findAndFocus(workspaceRecordMap[contextId].getWor
    kspaceRecordType(),workspaceRecordMap[contextId].getWorkspaceRecordId());
    }
    function stopEvent(e){
      var evt = e ? e:window.event;
      if (evt.stopPropagation)    evt.stopPropagation();
      if (evt.cancelBubble!=null) evt.cancelBubble = true;
    }
            
    

    Figure 6 - toolbar.html: findAndFocus & closeEditor Events

    You should now be able to create the contact lookup extension by following the directory structure and copying the code from this article into the appropriate files.

    In summary, you can see how easy it to build extensions for the Bui Desktop by using the various events, functions, and properties in the Extensibility Framework. There are several more features and functions available in the Extensibility Framework that you can read all about in the documentation here

    Please be sure to check out the other articles regarding the Extensibility Framework. We want to be involved in helping to customize the product to meet your business needs. Please let us know what you plan to build to keep the conversation going. Thanks!

    Comment

     

    • Cosimo Galasso

      Thanks Shiv

      is it possible to have the complete downloadable .ZIP file?

      Cosimo

       

    • Shiv Tenneti

      Hi Cosimo,

      Here's the downloadable code. Please use this for learning purposes only and let me know if you have any questions.

      Shiv

    • Ravindra Nath Kashyap

      Hi Shiv,

      I am very new in this technology and want to learn how it works, can you please help me how I can use this extension. I know how to add this is the extension but not getting how to make it useful means how to execute?

    • Shiv Tenneti

      Hi Ravindra,

      This extension will run on the new Agent Browser UI Desktop. Once you have made the required changes to the code as indicated in the article you can upload the files using the AddIn Manager and view the extension on the Agent Desktop. I would also recommend that you spend some time reading about the Agent Browser UI and take a look at the Extensibility Framework documentation to learn more about how to build extensions.

      Shiv.

    • David Callaghan

      Maybe im not looking well enough :), but I can't find many examples (like this one) for creating Browser UI components 

      Does anyone know of any other examples I can work through so I can better understand the BUI?

    • Pree S

      Hi Shiv- Hope all is well. Do you have some examples of Navigation and ContentPane extensions? We have a PHP page with some fields to allow agent to search a Contact in Legacy Application. We are planning to open this PHP page inside ContentPane extension which can be opened from a Navigation extension(like .net Addins).

      BR, Pree

    • Naveen Thulasidharan

      For creating and adding navigation item, please refer to :

      http://documentation.custhelp.com/euf/assets/devdocs/unversioned/BUI_Extensibility/topicrefs/INavigationItem.html?hl=navigation

      Code Sample for the same :

      ORACLE_SERVICE_CLOUD.extension_loader.load("CUSTOM_APP_ID" , "1")

      .then(function(extensionProvider)

          {

          extensionProvider.registerUserInterfaceExtension(function(IUserInterfaceContext)

              {

              IUserInterfaceContext.getNavigationSetContext().then(function(INavigationSetContext)

                  {

                  INavigationSetContext.getNavigationItem('someId').then(function(INavigationItem)

                      {

                      var childNavigationItem1 = INavigationItem.createChildItem();

                      childNavigationItem1.setLabel('Child Item');

                      INavigationItem.addChildItem(childNavigationItem1);

                      INavigationItem.render();

                      });

                  });

              });

          }

      );

      Similarly, for creating content pane please refer to :

      ORACLE_SERVICE_CLOUD.extension_loader.load("CUSTOM_APP_ID" , "1")

      .then(function(extensionProvider)

          {

          extensionProvider.registerUserInterfaceExtension(function(IUserInterfaceContext)

              {

              IUserInterfaceContext.getContentPaneContext().then(function(IContentPaneContext)

                  {

                  IContentPaneContext.createContentPane().then(function(IContentPane)

                      {

                      IContentPane.setContentUrl('http://www.phpPageUrl.com');

                      });

                  });

              });

          }

      );

       

    • Rajan Davis

      Not sure if it will help anyone, but I refactored the example with a few stylistic changes.

      The main differences are:

      1. Using async/await instead of nesting functions with Promises.

      2. No jquery - instead, the state and view updates are managed by Vue.js. This makes things like tracking the which phone numbers have incidents/contact workspaces open a bit easier to manage.

      3. REST calls are managed by a library that I wrote in Node. The major benefit is the code has a bit more structure and allows for less calls to get a session token (which adds up if you have hundreds of agents).

      4. It should work out of the box, all you need to do is load files from this folder.

      5. Javascript is minified into one script

      At a very high level, the development code is stylistically different in that I am using Vue.js to create a component with certain properties and functions that determine the state of the component

      In any case, I hope this helps someone along with their BUI exploration.

      Kind Regards,

      Raj

    • Craig Hogan

      I'm having a heck of a time creating a workspace plugin, and the last Item in the bullet list specifies workspace add-in. But the example code is an extension bar add-in. Can anyone provide a hello world example of a workspace add-in. that has a simple GUI, (say a button and a label) which can be placed on workspace (like the incident workspace)