OpenSubTab in the Console App using lightning:workspaceAPI

The workspace API allows you to programmatically control the workspace tabs and subtabs in the lightning console app. If you have experience working with the Salesforce console app you may have noticed records opened as workspace tabs and their related records opened as sub-tabs (child tabs).

In this post, I am going to solve one use case using the workspace API in the lighting component.

Use Case: We have a standard “Activities” tab/page on the parent (Account, Opportunity, etc) record page and users can create the activities (tasks, events) from there. After creation, the user remains on the parent record page and they can see the created activity records in the list.  
The requirement is to navigate to the created task/event on their detail page and open this record as sub-tabs of their parent record in the Salesforce Console app.

If you analyze the requirement, you will see two challenges here: 

  1. We are going to use the standard page to create the activities, so how can we navigate to the detail page of Task/Event after record creation from the parent record page? 
  2. The activity details page should open in the sub-tab in the Salesforce Console application.

For the first [1] challenge we are going to use 

  • Salesforce Platform event 
  • Apex Trigger on Activity (Task/Event)
  • Use empAPI in the Lightning Component

And for the second [2] we will use workspace API in the lighting component to control the tab behavior in the Salesforce Console app.

Platform Event: We can use platform events to connect business processes in Salesforce and external apps through the exchange of real-time event data. Publishers publish event messages that subscribers receive in real-time.

Create a Platform Event

Create some fields highlighted in the screenshot below on this platform event.

Create an Apex trigger or add the code to the existing apex trigger.

trigger TaskTrigger on Task (after insert) {
//List of platform event
List<Activity_Event__e> activityEventList = new List<Activity_Event__e>();
if(Trigger.isAfter && Trigger.isInsert){
system.debug('URL => ' + String.valueOf(URL.getCurrentRequestUrl()).toLowerCase());
Boolean recordCreateFromAPI = String.valueOf(URL.getCurrentRequestUrl()).toLowerCase().contains('services/soap');
system.debug('recordCreateFromAPI => ' + recordCreateFromAPI);
if(!recordCreateFromAPI){ //check if record is creating from API or not (below code will not execute if record inserted from data loader [API])
for(Task tsk: Trigger.new){
//Instantiating platform event
Activity_Event__e ae = new Activity_Event__e();
ae.Activity_Record_ID__c = tsk.Id; //put task record id
ae.What_ID__c = tsk.WhatId; // put parent record id
ae.Who_ID__c = tsk.whoId; // put who id (contact,Lead ID)
activityEventList.add(ae); //add event in the list
}
}
}
try{
if(activityEventList.size()>0)
EventBus.publish(activityEventList); //publish the event
}catch(Exception e){
system.debug('Exception has occurred! ' + e.getMessage());
}
}
view raw TaskTrigger.tgr hosted with ❤ by GitHub

Create a lighting component

To work with the workspace API, you have to create an instance of the “lightning:workspaceAPI” component and assign the aura id attribute to get this in the javascript controller. 

ActivityEventListener.cmp

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<lightning:empApi aura:id="empApi" />
<lightning:workspaceAPI aura:id="workspace"/>
</aura:component>

ActivityEventListenerController.js

({
doInit : function(component, event, helper) {
//get the platform event channel
var channel = '/event/Activity_Event__e';
const replayId = -1;
//get the empAPI component
const empApi = component.find("empApi");
//A callback function that's invoked for every event received
const callback = function (message) {
var result = message.data.payload;
console.log('msg = '+JSON.stringify(result));
console.log('Activity_Record_ID__c : ' +result.Activity_Record_ID__c );
console.log('What_ID__c : ' +result.What_ID__c );
console.log('Who_ID__c : ' +result.Who_ID__c );
if(result.What_ID__c == component.get("v.recordId") || result.Who_ID__c == cmp.get("v.recordId"))
{
helper.navigateToActivityRecord(component,event,helper,result.Activity_Record_ID__c);
}
};
// Subscribe to the channel and save the returned subscription object.
empApi.subscribe(channel, replayId, callback).then(function(newSubscription) {
//console.log("Subscribed to channel" + channel);
});
const errorHandler = function (message) {
console.error("Received error ", JSON.stringify(message));
};
//A callback function that's called when an error response is received from the server for the handshake, connect, subscribe, and unsubscribe meta channels.
empApi.onError(errorHandler);
}
})

