trailhead

Share Visualforce Pages Between Classic and Lightning Experience

Learning Objectives

After completing this unit, you’ll be able to:
  • List two benefits of sharing pages between Salesforce Classic and Lightning Experience.
  • Describe the difference between the user interface context requested by the user, and the user interface context the user is actually in.
  • Describe three different ways to test for and determine the current user’s user interface context.

Sharing Visualforce Pages Between Classic and Lightning Experience

We recommend that, wherever possible, you create Visualforce pages that behave correctly whether they run in Salesforce Classic or Lightning Experience. The benefits in terms of reduced complexity in your organization’s code and configuration are obvious. And there are a number of contexts, such as Visualforce overrides of standard actions, where you don’t have a choice. An action override always uses the same page, whether you’re running in Salesforce Classic, Lightning Experience, or the Salesforce app.

It’s perfectly reasonable, though, to want slightly or significantly different behavior or styling that’s based on the user experience context in which the page is running. In this unit we’ll look at a variety of ways to create pages that work correctly in all user experiences, and how your code can detect and make changes for specific contexts.

Detecting and Responding to the User Experience Context in Visualforce Markup

Use the $User.UITheme and $User.UIThemeDisplayed global variables to determine the current user experience context. You can use these variables in Visualforce expressions to adapt your pages to Lightning Experience, Salesforce Classic, and the Salesforce app.
These global variables return a string that uniquely identifies the current user interface context. The possible values for $User.UITheme and $User.UIThemeDisplayed are the same:
  • Theme1—Obsolete Salesforce theme
  • Theme2—Salesforce Classic 2005 user interface theme
  • Theme3—Salesforce Classic 2010 user interface theme
  • Theme4d—Modern “Lightning Experience” Salesforce theme
  • Theme4t—Salesforce mobile app theme
  • Theme4u—Lightning Console theme
  • PortalDefault—Salesforce Customer Portal theme
  • Webstore—Salesforce AppExchange theme
The difference between the two variables is that $User.UITheme returns the look and feel the user is supposed to see, while $User.UIThemeDisplayed returns the look and feel the user actually sees. For example, a user may have the preference and permissions to see the Lightning Experience look and feel, but if they are using a browser that doesn’t support that look and feel, for example, older versions of Internet Explorer, $User.UIThemeDisplayed returns a different value. In general, your code should use $User.UIThemeDisplayed.
The simplest way to use these theme globals is to use one in a Boolean expression, like {! $User.UIThemeDisplayed == "Theme3" }, in the rendered attribute of a component. The component will only display if the page appears in the desired user interface context.
<apex:outputText value="This is Salesforce Classic." 
    rendered="{! $User.UIThemeDisplayed == 'Theme3' }"/>
Although you can use this technique on individual user interface elements, it’s usually more efficient if you wrap larger chunks of markup into an <apex:outputPanel> or similar block-level component, and then create separate blocks for each different UI you want to present. Then place the theme test on the rendered attribute of the blocks, rather than the individual components. Not only should this perform better, your code will be less complicated.
<apex:outputPanel rendered="{! $User.UIThemeDisplayed == 'Theme3' }">
    <apex:outputText value="This is Salesforce Classic."/>
    <apex:outputText value="These are multiple components wrapped by an outputPanel."/>
</apex:outputPanel>
<apex:outputPanel rendered="{! $User.UIThemeDisplayed == 'Theme4d' }">
    <apex:outputText value="Everything is simpler in Lightning Experience."/>
</apex:outputPanel>
Another strategy you can use this with is to dynamically select a stylesheet to include on your page, and provide a different stylesheet for each theme. This is a bit trickier than you might think, because the <apex:stylesheet> tag doesn’t have a rendered attribute of its own. In this case, you must wrap the stylesheet components within another component that does have a rendered attribute. Here’s an example of how to provide a different stylesheet for each of the three modern themes supported by Salesforce.
<apex:page standardController="Account">

    <!-- Salesforce Classic "Aloha" theme -->
    <apex:variable var="uiTheme" value="classic2010Theme" 
        rendered="{!$User.UIThemeDisplayed == 'Theme3'}">
        <apex:stylesheet value="{!URLFOR($Resource.AppStyles, 
                                         'classic-styling.css')}" />
    </apex:variable>
    
    <!-- Lightning Desktop theme -->
    <apex:variable var="uiTheme" value="lightningDesktop" 
        rendered="{!$User.UIThemeDisplayed == 'Theme4d'}">
        <apex:stylesheet value="{!URLFOR($Resource.AppStyles, 
                                         'lightning-styling.css')}" />
    </apex:variable>
    
    <!-- Salesforce mobile theme -->
    <apex:variable var="uiTheme" value="SalesforceApp" 
        rendered="{!$User.UIThemeDisplayed == 'Theme4t'}">
        <apex:stylesheet value="{!URLFOR($Resource.AppStyles, 
                                         'mobile-styling.css')}" />
    </apex:variable>

    <!-- Rest of your page -->
    
    <p>
        Value of $User.UIThemeDisplayed: {! $User.UIThemeDisplayed }
    </p>
