trailhead

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. The best way to handle UX context detection in your JavaScript code is with a library of utility functions you can use everywhere.
At first this seems simple, just test against the same global variables available in Visualforce markup. Maybe something like this.
function isLightningDesktop() {
    return( "{! $User.UIThemeDisplayed }" == "Theme4d");
}
And if you add this code to a Visualforce page, lo, it works.

Here’s the problem. As soon as you move this code into a static resource—a best practice for code organization, improving performance, and other reasons—it stops working, because global variables aren’t available in static resources. Static resources aren’t processed for markup or expressions or globals—or at all. They’re simply served up. It’s why we call them static resources. ;-)

So how do we make this work, without putting duplicate JavaScript code in every page that needs to test for the user experience context? By creating a very simple page that does nothing but inject the $User.UIThemeDisplayed value into the right JavaScript context, and then using <apex:include> to add it to a page. Then we can test against the injected value in our actual utility code.

Here’s the “shim” Visualforce page that we use to inject the $User.UIThemeDisplayed global variable into a JavaScript context, as well as include the JavaScript utility static resource that uses it.
<apex:page docType="html-5.0" applyBodyTag="false" applyHtmlTag="false"
           showHeader="false" standardStylesheets="false">

<!-- UIUTILS SCRIPT -->
<apex:includeScript value="{!URLFOR($Resource.ForceUI)}"/>
<!-- UIUTILS SCRIPT -->

<!-- UITHEME INJECTOR -->
<script type="text/javascript">
    (function(myContext){
        // Don't overwrite ourself if we already exist.
        myContext.ForceUI = myContext.ForceUI || {};
        
        // Because this is Visualforce, not a static resource,
        // we can access a global variable in an expression.
        myContext.ForceUI.UserUITheme = '{! $User.UIThemeDisplayed }';
    })(this);
</script>
<!-- UITHEME INJECTOR -->

</apex:page>

This page does two things. First, it pulls in the JavaScript static resource that contains the actual utility method code. (Which, I promise, we will get to.) Second, it has an inline JavaScript that, because it’s running in a Visualforce page instead of a static resource, can evaluate an expression with the theme global. This script sets a variable inside the ForceUI utility object. This copies the theme value from Visualforce into JavaScript, so that it can be referenced by the JavaScript code that’s in the static resource.

This “page” isn’t intended to be accessed directly, but rather included into your real pages. This makes the work of adding the JavaScript utility methods a single line in those pages. (Raise your hand if you remember when #include wasn’t something you did on social media. Right there with you. Now let’s get those kids off that lawn!)

Let’s look at how this is used. Here’s a very simple page that shows how to add the JavaScript utility methods to the page, using the <apex:include> component within the <head> block. At the bottom of the page is a short bit of JavaScript that illustrates how to use the utility methods from within JavaScript. We’ve added emphasis to the code to highlight these elements.
<apex:page standardController="Account" extensions="ForceUIExtension"
           showHeader="false" standardStylesheets="false"
           applyHtmlTag="false" applyBodyTag="false"
           docType="html-5.0" title="ForceUI Utilities">

<html lang="en">
  <head>
    <title>ForceUI Utilities</title>
    <apex:include pageName="UIThemeUtilsInclude"/>
  </head>

  <body>
      
    <h1>ForceUI Utilities</h1>
    
    <p>This is a page used for testing different ways of determining 
       the user interface context in which it's being displayed.</p>
    
    <h2>$User.UITheme Global Variable</h2>
    
    <p><label>$User.UITheme</label>: {! $User.UITheme }</p>
    <p><label>$User.UIThemeDisplayed</label>: {! $User.UIThemeDisplayed }</p>
    
    
    <h2>UIUtils JavaScript</h2>
    
    <p><label>ForceUI.UserUITheme</label>: 
       <span id="UserUIThemeJS">(loading...)</span></p>
      
    <p><label>isSalesforceApp()</label>: 
       <span id="isSalesforceAppJS">(loading...)</span></p>
      
    <p><label>isLightningExperience()</label>: 
       <span id="isLightningExperienceJS">(loading...)</span></p>
      
    <p><label>isSalesforceClassic()</label>: 
       <span id="isSalesforceClassicJS">(loading...)</span></p>

    <script type="text/javascript">
      document.addEventListener('DOMContentLoaded', function(event){
          // Diagnostic only -- don't use this value directly
          document.getElementById('UserUIThemeJS').innerHTML = ForceUI.UserUITheme;
          // Instead, use these utility methods
          document.getElementById('isSalesforceAppJS').innerHTML = 
              ForceUI.isSalesforceApp();
          document.getElementById('isLightningExperienceJS').innerHTML = 
              ForceUI.isLightningExperience();
          document.getElementById('isSalesforceClassicJS').innerHTML = 
              ForceUI.isSalesforceClassic();
      });
    </script>
  </body>
</html>
</apex:page>
View this page in your org, in Lightning Experience, Salesforce Classic, and even the Salesforce app to confirm that the values changes depending on the environment.
Finally (#finally), here’s the utility library that contains the JavaScript utility functions that let you create if expressions to conditionally affect the results of your app’s JavaScript code based on the user interface context it’s running in.
// This is an anonymous self-executing function closure thingie,
// like all the cool kids are using these days.
(function(myContext){

    // Deal with possible order-of-execution issues.
    // Don't overwrite ourself if we already exist.
    myContext.ForceUI = myContext.ForceUI || {};

    // Utility methods that make simple string comparisons
    // against a local UserUITheme value. This value is
    // injected from a Visualforce page to allow expression
    // evaluation of the $User.UIThemeDisplayed global.
    myContext.ForceUI.isSalesforceClassic = function() {
        return (this.UserUITheme == 'Theme3');
    }
    myContext.ForceUI.isLightningExperience = function() {
        return (this.UserUITheme == 'Theme4d');
    }
    myContext.ForceUI.isSalesforceApp = function() {
        return (this.UserUITheme == 'Theme4t');
    }
})(this);

Except for the possibly unfamiliar syntax of the self-executing function, the code here is laughably simple. The result of the code executing is a utility object, ForceUI, added to the global scope of your page. The object receives, from the injector JavaScript in the earlier Visualforce shim page, the value of the $User.UIThemeDisplayed global variable. This value is saved in a local variable named UserUITheme, which you should treat as a private implementation detail. Never access it directly!

The public API of the object is exposed as a series of functions, isLightningExperience() and so on, which you use in the rest of your code as illustrated above. You can even add your own additional functions, for example, to tell mobile from desktop, or one.app from plain Visualforce.

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