Using Lightning Design System with Visual Workflow

***Note: Originally posted on Adviceforce.com‘s blog

Visual workflows can be a useful tool to build custom interfaces and back-end logic. Flows can also be embedded into Visualforce pages as well. With the introduction of the Lightning Experience, however, the visuals of a flow screen may not fit within the new styling.zip_lookup_classic Unless Salesforce gives the flow UI components a needed zap of Lightning those who want to continue to use Visual workflow may have to settle with the conflicting styling. Here is an idea that is requesting that update.

We do have the Lightning Design System that provides us with Lightning Experience-ready components that can be used with Visualforce. My post on building interfaces with LDS in Visualforce provided an example of what can be done. Since invoking a flow via REST API is technically possible, we could continue use visual workflows to design the logic and use LDS components for the actual interface. We will walk through an example that implement this combination in this post.

Overview

zipcodeobj

For this exercise we will create a Visualforce page that will be able to lookup the city and state by search a postal code and vice versa. All search and error logic will be handled by an auto-launched flow and the Visualforce page will handle accepting inputs and displaying results along with error messages.

A custom Zip Code object will be used to store all postal code, city, and state/province data. I will not be describing the flow in detail in this post as the goal is to show how to use flows to handle back-end logic and invoke them within Visualforce.

flowdiagram

flowVars

 

 

 

The general details of the flows are the following:

  • The flow checks if the user wants to search by zip code or by city and state.
  • Checks if the provided input is valid, if not valid it will update an output text variable with  an error message.
  • Based on lookup type, fast lookup will be performed to query the Zip codes object with a zip code filter or city and state filter.
  • Any results are stored in a Sobject or SObject collection output variables.

 

 

 

 

 

The Visualforce Page

We will be using components from the Lightning Design System to build an interface within a Visualforce page. The interface will contain inputs for zip, city, state, search buttons, and a section for search results.

zip_lookup_le

The first half of the markup contains input fields and buttons. Clicking on the Search Zip button will execute the Javascript searchZip function that will initiate search against the the postal code field on the Zip Code object. The Search City and State button will execute the searchCityState function that will initiate a search on the City and State / Province fields of the object. The functions will perform a call to the flow via the REST API  and will pass the zip code or city /state based on which buttons are used.




<div class="slds">
<div class="slds-container--center slds-container--medium">
<div class="slds-card">
<div class="slds-card__header slds-grid">
<div class="slds-media slds-media--center slds-has-flexi-truncate">
<div class="slds-media__body">
<h2 class="slds-text-heading--small slds-truncate">Postal Code Search</h2>
<span>Postal Code</span>
<span>City</span>
<span>State / Province</span></div>
</div>
<div class="slds-no-flex">
<div class="slds-button-group">
Search Zip
Search City and State</div>
</div>
</div>
........................................................

The second half of the markup contains the table that will display search results based on the sObject or sObject collections output variables returned by the flow.

.......................................
<div class="slds-card__body">
<h4>Search Results</h4>
<table class="slds-table slds-table--bordered slds-max-medium-table--stacked-horizontal slds-no-row-hover">
<thead>
<tr>
<th class="slds-text-heading--label slds-size--1-of-3" scope="col">Postal Code</th>
<th class="slds-text-heading--label slds-size--1-of-3" scope="col">City</th>
<th class="slds-text-heading--label slds-size--1-of-3" scope="col">State / Province</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
</div>
</div>

 

The Code

Now that we have covered the markup, let’s walk through how each function will call the flow and how we are going to pass parameters into the flow along with retrieving output values.

The searchZip function retrieves the value entered into the Postal Code input field by the id of the input. The postal code value along with the lookup type is stored in a object made up of key / value pairs in the data variable.

  • lookupType is the text input variable defined in the flow. The string “zip” will indicate to the flow to perform a Fast Lookup against the Zip Code object records and filter by the Postal Code field.
  • zipCode is the text input variable defined in the flow. The zipCode flow variable is used to in the filter for the Fast Lookup.