</apex:page>

Beyond the Basics

This is an unusual way to use <apex:variable>, because we’re not actually interested in the value of the variable created. Instead we just want a component that doesn’t render any output of its own to wrap the <apex:stylesheet> component. You can think of this as <apex:variable> “lending” its rendered attribute to the wrapped <apex:stylesheet> component.

It’s a good thing we don’t care about the variable itself, because another unusual aspect of wrapping the <apex:variable> component around something else is that the variable isn’t actually created! Feature or bug? Let’s call it...undefined behavior, and avoid using the uiTheme variable elsewhere.

Detecting and Responding to the User Experience Context in JavaScript

Detecting the current user experience context in JavaScript code is important if you’re using JavaScript heavily in your pages and apps. It’s especially important for using the right technique to manage navigation in your JavaScript code.

To detect what the user sees in JavaScript, we use a similar method to determining the current user experience context in Visualforce. Call the UITheme.getUITheme() global variable to return a value that identifies the current user interface theme.

Here the code checks if the current user experience context is the Lightning Experience theme.

function isLightningDesktop() {
  return UITheme.getUITheme === "Theme4d";
}

Determining the User Experience Context in Apex

Use the UserInfo.getUiTheme() and UserInfo.getUiThemeDisplayed() system methods to determine the current user experience context in Apex code. You can use them when your controller action methods or properties need to behave differently in different contexts.
The following example illustrates how to use these methods by making them available via getter methods in a controller extension.
public with sharing class ForceUIExtension {

    // Empty constructor, required for Visualforce controller extension
    public ForceUIExtension(ApexPages.StandardController controller) { }
    
    // Simple accessors for the System.UserInfo theme methods
    public String getContextUserUiTheme() {
        return UserInfo.getUiTheme();
    }    
    public String getContextUserUiThemeDisplayed() {
        return UserInfo.getUiThemeDisplayed();
    }    

}
You could of course work with the values in your Apex code, rather than directly returning the method call results.
These Apex system methods return a string that uniquely identifies the current user interface context. The possible values returned by these methods are the same as those returned by the $User.UITheme and $User.UIThemeDisplayed global variables.
  • Theme1—Obsolete Salesforce theme
  • Theme2—Salesforce Classic 2005 user interface theme
  • Theme3—Salesforce Classic 2010 user interface theme
  • Theme4d—Modern “Lightning Experience” Salesforce theme
  • Theme4t—Salesforce mobile app theme
  • Theme4u—Lightning Console theme
  • PortalDefault—Salesforce Customer Portal theme
  • Webstore—Salesforce AppExchange theme

Using these methods in server-side controller code should be rare, at least compared to providing different Visualforce markup or JavaScript code. It’s a best practice for your controller and controller extension code to be neutral in terms of the UX context. Let your front end code, whether Visualforce or JavaScript, handle the user interface differences.

Querying for Lightning Experience via SOQL and API Access

Although we don’t recommend this technique, you can query for the current user’s preferred user experience directly using SOQL.
The basic SOQL query is the following.
SELECT UserPreferencesLightningExperiencePreferred FROM User WHERE Id = 'CurrentUserId'
The result is a raw preference value, which you need to convert into something useable.
Here’s just about the simplest possible Visualforce page that runs the above SOQL query and displays the result on the page.
<apex:page>

<script src="/soap/ajax/36.0/connection.js" type="text/javascript"></script>
<script type="text/javascript">

    // Query for the preference value
    sforce.connection.sessionId = '{! $Api.Session_ID }';
    var uiPrefQuery = "SELECT Id, UserPreferencesLightningExperiencePreferred " +
                      "FROM User WHERE Id = '{! $User.Id }'";
    var userThemePreferenceResult = sforce.connection.query(uiPrefQuery);
    
    // Display the returned result on the page
    document.addEventListener('DOMContentLoaded', function(event){
        document.getElementById('userThemePreferenceResult').innerHTML = 
            userThemePreferenceResult;
    });
</script>

<h1>userThemePreferenceResult (JSON)</h1>

<pre><span id="userThemePreferenceResult"/></pre>

</apex:page>

Querying for the user’s Lightning Experience preference directly is discouraged. The result tells you what the user’s current preference setting is, not what user experience actually is on their screen. There are several use cases where the preference value might not reflect the user experience that’s actually being delivered. To determine the actual user experience being delivered in the current request, use $User.UIThemeDisplayed or UserInfo.getUiThemeDisplayed().

retargeting