Sunday, January 20, 2019

Create a Lightning Component that Utilizes the Native Salesforce Global Search

I was recently working on a project where I needed to create Lightning Component that utilized the native global search to return items from several objects. I've done this in the past in a Visual Force page by redirecting the page to .../_ui/search/ui/UnifiedSearchResults?searchType=2&str=ACME+Corporation.

That works fine in Classic, but when I looked at the URL in Lightning I noticed that it's quite different.

../one/one.app#eyJjb21wb25lbnREZWYiOiJmb3JjZVNlYXJjaDpzZWFyY2hQYWdlI...

The first thing you may notice is that our search term is nowhere to be found. That's because it has been converted to a base64 encoded string value. In order to see how to pass our search terms, we need to decode this value and see what it consists of. You can do this online at sites like https://www.base64decode.org/.  Here's the decoded value.

{"componentDef":"forceSearch:searchPage","attributes":{"term":"ACME Corporation","scopeMap":{"resultsCmp":"forceSearch:resultsTopResults","label":"Top Results","type":"TOP_RESULTS","cacheable":"Y","id":"TOP_RESULTS","labelPlural":"Top Results"},"context":{"disableSpellCorrection":false,"disableIntentQuery":false,"permsAndPrefs":{"OrgPermissions.UnionAppNavSmartScope":false,"SearchUi.feedbackComponentEnabled":false,"SearchUi.searchUIPilotFeatureEnabled":false,"SearchExperience.LeftNavEnhancementEnabled":true,"Search.crossObjectsAutoSuggestEnabled":true,"Search.maskSearchInfoInLogs":false,"SearchUi.orgHasAccessToSearchTermHistory":false,"SearchResultsLVM.lvmEnabledForSearchResultsOn":true,"SearchUi.searchUIInteractionLoggingEnabled":false,"MySearch.userCanHaveMySearchBestResult":false,"SearchResultsLVM.lvmEnabledForTopResults":false,"MySearch.userCanHaveMySearch":false}},"groupId":"DEFAULT"},"state":{}}

Did you notice the bold 'term' attribute? That's where we want to put our search terms. Now that we know what the encoded string consists of and where to put our search terms all that's left to do is write some code. Let's take a look at how we can take this decoded JSON and use it in a Lightning Component.

In the first line of the code, we will declare our search term variable and assign the value 'ACME Corporation'.

var searchterm='ACME Corporation';

Next, we need to take the decoded JSON and add our search term.

var stringToEncode = '{"componentDef":"forceSearch:search","attributes":{"term":"'+searchterm+'"...

Now that we have the JSON ready, we need to Base64 encode it for use in the URL. For this example I created a variable that references the base64min.js library. In a real-world project, you may want to create a static resource for this.

var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234...
var searchcriteria = Base64.encode(stringToEncode);


And last but not least we redirect to the newly created search URL.

window.location.href='/one/one.app?source=alohaHeader#'+searchcriteria;

That's pretty much it! Here's the complete code snippet.

Friday, December 28, 2018

Passing a record Id from a Lightning Component to an APEX Controller

One of the most common things you will come across when working with Lightning Components is the need to pass a record Id to an APEX controller. When we talk about Lightning Components we are referring to an AuraBundleDefinition. Within that Aura bundle there are a number of different items, but for this example, we will only be using the component and the controller. If you want to learn more about the AuraBundleDefinition you can click here. Enough small talk, let's jump right in and take a look at the code.

LightningRecordIdExample.cmp

The first line of the component is where we specify the controller class and implement our interfaces.

<aura:component controller="LightningRecordIdExampleController" implements="flexipage:availableForAllPageTypes,force:hasRecordId"></aura:component>

And as you've probably figured out from the snippet above, the interface used to get the record Id is ...force:hasRecordId". 


The force:hasRecordId interface does a couple of thing to the component that implements it.

  • It adds an attribute named recordId to your component. This attribute is of type String, and its value is an 18-character Salesforce record ID, for example: 001xx000003DGSWAA4. If you added it yourself, the attribute definition would look like the following markup:
<aura:attribute name="recordId" type="String"></aura:attribute>

Note If your component implements force:hasRecordId, you don’t need to add a recordId attribute to the component yourself. If you do add it, don’t change the access level or type of the attribute or the component will cause a runtime error.
  • When your component is invoked in a record context in Lightning Experience or the Salesforce app, the recordId is set to the ID of the record being viewed.

Now that we have our record Id, let's take a look at the Lightning controller class.

LightningRecordIdExample.js

In our controller class, we have a single function call doInit the gets called upon page initialization.