The call to the flow is handled by the jQuery ajax function which performs HTTP requests. The ajax function will require settings in the form of key / value pairs that will be used to perform the call to the flow.

  • type:  “POST” will be used as we will be performing a post request to the flow url.
  • url: The endpoint for the flow we have created. The partial url is going to be “/services/data/<version number>/actions/flow/<API Name of the flow.>”.
  • contentType: We will be passing the value as “application/json”.
  • data:  The data we are going to pass into this setting will be the data variable we created based on the input variable key pairs. The pairs will be converted into a query string that will be appended to the url.
  • beforeSend: This setting is used to include the session Id of the logged in user for authorization to access the flow. Keep in mind that the running user needs to have the API Enabled setting via profile or permission set.
  • success: If the call to the flow was successful the function defined in the setting will execute. Results from the flow run will be returned in the r variable, where we will be getting values from the flow’s output variables.
  • fail: If there are any issues during the call out to the flow will result in the function defined to be executed. In our case we will just print a message to the console.
 

 var $j = jQuery.noConflict();

 function searchZip(){
 var zip = $j('#zipInput').val();

 var data = { "inputs" : [ {"lookupType" : 'zip', "zipCode" : zip } ] }; 

 $j.ajax({
 type: "POST",
 url: "/services/data/v33.0/actions/custom/flow/City_State_Lookup_By_Zip",
 contentType: 'application/json',
 data: JSON.stringify(data),
 beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}')},
 success: function(r){
 console.log('flow success');
 console.log(r);
 var zipRecs = r['0']['outputValues']['ZipObjRec'];
 var errMsg = r['0']['outputValues']['Message']; 

 if(errMsg !== 'None'){
 alert(errMsg);
 }
 else if(zipRecs === null){
 alert('No match found!');
 }
 else{

 $j('#tableBody').html('');
 var row = '

<tr>

<td>' + zipRecs.Postal_Code__c + '</td>


<td>' + zipRecs.City__c + '</td>


<td>' + zipRecs.State_Province__c + '&lt;/td</tr>

';
 $j('#tableBody').html(row) 

 }

 },
 fail: function(){
 console.log('Flow Fail');
 } 

 });

 }
...............................

dev_console_1 After a successful flow run, the function defined in the success setting will execute. In order to understand what the flow returns in the object, I usually print the contents onto the console. We can see that the root property of the object has the name of “0” (zero). The next level we need to drill into is outputValues. The two output variables we defined in the flow that we will need is the Message variable and the ZipObjRec variable. If the Message value returned is ‘None’ then the flow did not return any errors. The ZipObjRec variable will return a null value if no matching records were found. If that is the case then the user will be alerted. The remaining else condition will handle the outcome where a matching zip code if found. A new row string will be created with the returned value of postal code, city, and state as column values. The row will be added to the html table using the jquery html function. Below is an example of the search in action. search via zip

 

The searchCityState function is called when the Search City and State button is clicked. The function retrieves the city and state input field and stores the values and passes them into the flow call as performed in the searchZip function.

function searchCityState(){
 var city = $j('#cityInput').val();
 var state = $j('#stateInput').val();
 var data = { "inputs" : [ {"lookupType" : 'cityState', "city" : city, "stateProv" : state } ] }; 

 $j.ajax({
 type: "POST",
 url: "/services/data/v33.0/actions/custom/flow/City_State_Lookup_By_Zip",
 processData: false,
 contentType: 'application/json',
 data: JSON.stringify(data),
 beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}')},
 success: function(r){
 console.log('flow success');
 console.log(r); 

 var zipRecs = r['0']['outputValues']['ZipRecs'];
 var errMsg = r['0']['outputValues']['Message']; 

 if(errMsg !== 'None'){
 alert(errMsg);
 }
 else if(zipRecs === null){
 alert('No match found!');
 }
 else{
 var rows = '';
 for(var i = 0; i &lt; zipRecs.length; i++){
 rows += &#039;

<tr>

<td>' + zipRecs[i].Postal_Code__c + '</td>


<td>' + zipRecs[i].City__c + '</td>


<td>' + zipRecs[i].State_Province__c + '&lt;/td</tr>

';
 }

 $j('#tableBody').html('');
 $j('#tableBody').html(rows) 

 }

 },
 fail: function(){
 console.log('Flow Fail');
 } 

 });

 }

 


