Migration to Salesforce Lightning Experience: A Developer’s Guide
April 3, 2018

Migration to Salesforce Lightning Experience: A Developer’s Guide

Almost two and a half years have passed since Salesforce announced their new and sexy Lightning Experience (a.k.a. LEX) – I found this amazing, but lots of organizations are still using Salesforce Classic UI! It’s true, that some features are still unavailable for Lightning Experience, and some of the classic users still should postpone their migration to modern LEX, but it seems, that not only unreleased features is a problem with migration, but also some massive coding concepts have changed since then. This article will cover both – what’s still not released in LEX, and what changes developer needs to apply to existing customization so this migration won’t be unsuccessful for business.

Chapter I: Pre-Development Considerations

Prior to development, you need to check, if it’s possible to migrate to LEX – by Salesforce features which are already in the organization. Salesforce development team are doing their best to deliver most of the functionality to LEX, so, it only means that you can plan your migration pretty soon. Below is the list of the features, which are not available in LEX:

  • General Platform
    • Javascript buttons
    • Mass Actions On A Records List View
    • Mass Inline Editing for List View
    • Recycle Bin
    • Printable View
    • Manual Sharing
  • Sales Cloud
    • Account Contact Roles
    • Account Partners
    • Divisions
    • Mass Email (for Users, Cases, Campaigns). Mass Email Scheduling
    • Customizable Forecasting
    • Similiar Opportunities
    • Territory Management
    • Opportunity PartnersSales Cloud
  • Service Cloud
    • Public Knowledge
    • Solutions
    • Ideas
    • SOS
    • Knowledge (can be used, but lots of functionality is unavailable at this moment)

If your business is OK with switching to Salesforce Classic to continue to use those features – then you’ll be fine to proceed with customization changes

Chapter II: Moving Existing Customization To LEX

Basically, two main issues when you’re migrating will be:

  • Custom Javascript Buttons (some people still have all the business logic developed as a series of custom buttons!)
  • Visualforce Pages Service Cloud

Custom Javascript Buttons

The main point here will be – change the javascript buttons to lightning quick actions, here it’s described in details on how to do this. The reason to ban javascript buttons is simple – it’s unsafe, and usually not very user-friendly. Below I’ll put a simple example on how to change the button from Javascript to a Lightning Quick Action.

1. Identify The Process Behind Your Button

There are a lot of the use cases for Javascript buttons – from simple record update – to difficult business process of creation of the data model along with triggering the webservice from Apex. Basically, the more complex your javascript button logic is – the worse is system design – and this requires rework of the whole process. But for a simple record update/new records creation, there’s a very simple process of transition, which as a result will give No Changes for an end user flow.

2. Move the code!

The simple example of the case javascript button named “Transform Social Case”
 
transform-social-case.js
 

{!REQUIRESCRIPT("/soap/ajax/23.0/connection.js")};

var сaseObject = new sforce.SObject("Case");
сaseObject.Id = "{!Case.Id}";
var type = "{!Case.Type}";

if ( type != "Social" )
{
    alert("error message for non-social case");
}

else
{
    сaseObject.Type = "Social Plus";
    var result = sforce.connection.update([сaseObject]);

    if (result[0].success == "true")
    {
        location.reload(true);
    }

    else
    {
        alert(result[0].errors.message);
    }
}

 

The process is simple as 1-2-3: Check the case type and update it if it’s social. It’s super easy to move this process to lightning component – no Apex required!
 
TransformSocialCaseAction.cmp
 

<aura:component implements="force:LightningQuickActionWithoutHeader,force:hasRecordId">

    <aura:attribute name="record" type="Object"/>
    <aura:attribute name="simpleRecord" type="Object"/>
    <aura:attribute name="recordError" type="String"/>

    <force:recordData aura:id="recordLoader"
      recordId="{!v.recordId}"
      layoutType="FULL"
      targetRecord="{!v.record}"
      targetFields="{!v.simpleRecord}"
      targetError="{!v.recordError}"
      recordUpdated="{!c.handleRecordUpdated}"
    />

    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            <ui:message title="Error" severity="error" closable="true">
                {!v.recordError}
            </ui:message>
        </div>
    </aura:if>
</aura:component>

 
TransformSocialCaseActionController.js
 

({
    handleRecordUpdated: function(component, event, helper) {
        var eventParams = event.getParams();
        if (eventParams.changeType === "LOADED") {
            helper.updateSocialTypeCase(component);
        }
    }
})

 
TransformSocialCaseActionHelper.js
 

({
    updateSocialTypeCase : function(component) {
        var caseObject = component.get("v.record");

        if (caseObject.Type != "Social") {
            component.set("v.recordError", "error message for non-social case");
        } else {
            caseObject.Type = "Social Plus"
            component.set("v.record", caseObject);
            component.find("recordLoader").saveRecord(
                $A.getCallback(
                    function(saveResult) {
                        //You can add error handling here
                    }
                )
            );
        }
    }
})

A Final step will be simple – create action with this component for Case sObject and add it to the layout! No Apex/3rd-party library required.

Visualforce Pages

Every scenario here is pretty unique, however, few common page types can be separated: Reports pages, PDF-rendered pages, and new/edit/view pages. For the PDF we’ll keep it as it is – as no changes and no ability to render lightning components as a pdf. But for the report-like and new/edit/view pages we have to change it to the lightning UI;
1. Set the lightning styles

If you want it fast, your page is too big to transform and you just want it to look like Lightning UI? Quickest way will be just to add styles – although, it is limited (e.g. some VF tags are not available – click here for details). How to do this?
 
LightningPage.vfpage
 

<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">

  <apex:slds />

    <div class="slds-scope">

        <!-- CONTENT GOES HERE -->

    </div>

</apex:page>