ActivityEventListenerHelper.js

({
navigateToActivityRecord : function(component,event,helper,ActivityRecord_ID) {
//Workspace API used to open the activity in sub tabs in console app
var workspaceAPI = component.find("workspace"); //get the workspace component
workspaceAPI.isConsoleNavigation().then(function(response) { //check if it is console app
workspaceAPI.getEnclosingTabId().then(function(tabId) { //get the parent tab id
return workspaceAPI.openSubtab({ //open sub tab
recordId: ActivityRecord_ID, //record id of activity
focus: true, //make the tab in focus
parentTabId : tabId //parent tab
}).then(function(response) {
workspaceAPI.getTabInfo({
tabId: response
}).then(function(tabInfo) {
console.log("The url for this tab is: " + tabInfo.url);
});
})
.catch(function(error) { //catch errors
console.log('Error here >> ' + error);
//if(error == 'Error: API `openTab` is not currently supported in this application.'){
var urlEvent = $A.get("e.force:navigateToSObject");
urlEvent.setParams({
"recordId": ActivityRecord_ID,
"isredirect": "true"
});
urlEvent.fire();
});
})
.catch(function(error) {
console.log(error);
});
})
.catch(function(error) {
console.log('catch : '+ JSON.stringify(error));
});
}
})

Now put the “ActivityEventListener” component on the parent record page (Account page, Opportunity page, etc.) and create the activity (Task) from there and check the result.

Let’s decode the workspace API code line by line.

The below line determines whether the app it’s used within uses console navigation.

Get the parent tab id using 

Open the sub-tab using the record id and parent tab id.

Get the tab information, you can check the URL of the tab in the console.

You can see in the code I have used empAPI in the lightning component to listen to the platform event. So let’s learn a little bit about empAPI in lightning.

What is the Emp API ?

This lightning empApi module provides access to methods to subscribe to the streaming channels or listen to the platform events. All streaming channels are supported, including channels for platform events, PushTopic events, generic events, and Change Data Capture events.

In the java-script file of your lightning web component, you can import the methods of the lightning/empApi module using the below syntax.

Put the platform name after “/event/” (/event/Activity_Event__e)

replayId Indicates what point in the stream to replay events from. Specify -1 to get new events from the tip of the stream, -2 to replay from the last saved event, or a specific event replay ID to get all saved and new events after that ID.

Get the empApi component that you defined in the component code.

The callback function returns the data from the platform event, you can get the data/message in this method and can execute your logic based on that. I am using What_ID__c and Who_ID__c received from platform events to compare with the parent record id (e.g Account/Opportunity,Contact,etc).

Subscribe to a given channel and returns a promise that holds a subscription object, which you use to unsubscribe later.

Advertisements

The workspace API component supports the following methods:

