学习目标
完成本单元后,您将能够:
- 列出Salesforce Classic和Lightning Experience共享页面的两个好处。
 - 描述用户所请求的用户界面上下文与用户实际所在的用户界面上下文之间的区别。
 - 描述测试和确定当前用户的用户界面上下文的三种不同方式。
 
在经典和闪电体验之间共享Visualforce页面
我们建议尽可能创建Visualforce页面,无论这些页面是在Salesforce Classic还是Lightning Experience中运行。降低组织代码和配置复杂性的好处是显而易见的。还有一些上下文,例如Visualforce替代标准操作,你没有选择的地方。无论您是在Salesforce Classic,Lightning Experience还是Salesforce应用程序中运行,操作覆盖总是使用相同的页面。
然而,这是完全合理的,希望基于用户体验上下文在其中运行的行为或样式稍微或明显不同。在本单元中,我们将介绍各种创建可在所有用户体验中正常工作的页面的方法,以及代码如何检测特定上下文并对其进行更改。
然而,这是完全合理的,希望基于用户体验上下文在其中运行的行为或样式稍微或明显不同。在本单元中,我们将介绍各种创建可在所有用户体验中正常工作的页面的方法,以及代码如何检测特定上下文并对其进行更改。
在Visualforce标记中检测和响应用户体验上下文
使用$ User.UITheme和$ User.UIThemeDisplayed全局变量来确定当前的用户体验上下文。您可以在Visualforce表达式中使用这些变量,使页面适应Lightning Experience,Salesforce Classic和Salesforce应用程序。
这些全局变量返回一个唯一标识当前用户界面上下文的字符串。 $ User.UITheme和$ User.UIThemeDisplayed的可能值是相同的:
这些全局变量返回一个唯一标识当前用户界面上下文的字符串。 $ User.UITheme和$ User.UIThemeDisplayed的可能值是相同的:
- Theme1 – 已过时的Salesforce主题
 - Theme2-Salesforce Classic 2005用户界面主题
 - Theme3-Salesforce Classic 2010用户界面主题
 - Theme4d – 现代“闪电体验”Salesforce主题
 - Theme4t-Salesforce移动应用程序Salesforce主题
 - PortalDefault-Salesforce客户门户主题
 - Webstore-Salesforce AppExchange主题
 
两个变量的区别在于$ User.UITheme返回用户应该看到的外观,而$ User.UIThemeDisplayed返回用户实际看到的外观。例如,用户可能有首选项和权限来查看Lightning Experience外观,但是如果他们使用的浏览器不支持该外观(例如,旧版本的Internet Explorer),则$ User.UIThemeDisplayed返回一个不同的值。一般来说,你的代码应该使用$ User.UIThemeDisplayed。
使用这些主题全局变量的最简单的方法是在布尔表达式中使用一个,如{! $ User.UIThemeDisplayed ==“Theme3”},在组件的呈现属性。只有页面出现在所需的用户界面上下文中时,组件才会显示。
<apex:outputText value="This is Salesforce Classic." 
    rendered="{! $User.UIThemeDisplayed == 'Theme3' }"/>
<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>
<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="Salesforce1" 
        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>
超越基础
这是使用<apex:variable>的一种不寻常的方法,因为我们实际上并不关心创建的变量的值。相反,我们只需要一个组件,它不会自己提供任何输出来包装<apex:stylesheet>组件。您可以将其视为<apex:variable>“借出”其呈现的属性到包装的<apex:stylesheet>组件。
我们并不关心变量本身,这是一件好事,因为另外一个不寻常的方面就是变量不是真正创建的!功能或错误?我们称之为…未定义的行为,并避免在其他地方使用uiTheme变量。
在JavaScript中检测和响应用户体验上下文
在JavaScript代码中检测当前用户体验上下文非常重要,如果您在页面和应用程序中使用JavaScript。使用正确的技术来管理JavaScript代码中的导航特别重要。在您的JavaScript代码中处理UX上下文检测的最佳方法是使用可在任何地方使用的实用函数库。
起初,这看起来很简单,只需测试Visualforce标记中提供的相同全局变量即可。也许这样的事情
起初,这看起来很简单,只需测试Visualforce标记中提供的相同全局变量即可。也许这样的事情
function isLightningDesktop() {
    return( "{! $User.UIThemeDisplayed }" == "Theme4d");
}
<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){
        // 如果我们已经存在,不要覆盖自己。
        myContext.ForceUI = myContext.ForceUI || {};
        
        // 因为这是Visualforce,不是一个静态资源,
        // 我们可以在表达式中访问一个全局变量。
        myContext.ForceUI.UserUITheme = '{! $User.UIThemeDisplayed }';
    })(this);