That’s IT! No changes in your controller, only some small changes in the page markup! But if you want to go even further.
2. Transform the Visualforce page into a set of Lightning Components
Well.. sometimes – this is hardcore, especially, if the backend composition is hard and depends on a viewstate – because there’s no viewstate in Lightning UI. I will not focus on the frontend changes – they will be huge, and they will be different from page to page. Please note, that some things are critically changed, especially input lookup (native one is unavailable) and some chatter vf tags.

But how to transform the backend? At first, we should answer two questions: – Should we use page both on Salesforce Classic and in Lightning UI? If yes, then consider lightning styles for a visualforce page. Only do this if you cannot utilize VF page in two different places – has your backend developed through @RemoteAction annotated methods, or it’s done via view state instance methods?

RemoteAction changes

RemoteAction changes are very simple – both VF and LC can use the same controller with the same methods! An example below will compile and work:
 
TestController.apxc
 

public class TestController {

    @AuraEnabled @RemoteAction

    public static void remoteAction() {

        //magic in here

    }

}

Instance Methods Changes
Let’s say, that in 95% of all cases visualforce controllers contain both logic, model and view model data (view model data – is button enabled, Boolean showErrorMessage, etc). To be able to split to two different controllers – one for VF and one for LC we need to utilize full split of the classes: there will be one model, two view models (if they are actually needed – can be just for VF while aura will handle all of the view models as aura:attributes) and one business logic component. As the source, I’ll take this VF controller:
 
TestVFController.apxc
 

public with sharing class TestVFController {

    public Boolean hasAccountResults { get; private set; }

    public Boolean hasBrandResults { get; private set; }

    public Boolean isPopupEnabled { get; private set; }

    public Boolean hasSearchBeenInitiated { get; private set; }

    public List&lt;SObjectModel&gt; accounts;

    public List&lt;SObjectModel&gt; brands;

    public TestVFController() {

        //some logic to init model

    }

    public void search(String term) {

        //actual SOSL queries and model creation from SOSL results

    }

    public void save() {

        //actual DML operations to unwrap the model and save

    }

}

We should split that. There should be some service which will perform some operations on a model (not inside the controller), we should somehow unite the model and separate the model from the view model. Don’t forget why we’re doing this – to be able to reuse all of the code we did on a Lightning UI. Resulting code will be something like:
 
TestVFController.apxc
 

public with sharing class TestVFController {
    public ViewModel viewModel { get; private set; }

    //names should correspond to the processes and tasks for this class.
    //e.g. AccountModel if only accounts are in use, UpdateService if the service is doing update and so on.
    // I'm putting the simple realization at the bottom of this class

    public Model model { get; private set; }

    private Service service {get; private set;}


    public TestVFController() {

        viewModel = new ViewModel();

        service = new Service(); //should be common for two classes

        model = service.initializeModel();

    }

    public void search(String term) {

        service.search(model, term);

    }

    public void save() {

        service.save(model);

    }

    public class ViewModel {
        //some variables to control the view for VF page e.g. Boolean hasAccountResults

        public ViewModel(){}
    }

    public class Model {

        public List<SObjectModel> accounts;

        public List<SObjectModel> brands;

        public Model(){}

    }

    public class Service {

        public Service(){}

        public void search(...) {}

        public void save(...) {}

    }
}

 
TestLCController.apxc
 

public with sharing class TestLCController {

    @AuraEnabled
    public static State initializeLightningComponent() {

        State lightningComponentState = new State();

        lightningComponentState.initialize(service);

        return lightningComponentState;
    }

    @AuraEnabled
    public static State search(State lightningComponentState, String term) {

        lightningComponentState.service.search(lightningComponentState.model, term);

        return lightningComponentState; //can be removed. But usually state is changed after operations like search
    
    }

    @AuraEnabled
    public static State save(State lightningComponentState) {

        lightningComponentState.service.save(lightningComponentState.model);

        return lightningComponentState; //can be removed. But usually state is changed after operations like search
    }

    public class State {

        public @AuraEnabled TestVFController.ViewModel viewModel;

        public @AuraEnabled TestVFController.Model model;

        public @AuraEnabled TestVFController.Service service;

        public State() {}

        public void initialize() {

            viewModel = new TestVFController.ViewModel(); //This one can be completely removed as usually

                //view model info are not passed to apex controller and it's value determines inside of the javascript helpers

            service = new TestVFController.Service();

            model = service.initializeModel();
        }
    }
}

A state is stored in LC as an Aura. Attribute – an idea is to mock the view state by creating a new version of it. For creation and managing this State class, you can utilize Memento pattern

Final Thoughts

Lightning Components – is a different view of the frontend development – it’s made to be reusable. Use the same button set for different pages, use lightning components as building blocks. Visualforce Components were similar – but developers worldwide didn’t utilize the power of components and they are not designed as LC. So, this frontend change is the greatest change for development and a big challenge for developers – will developers change their mind about reusability?

Senior Salesforce Engineer, CoreValue

Tags

CoreValueLightning experienceLightning migrationSalesforce

Share


Recent Articles

Using Lightning Promises

June 14, 2018 | Bohdan Dovhan, Senior Engineer

Build Simple Employee List application with Datatable Introduction Before Lightning:Datatable was introduced by Salesforce, we had a project that required table data display in Lightning. There are often Visualforce legacy pages where display data tables need to be translated into Lightning. In such cases, Lightning:Datatable from the Lightning library can be used. Also, Lightning Promises […]

Salesforce Summer School at CoreValue

June 12, 2018

This program by CoreValue, a leading platform vendor and a Salesforce Registered partner, includes a training course, lectures and practical tasks in a real-time environment. The one-month course is designed for those who would like to plunge into working with the global Cloud-based CRM platform.