Method NameDescription/Uses
closeTab({tabId})Use this method if you want to close the particular tab (workspace tab or subtab).
Parameter:
tabId (string): ID of the workspace tab or subtab to close.
Returns a Promise. Success resolves to true. The Promise will be rejected on error.
focusTab({tabId})Use this method to make the workspace tab/subtab focused.
Parameter:
tabId (string): The ID of the workspace tab or subtab on which to focus.
Returns a Promise. Success resolves to true. The Promise will be rejected on error.
getAllTabInfo()This method returns information about all tabs.
Returns a Promise. Success resolves to an array of tabInfo objects. The Promise will be rejected on error.
getFocusedTabInfo()This method returns information about focus tab/subtabs.
Returns a Promise. Success resolves to a tabInfo object. The Promise will be rejected on error.
getTabInfo({tabId})Return information about specified tabs/subtabs.
Parameter:
tabId (string): ID of the tab for which to retrieve the information.
Returns a Promise. Success resolves to a tabInfo object. The Promise will be rejected on error.
getTabURL({tabId})Return the tab/subtab URL
Parameter:
tabId (string): ID of the tab for which to retrieve the URL.
Returns a Promise. Success resolves to the tab URL. The Promise will be rejected on error.
isSubtab({tabId})This method checks whether the specified tab is subtab or not.
Parameter:
tabId (string): ID of the tab.
Returns a Promise. Success resolves to true if the tab is a subtab, false otherwise. The Promise will be rejected on error.
isConsoleNavigation()Checks whether the navigation style of the current app is Console (instead of Standard).
Returns a Promise. Success resolves to true if console navigation is present, false otherwise. The Promise will be rejected on error.
getEnclosingTabId()This method returns the enclosing tab id.
Returns a Promise. Success resolves to the enclosing tabId or false if not within a tab. The Promise will be rejected on error.
openSubtab({parentTabId, pageReference, recordId, url, focus})Method opens a subtab within a workspace tab. The new subtab displays the content of the specified pageReference, recordId, or URL, which can be relative or absolute. If the specified subtab is already open and focus is set to true, it is focused.
Parameter:
parentTabId (string): ID of the workspace tab within which the new subtab should open.
pageReference (object): Optional. A PageReference representing the content of the new subtab.
recordId (string): Optional. A record ID representing the content of the new subtab.
url (string): Optional. The URL representing the content of the new subtab. URLs can be either relative or absolute.
focus (boolean): Optional. Specifies whether the new subtab has focus.
openTab({pageReference, recordId, url, focus, overrideNavRules})Opens a new workspace tab that displays the content of the specified pageReference, recordId, or URL, which can be relative or absolute. If the specified tab is already open and focus is set to true, it is focused.
Parameter:
pageReference (object): Optional. A PageReference representing the content of the new tab.
recordId (string): Optional. A record ID representing the content of the new tab.
url (string): Optional. The URL representing the content of the new tab. URLs can be either relative or absolute.
focus (boolean): Optional. Specifies whether the new tab has focus.
overrideNavRules (boolean): Optional. Specifies whether to override nav rules when opening the new tab.
setTabIcon({tabId, icon, iconAlt})Sets the icon and alternative text of the specified tab.Parameter:
tabId (string): The ID of the tab for which to set the icon.
icon (string): An SLDS icon key. See a full list of icon keys on the SLDS reference site.
iconAlt (string): Optional. Alternative text for the icon.
setTabLabel({tabId, label})Sets the label of the specified tab.
Parameter:
tabId (string): The ID of the tab for which to set the label.
label (string): The label of the workspace tab or subtab.
setTabHighlighted({tabId, highlighted, options})This method will highlight the specified tab.
Parameter:
tabId (string): The ID of the tab for which to highlight.
highlighted (boolean): Specifies whether the new tab should be highlighted.
options (object): Optional. Additional options that modify the appearance of the highlighted tab.
Returns a Promise. Success resolves to a tabInfo object of the modified tab. The Promise will be rejected on error.
disableTabClose({tabId, disabled})Disables or enables close capabilities of the specified tab.
Parameter:
tabId (string): The ID of the tab for which to disable or enable close capabilities.
disabled (boolean): Specifies whether the tab should have close capabilities disabled or enabled.
Returns a Promise. Success resolves to a tabInfo object of the modified tab. The Promise will be rejected on error.
refreshTab({tabId,includeAllSubtabs})Refreshes a workspace tab or a subtab specified by tabId. Keep in mind that the first subtab has the same tabId as the workspace tab.
Parameter:
tabId (string): ID of the workspace tab or subtab to refresh.
includeAllSubtabs (boolean): Optional. If the tabId corresponds to a workspace tab, all sub tabs within that workspace are refreshed. The default is true. Keep in mind that the first subtab has the same tabId as the workspace tab.
Returns a Promise. Success resolves to true. The Promise will be rejected on error.
generateConsoleURL({pageReferences})Generates a URL to a workspace or workspace and subtabs.
Parameter:
pageReferences (array): An array of PageReferences to a workspace or workspace and subtabs to generate a URL for.Generates a URL to a workspace or workspace and subtabs. 
Returns a Promise. Success resolves to the active tab id. The Promise will be rejected on error.
openConsoleURL({url, focus, labels})Opens a URL generated via the generateConsoleURL API.
url (string): The URL representing the content of the new workspace or workspace and subtabs.
focus (boolean): Optional. Specifies whether the new active tab has focus.labels (array): Optional. An array of labels to be applied to the workspace or workspace and subtabs.Opens a URL generated via the generateConsoleURL API. 
Returns a Promise. Success resolves to true. The Promise will be rejected on error.

Summary

We have learned how to use the Workspace API in a Lightning Component to navigate to the task or event detail page after creating an activity and open it as a sub-tab of the parent record. For more information, check out the resources below.

Resources

Join 768 other followers

Advertisements
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s