Querying Records with Visualforce Remote Objects in Lightning Components

Do you want to start building Lightning Components or would like to convert some custom visualforce pages to a Lightning app? Do your current visualforce pages use remote objects to query or load records? How could you accomplish that using lightning? Create Apex server-side controllers (but you didn’t need apex controllers for you visualforce page, everything was handled client-side). Well, you might think about using lightning data service since that allows you to load and edit records with client-side code. Lightning data service, however, allows you to load or update a single record at a time along with only being supported in LEX and the salesforce mobile app. I wonder if there was a way to use visualforce remote objects with lightning components so I can eliminate the need for server-side apex controllers to handle data access. In today’s post…we find out.

After a bit of searching I found this Salesforce developer blog article on communication between a visualforce page embedded within a lightning component. I used this as the basis of building a way for a component to use remote objects to query and load records.

The Scenario

Let’s say we want to build a lightning app where someone can enter an amount, click on a search button and the component would return a list of opportunities where the amount field is greater or equal to that value. We also want to accomplish this without using an Apex controller class to handle the query of data.

The Lightning Component

The below markup is where we start this experiment. We have simple component that has an input field and a lightning button.

The button should start a process of handing off the amount entered in the input field and passing it to a visualforce page that should be embedded in the component as an iframe. The source of the iframe is derived from the value of the vfHost attribute and the path to the visualforce page.

As you may have noticed the lightning button executes a controller function named as getOpps when clicked.

getOpps : function(component, event, helper) {
   var oppAmt = component.get("v.oppAmt");
   console.log(oppAmt);
   var vfOrigin = "https://" + component.get("v.vfHost");
   var vfWindow = component.find("vfFrame").getElement().contentWindow;
   vfWindow.postMessage(oppAmt, vfOrigin);
}

The getOpps function will get the inputted amount value and the host url of the visualforce page. We get the contentWindow value of the iframe component by aura id and with that call the postMessage function passing in the amount value as the message and the vf origin. This allows us to pass messages between the window where the lightning component is loaded and the window where the visualforce page is loaded. Now that we see how we are going to send the amount value to the VF page, let’s review how we are going to accept the data from the page.

The Visualforce Page

The visualforce page will have a remote object model defined for the opportunity object. We will be using the Id, Name, Amount, and Stagename standard fields.

<apex:page >

<apex:remoteObjects >
<apex:remoteObjectModel fields=”Id,Name,Amount,StageName” jsShorthand=”Oppty” name=”Opportunity”>
</apex:remoteObjectModel>
</apex:remoteObjects>

<script type=”text/javascript”>
console.log(‘page load’);
var lexOrigin = “https://XXXXXXXXXXX.lightning.force.com&#8221;;
window.addEventListener(“message”, function(event) {
console.log(‘event fire’);
if (event.origin !== lexOrigin) {
// Not the expected origin: reject message!
console.log(‘error’)
return;
}
// Handle message
getOpps(event.data);
}, false);

……………………………………………………………………..

The first half of the page we have javascript where we add a listener for the message event with a callback function where we handle the message data received. We also check if the origin of the message matches where we expect it to be sent from to confirm that the request is coming from where it should be. The lexOrigin should be the of the lightning url (the url when you load a lightning app for example). If origin of the message matches what we expect we will process the request by call the getOpps function.

……………………………………………………………………………………

function getOpps(amt){
amt = parseInt(amt);
console.log(‘getting opps with amount greater than ‘ + amt);
var opp = new SObjectModel.Oppty();

// Empty callback functions for simplicity
opp.retrieve(
{
where: {
Amount: {gte: amt }
}
}, function(error, records, event) {
console.log(records);
sendResultsToLtngCmp(records);
}); // query object
}

function sendResultsToLtngCmp(records){
var lexOrigin = “https://XXXXXXX.lightning.force.com&#8221;;
var msg = ”;
for(var i = 0; i < records.length; i++){
msg += records[i].get(‘Id’) + ‘,’ + records[i].get(‘Name’) + ‘,’ + records[i].get(‘StageName’) + ‘,’ + records[i].get(‘Amount’) + ‘||’;
}

parent.postMessage(msg, lexOrigin);
}