doInit : function(component, event, helper){

This function creates a one-time instance of the getAccount function in the server-side controller.

var action = component.get("c.getAccount");

Next, we set the accountId parameter by getting the recordId attribute value from the component.

action.setParams({
     "accountId": component.get("v.recordId")
});


Now that we have defined the action and added the record Id as a parameter, all that's left to do is to queue the action and handle the callback response.

action.setCallback(this, function(response) {
   var state = response.getState();
   

   if (component.isValid() && state === "SUCCESS") {
      component.set("v.account", response.getReturnValue());
      ...


That's pretty much it! You can check out the server-side controller class below.

Monday, October 8, 2018

Winter '19 Platform Events and the new Lightning Emp API Component

With the Winter '19 release there are a number of lightning updates to help make working with lightning components a little bit easier. For this post, I thought that I'd focus on the new Lightning Emp Api component.

lightning:empApi

Embed the lightning:empApi component in your custom Lightning component to subscribe to a streaming event channel and receive event notifications. You can subscribe to any type of event channel on the Lightning Platform, including channels for platform events, PushTopic and generic events, and Change Data Capture (Developer Preview) events. The lightning:empApicomponent uses a shared CometD-based Streaming API connection, enabling you to run multiple streaming apps in the browser.

One of the nicest things about this new component, is that it greatly reduces the amount of code that I need to copy and paste each time I want to consume a Platform Event. In the past, we had to use the cometd library which required a fair bit of work to setup and initialize. This included downloading the library, creating a static resource, initializing the resource, configuring the endpoint, etc... I'm getting tired just thinking about it. With the new lightning:empApi component, it's just a matter of adding the lightning:empApi component to your custom lightning component, creating a error handler to catch any issues, and subscribing to the event. Pretty easy huh? Now that that's all out of the way, let's take a look at some code.

Let's start off by creating a new Platform Event called "Demo_EmpApi_Event__e"...
...and add a new custom field called "Message__c".

Next, we need to add the reference to Emp API to our lightning component:
  <lightning:empapi aura:id="empApi"></lightning:empapi>
The bulk of the logic is handled by the init method in the controller class. The first thing that we need to do is to get a reference the Emp API component:

  var empApi = cmp.find("empApi");
Once we have a reference to the Emp API component we can subscribe to the event:
  var replayId=-1; //use -1 for new events
  empApi.subscribe(channel, replayId, callback).then(function(value) {
      console.log("Subscribed to channel " + channel);
      sub = value;
      cmp.set("v.sub", sub);
  });
When we receive a message we need to handle is somehow. In this case I've created a callback function to popup an alert.
  var callback = function (message) {
      alert('Event Received!');        
  }.bind(this);

The final piece of this is to publish the event. In order to do so, I've created an Apex class that instantiates the Demo_EmpApi_Event__e and then uses the EventBus Class to publish it.
  Demo_EmpApi_Event__e event = new Demo_EmpApi_Event__e(Message__c=message);      
  Database.SaveResult result = EventBus.publish(event);

That's pretty much it! Now when we publish an event we get an alert.

I've posted the complete code examples below. Happy coding!

EmpApi_example.cmp
EmpApi_exampleController.js 
EmpApi_exampleController.cls

Saturday, April 7, 2018

Lightning Knowledge Promoted Terms in Spring'18

I've been working on migrating our current Service Console from Classic to Lightning and although Salesforce has been adding new features to Lightning, I continue to run into parity issues. As of Spring '18 there are still key features that are not yet available in Lightning Knowledge. One of these key features is the use of Promoted Search Terms. One of the ways that we help our agents find knowledge articles is to use Promoted Search Terms to promote articles in the search results. This has worked well in Classic, but thus far this feature is not available in Lightning. To bridge the gap, I've created a Lightning App that let's the knowledge admins add Promoted Terms to existing articles. The APEX code to do just that is surprisingly simple.


Let's take a closer look at the code. After selecting the article by Id, we create a new instance of a SearchPromotionRule where we provide the search term (Query='Trailblazer') and the article Id to promote (PromotedEntityId=articles[0].Id);). The last line simply inserts the record. That's pretty much all there is to it.

A couple of things to note:
  • The user must have the "Manage Promoted Search Terms" permission.
  • The search term can be a maximum of 100 characters.
  • A search term can be associated with multiple articles.


Monday, April 10, 2017

How to avoid the MIXED_DML_OPERATION error

I recently came across the MIXED_DML_OPERATION error when writing a trigger to automate user access requests. The error was thrown when an update to a custom object triggered a process that updates the  users PermissionSetAssignment. Well as it turns out, DML operations on certain setup sObjects can’t be mixed with DML on other sObjects in the same transaction. In other words, if you're using a Visualforce page with a custom controller, you can't mix sObject types with any of these special sObjects within a single request or action. However, you can perform DML operations on these different types of sObjects in subsequent requests. For example, you can create an account with a save button, and then create a user with a non-null role with a submit button.

You can’t use the following sObjects with other sObjects when performing DML operations in the same transaction. 
  • FieldPermissions
  • GroupYou can only insert and update a group in a transaction with other sObjects. Other DML operations aren’t allowed.
  • GroupMemberYou can insert and update a group member only in a transaction with other sObjects in Apex code saved using Salesforce API version 14.0 and earlier.
  • ObjectPermissions
  • PermissionSet
  • PermissionSetAssignment
  • QueueSObject
  • ObjectTerritory2AssignmentRule
  • ObjectTerritory2AssignmentRuleItem
  • RuleTerritory2Association
  • SetupEntityAccess
  • Territory2
  • Territory2Model
  • UserTerritory2Association
  • User
So how do we get around this? 
  1. Create a method that performs a DML operation on one type of sObject.
  2. Create a second method that uses the future annotation to manipulate a second sObject type.

Here's an example of how to perform mixed DML operations by using a future method to perform a DML operation on the User object.

Create a Lightning Component that Utilizes the Native Salesforce Global Search

I was recently working on a project where I needed to create Lightning Component that utilized the native global search to return items from...