</script>
<!-- UITHEME INJECTOR -->
</apex:page>
<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>isSalesforce1()</label>: 
       <span id="isSalesforce1JS">(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){
          // 仅诊断 - 不要直接使用此值
          document.getElementById('UserUIThemeJS').innerHTML = ForceUI.UserUITheme;
          // 而是使用这些实用方法
          document.getElementById('isSalesforce1JS').innerHTML = 
              ForceUI.isSalesforce1();
          document.getElementById('isLightningExperienceJS').innerHTML = 
              ForceUI.isLightningExperience();
          document.getElementById('isSalesforceClassicJS').innerHTML = 
              ForceUI.isSalesforceClassic();
      });
    </script>
  </body>
</html>
</apex:page>
// 这是一个匿名的自动执行的函数关闭thingie,
// 像所有这些天凉爽的孩子一样
(function(myContext){
    // 处理可能的执行顺序问题。
    // 如果我们已经存在,不要覆盖自己。
    myContext.ForceUI = myContext.ForceUI || {};
    // 根据本地UserUITheme值进行简单字符串比较的实用程序方法。
    // 该值从Visualforce页面注入,
    // 以允许$ User.UIThemeDisplayed全局的表达式评估。
    myContext.ForceUI.isSalesforceClassic = function() {
        return (this.UserUITheme == 'Theme3');
    }
    myContext.ForceUI.isLightningExperience = function() {
        return (this.UserUITheme == 'Theme4d');
    }
    myContext.ForceUI.isSalesforce1 = function() {
        return (this.UserUITheme == 'Theme4t');
    }
})(this);
确定Apex中的用户体验环境
使用UserInfo.getUiTheme()和UserInfo.getUiThemeDisplayed()系统方法确定Apex代码中的当前用户体验上下文。当你的控制器动作方法或属性需要在不同的上下文中表现不同时,你可以使用它们。
以下示例说明了如何通过在控制器扩展中通过getter方法使用这些方法来使用这些方法。
以下示例说明了如何通过在控制器扩展中通过getter方法使用这些方法来使用这些方法。
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();
    }    
}
- Theme1 – 已过时的Salesforce主题
 - Theme2-Salesforce Classic 2005用户界面主题
 - Theme3-Salesforce Classic 2010用户界面主题
 - Theme4d – 现代“闪电体验”Salesforce主题
 - Theme4t-Salesforce移动应用程序Salesforce主题
 - PortalDefault-Salesforce客户门户主题
 - Webstore-Salesforce AppExchange主题
 
在服务器端控制器代码中使用这些方法应该很少,至少与提供不同的Visualforce标记或JavaScript代码相比。对于您的控制器和控制器扩展代码来说,在UX上下文中是最好的做法。让您的前端代码(无论是Visualforce还是JavaScript)处理用户界面差异。
通过SOQL和API访问查询闪电体验
虽然我们不推荐这种技术,但您可以直接使用SOQL查询当前用户的首选用户体验。
基本的SOQL查询如下。
基本的SOQL查询如下。
SELECT UserPreferencesLightningExperiencePreferred FROM User WHERE Id = 'CurrentUserId'
<apex:page>
<script src="/soap/ajax/36.0/connection.js" type="text/javascript"></script>
<script type="text/javascript">
    // 查询偏好值
    sforce.connection.sessionId = '{! $Api.Session_ID }';
    var uiPrefQuery = "SELECT Id, UserPreferencesLightningExperiencePreferred " +
                      "FROM User WHERE Id = '{! $User.Id }'";
    var userThemePreferenceResult = sforce.connection.query(uiPrefQuery);
    
    // 在页面上显示返回的结果
    document.addEventListener('DOMContentLoaded', function(event){
        document.getElementById('userThemePreferenceResult').innerHTML = 
            userThemePreferenceResult;
    });
</script>
<h1>userThemePreferenceResult (JSON)</h1>
<pre><span id="userThemePreferenceResult"/></pre>
</apex:page>