</apex:page>

The second half of the page consists of the functions getOpps and sendResultsToLtngCmp. The first function getOpps takes an opportunity amount value as an argument then instantiates a opportunity remote object model to the opp variable. We query the opportunity object using the remote object retrieve function passing the amount in the where condition. If actual results are returned in the callback function we will pass the records array when calling the second function sendResultsToLtngCmp.

Once we have valid results returned we will use sendResultsToLtngCmp to prepare the records to be sent as a message back to the lightning component. We iterate through the records array and stores each record in the following format:

 

colDetailsFormat.png

Essentially each records field values will be separated by a comma and each record will be separated by the pipe character. Once the data is prepared we will send the data back to the lightning component using postMessage again, pass the data as the msg and origin of the the lighting app’s url.

Now that we understand how the visualforce page accepts and process the request along with how it send the response back, let’s go over how the lightning component will accept and process the response.

Back to Lightning

Two additional items will be added to the component to start handling the data response and displaying it onto the component body. The item would be the v.body attribute where we will push the custom table component I built in my previous post where I cover creating components for flow screens.

The second item is a handler that will execute a new javascript controller function named doInit when the component is initially loaded. This new function will handle post message event from the visualforce page.

The Controller…Again

Much like the code in the visualforce page, we will add a listener to handle a message event with a callback function that checks if the origin of where the message was sent from matches what we expect it to be. If so, we call a loadData helper function passing the component and the response data from the VF page as arguments.

doInit : function(component, event, helper) {
   var vfOrigin = "https://" + component.get("v.vfHost");
   window.addEventListener("message", function(event) {
   console.log('event fired from vf page');
   console.log(event.origin);
   console.log(vfOrigin);
   if (event.origin !== vfOrigin) {
      console.log('error from lc');
      // Not the expected origin: Reject the message!
      return;
   }
   // Handle the message
   //console.log(event.data);
   helper.loadData(component, event.data);
   }, false);
},

The Helper

The first half of the loadData function will be splitting the data string by the pipe character so that we have an array of comma separated string values. We will also create a string of column detail values used for the custom FlowDataTableCmp component I built in my previous post. The component essentially load a standard lightning:datatable component, but already handles the preparation work so I just need to provide the data array and col details string in order to render the table onto the screen.

To handle the search functionality using multiple times I am clearing the contents of v.body each time loadData is executed so that I am clearing any existing table component and loading a new one each time we get results back from the visualforce page.

{
loadData : function(cmp, dataStr) {
   var dataStrArr = dataStr.split('|');
   var colDetails =
      'Id,id,text;Name,name,text;Stage,stagename,text;Amount,amount,text';

   console.log(dataStrArr);

   var body = cmp.get('v.body');
   body.splice(0, 1);

   cmp.set("v.body", body);$A.createComponent(
    <span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>"c:FlowDataTableCmp",
      {
         "aura:id": "oppTable",
         "dataArr": dataStrArr,
         "columnsStr" : colDetails
      },
      function(oppTableCmp, status, errorMessage){
        //Add the new button to the body array
       if (status === "SUCCESS") {
         console.log('success');
         var body = cmp.get("v.body");
         body.push(oppTableCmp);
         cmp.set("v.body", body);
       }
     }
    );
}

The second half of the function we will use the createComponent function to push a new instance of the FlowDataTableCmp providing an aura id, the dataStrArr value, and colDetails as attribute values. If the creation of the component was successful we will take the newly created component and push it into the v.body. This action will load a lightning:datatable component onto the component body with the column headers and row data we provided.

Results

Here is what the final product looks like. Check out the search page in action here.

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 )

Google+ photo

You are commenting using your Google+ 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

This site uses Akismet to reduce spam. Learn how your comment data is processed.