The function pulls the value returned from the ZipRecs sObject collection (array) variable and from the Message variable. If any errors messages are returned then the user is prompted with the error. A null value returned in the ZipRecs variable indicates that no records were found based on the Fast Lookup. Otherwise, the array is iterated through and the zip code, city , state fields are inserted into the HTML table.

search via city state

Thanks for reading and I hope you find this post helpful. Below is the entire markup and JavaScript.

Until next time!

 




<div class="slds">
<div class="slds-container--center slds-container--medium">
<div class="slds-card">
<div class="slds-card__header slds-grid">
<div class="slds-media slds-media--center slds-has-flexi-truncate">
<div class="slds-media__body">
<h2 class="slds-text-heading--small slds-truncate">Postal Code Search</h2>
<span>Postal Code</span>
<span>City</span>
<span>State / Province</span></div>
</div>
<div class="slds-no-flex">
<div class="slds-button-group">
Search Zip
Search City and State</div>
</div>
</div>
<div class="slds-card__body">
<h4>Search Results</h4>
<table class="slds-table slds-table--bordered slds-max-medium-table--stacked-horizontal slds-no-row-hover">
<thead>
<tr>
<th class="slds-text-heading--label slds-size--1-of-3" scope="col">Postal Code</th>
<th class="slds-text-heading--label slds-size--1-of-3" scope="col">City</th>
<th class="slds-text-heading--label slds-size--1-of-3" scope="col">State / Province</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
</div>
</div>


 var $j = jQuery.noConflict();

 function searchZip(){
 var zip = $j('#zipInput').val();

 var data = { "inputs" : [ {"lookupType" : 'zip', "zipCode" : zip } ] }; 

 $j.ajax({
 type: "POST",
 url: "/services/data/v33.0/actions/custom/flow/City_State_Lookup_By_Zip",
 contentType: 'application/json',
 data: JSON.stringify(data),
 beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}')},
 success: function(r){
 console.log('flow success');
 console.log(r);
 var zipRecs = r['0']['outputValues']['ZipObjRec'];
 var errMsg = r['0']['outputValues']['Message']; 

 if(errMsg !== 'None'){
 alert(errMsg);
 }
 else if(zipRecs === null){
 alert('No match found!');
 }
 else{

 $j('#tableBody').html('');
 var row = '

<tr>

<td>' + zipRecs.Postal_Code__c + '</td>


<td>' + zipRecs.City__c + '</td>


<td>' + zipRecs.State_Province__c + '&lt;/td</tr>

';
 $j('#tableBody').html(row) 

 }

 },
 fail: function(){
 console.log('Flow Fail');
 } 

 });

 }

 function searchCityState(){
 var city = $j('#cityInput').val();
 var state = $j('#stateInput').val();
 var data = { "inputs" : [ {"lookupType" : 'cityState', "city" : city, "stateProv" : state } ] }; 

 $j.ajax({
 type: "POST",
 url: "/services/data/v33.0/actions/custom/flow/City_State_Lookup_By_Zip",
 processData: false,
 contentType: 'application/json',
 data: JSON.stringify(data),
 beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}')},
 success: function(r){
 console.log('flow success');
 console.log(r); 

 var zipRecs = r['0']['outputValues']['ZipRecs'];
 var errMsg = r['0']['outputValues']['Message']; 

 if(errMsg !== 'None'){
 alert(errMsg);
 }
 else if(zipRecs === null){
 alert('No match found!');
 }
 else{
 var rows = '';
 for(var i = 0; i &lt; zipRecs.length; i++){
 rows += &#039;

<tr>

<td>' + zipRecs[i].Postal_Code__c + '</td>


<td>' + zipRecs[i].City__c + '</td>


<td>' + zipRecs[i].State_Province__c + '&lt;/td</tr>

';
 }

 $j('#tableBody').html('');
 $j('#tableBody').html(rows) 

 }

 },
 fail: function(){
 console.log('Flow Fail');
 } 

 });

 }

 



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.