Visualforce和闪电体验

Visualforce和闪电体验 – 在经典体验和闪电体验之间共享Visualforce页面

学习目标

完成本单元后,您将能够:

  • 列出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的可能值是相同的:
  • 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>或类似的块级别组件中,则通常会更有效,然后为每个想要呈现的不同UI创建单独的块。然后将主题测试放置在块的呈现属性上,而不是单个组件。这不仅应该表现得更好,你的代码将不那么复杂。
<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:stylesheet>标记没有自己的渲染属性。在这种情况下,您必须将样式表组件包装在具有渲染属性的另一个组件中。以下是如何为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="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标记中提供的相同全局变量即可。也许这样的事情
function isLightningDesktop() {
    return( "{! $User.UIThemeDisplayed }" == "Theme4d");
}

如果您将此代码添加到Visualforce页面,则它可以工作。
这是问题。只要将此代码移动到静态资源中(这是代码组织的最佳实践,提高性能和其他原因),它就会停止工作,因为全局变量在静态资源中不可用。不会为标记或表达式或全局处理静态资源,或者根本不处理静态资源。他们只是服务。这就是为什么我们称之为静态资源。 😉

那么,我们如何做到这一点,而不是在每个需要测试用户体验上下文的页面中添加重复的JavaScript代码呢?通过创建一个非常简单的页面,只需将$ User.UIThemeDisplayed值插入到正确的JavaScript上下文中,然后使用<apex:include>将其添加到页面。然后我们可以在我们的实际工具代码中测试注入的值。

下面是我们用于将$ User.UIThemeDisplayed全局变量注入到JavaScript上下文中的“shim”Visualforce页面,以及包含使用它的JavaScript实用程序静态资源。

<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>

这个页面有两件事。首先,它抽取包含实际工具方法代码的JavaScript静态资源。 (其中,我承诺,我们将继续讨论)。其次,它有一个内联JavaScript,因为它在Visualforce页面中运行,而不是静态资源,因此可以使用全局主题评估表达式。此脚本在ForceUI实用程序对象内设置一个变量。这会将Visualforce的主题值复制到JavaScript中,以便可以通过静态资源中的JavaScript代码进行引用。

这个“页面”不是直接访问,而是包含在你的真实页面中。这使得在这些页面中添加JavaScript实用程序方法成为单行的工作。 (如果你记得#include不是你在社交媒体上做的事情,那么举起你的手,就在那儿,现在让我们把那些孩子从那个草坪上拿走吧!)

我们来看看如何使用它。这是一个非常简单的页面,展示了如何使用<head>块中的<apex:include>组件将JavaScript实用程序方法添加到页面中。在页面底部是一小段JavaScript,演示了如何使用JavaScript中的实用工具方法。我们在代码中加入了重点来突出这些元素。

<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>
在您的组织,Lightning Experience,Salesforce Classic甚至Salesforce应用程序中查看此页面,以确认这些值取决于环境而变化。
最后(#finally),这里是包含JavaScript实用程序函数的实用程序库,它允许您创建表达式,以根据运行的用户界面上下文有条件地影响应用程序JavaScript代码的结果。
// 这是一个匿名的自动执行的函数关闭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);

除了自执行函数可能不熟悉的语法之外,这里的代码是非常简单的。代码执行的结果是一个实用程序对象ForceUI,添加到您的页面的全局范围。该对象从早期的Visualforce填充页面中的JavaScript注入器接收$ User.UIThemeDisplayed全局变量的值。该值保存在一个名为UserUITheme的本地变量中,您应该将其视为一个私有的实现细节。切勿直接访问它!

该对象的公共API被暴露为一系列的函数,isLightningExperience()等等,你在代码的其余部分中使用,如上图所示。您甚至可以添加自己的附加功能,例如,从桌面或从简单的Visualforce中分配one.app。

确定Apex中的用户体验环境

使用UserInfo.getUiTheme()和UserInfo.getUiThemeDisplayed()系统方法确定Apex代码中的当前用户体验上下文。当你的控制器动作方法或属性需要在不同的上下文中表现不同时,你可以使用它们。
以下示例说明了如何通过在控制器扩展中通过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();
    }    

}
您当然可以使用Apex代码中的值,而不是直接返回方法调用结果。
这些Apex系统方法返回一个唯一标识当前用户界面上下文的字符串。这些方法返回的可能值与$ User.UITheme和$ User.UIThemeDisplayed全局变量返回的值相同。
Theme1 – 已过时的Salesforce主题
  • 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查询如下。
SELECT UserPreferencesLightningExperiencePreferred FROM User WHERE Id = 'CurrentUserId'
结果是一个原始的偏好值,你需要转换成可用的东西。
以下是运行上述SOQL查询的最简单的Visualforce页面,并在页面上显示结果。
<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>
不鼓励直接查询用户的Lightning Experience偏好。结果将告诉您用户当前的首选项设置是什么,而不是用户的实际体验。有几种使用情况下,首选项值可能不会反映实际交付的用户体验。要确定当前请求中传递的实际用户体验,请使用$ User.UIThemeDisplayed或UserInfo.getUiThemeDisplayed()。

你可能也会喜欢...