Lightning-Visualforce(7)

学习目标

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

  • 列出至少两个组件以避免在Lightning Experience中使用的页面上使用。
  • 确定三个Lightning Experience功能,您无法使用Visualforce页面或组件。

了解在闪电体验中要避免哪些特征

我们建议您避免在Lightning Experience中使用的页面上存在有限数量的Visualforce组件。此外,在Lightning Experience中使用Visualforce的一些功能时会有不同的表现。最后,Lightning Experience中有几个地方不能使用Visualforce页面或应用程序,或者它们可能无法正常工作。
闪电体验还在不断发展和壮大,安全港警报! – 我们希望随着时间的推移缩小这个名单。

闪电体验标题和导航菜单不能被抑制

在Lightning Experience中运行时,Visualforce页面始终显示标准的Lightning Experience用户界面。没有办法抑制或改变Lightning Experience标题或边栏。特别是,在“Lightning Experience”中显示时,<apex:page>的showHeader和sidebar属性对Visualforce页面没有影响。
这种行为是有意的。在Lightning Experience中显示的应用程序是Lightning Experience应用程序。如果您需要为您的应用程序提供完全自定义的界面,则需要在Salesforce Classic中运行它。

Salesforce Classic标题和边栏始终被抑制

在Lightning Experience中显示时,标准Salesforce Classic标题和侧边栏始终会被隐藏。特别是,在“Lightning Experience”中显示时,<apex:page>的showHeader和sidebar属性对Visualforce页面没有影响。
页面的行为就好像<apex:page>的showHeader和sidebar属性都设置为false。

注意

确定是否包含或取消标准Salesforce Classic样式表的<apex:page>的standardStylesheets属性不受Lightning Experience的影响。也就是说,在Lightning Experience中它默认为true,但是你可以改变它。

<apex:relatedList>和黑名单相关列表

有许多相关的列表在Lightning Experience中不被支持。这些相关的名单是“黑名单”,这意味着他们被明确阻止使用。正如您所期望的那样,这些相同的相关列表在Visualforce中使用<apex:relatedList>标记列入黑名单。
有关Lightning Experience不支持哪些相关列表的详细信息,请参阅联机帮助中的“数据访问和视图:闪电体验中的不同或不可用”。

避免 <apex:iframe>

尽管在Lightning Experience的Visualforce页面上使用<apex:iframe>并非不可能,但我们建议避免使用它。
在Lightning Experience中显示时,Visualforce页面将封装在各自的iframe中。正如在探索Visualforce应用程序容器中详细讨论的那样,这对页面的行为有很多重要的影响。向iframe堆栈添加额外的级别会增加环境的复杂性。

如果您真的了解iframe以及它们如何影响DOM和JavaScript,则可以管理这种复杂性。但是除非你已经在使用嵌套的iframe,否则很难调试问题。出于这个原因,我们建议您避免在Lightning Experience中使用的页面上使用此标签。

不,真的,不要直接设置window.location

在这一点上,我们可能听起来像一个破碎的记录,但这很重要。如果你的页面的JavaScript代码直接设置了window.location变量,当页面显示在Lightning Experience中时,这个功能将不起作用。您必须修改此代码才能使页面在Lightning Experience中运行。
有关详情,请参阅管理导航单元。

sforce.one不是Salesforce Mobile-Only

sforce.one JavaScript实用程序对象可用于Salesforce应用程序和Lightning Experience中的Visualforce页面。如果您一直在使用sforce.one对象作为判断您的页面是否在移动或桌面环境中运行的方式,则需要更新代码。
使用其中一种记录的方法来区分Salesforce Classic,Salesforce应用程序和Lightning Experience环境。 Visualforce,Apex和JavaScript中提供了支持的技术。

有关完整的详细信息,请参阅在Classic和Lightning Experience之间共享Visualforce页面单元。

动作重写的更改

对于可能难以解决的事情,Visualforce覆盖标准操作的最显着的变化可能与Lightning Experience相比略有不同。 Lightning Experience中将无法访问对象列表操作的任何覆盖。
具体而言,您可以在Salesforce Classic中为对象覆盖六个标准操作:
  • Object tab
  • Object list
  • Record view
  • Record edit
  • Record create
  • Record delete

在闪电体验中,前两个动作合并为一个页面,将对象归入主页。对象首页与对象列表类似,添加了对象选项卡的一些元素,例如最近的项目。其他的,如报告或工具,已经转移到用户界面的其他部分。
无论组织中的用户界面设置如何,“对象”选项卡和对象列表都可在“设置”中被覆盖。按照预期,覆盖对象选项卡操作将覆盖Lightning Experience中的对象主页。

但是,在Lightning Experience中,对象列表操作在用户界面中无法访问,因此无法将其解除。如果您的组织已经重写了任何对象的对象列表操作,那么当用户使用Lightning Experience时,该功能将不可用。如果在重写中有必要的功能,则需要找到其他方法使其可用。

此表列出了您可以在安装程序中替代对象的标准操作,以及三种不同用户体验中覆盖的操作。

在设置中覆盖 Salesforce Classic 闪电的经验 Salesforce应用程序
选项卡 对象选项卡 对象主页 搜索
列表 对象列表 n/a 对象主页
视图 记录视图 记录主页 记录主页
编辑 记录编辑 记录编辑 记录编辑
创建 纪录创建 纪录创建 纪录创建
删除 记录删除 记录删除 记录删除

注意

“n/a”并不意味着您不能访问标准行为,也并不意味着您不能覆盖标准行为。 这意味着你不能访问覆盖。 这是覆盖的功能,不可用。

Lightning-Visualforce(6)

学习目标

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

  • 描述改变内置Visualforce组件样式的两种方法。
  • 描述可以使用CSS样式表进行更改的Salesforce Classic和Lightning Experience样式之间的差异。
  • 描述将Salesforce Lightning设计系统应用于Visualforce页面的两种方法。

了解重要的视觉设计注意事项

无论是在Salesforce Classic还是Lightning Experience中运行,Visualforce页面都是一样的,除非您重新修改它们以适应适当的用户界面上下文。显示用户界面元素的内置Visualforce组件不容易重新配置,以与Lightning Experience外观相匹配。
具体而言,当页面显示在Lightning Experience中时,由内置的Visualforce组件呈现的HTML不会更改,并且这些组件使用的Salesforce Classic样式表默认情况下由页面加载。其效果是,使用<apex:inputField>,<apex:outputField>,<apex:pageBlock>组件以及与Salesforce Classic可视化设计相匹配的其他粗糙和精细组件的页面仍然与该可视化设计相匹配。您在Lightning Experience中获得一小部分Salesforce Classic。

这是我们的一般建议,现在,对于现有的网页,你不要试图去适应闪电体验的视觉设计。闪电体验仍在不断发展,自己的造型搭配意味着你正在追逐一个移动的目标。这是工作。

不过,在某些情况下,您会希望某些页面与Lightning Experience的视觉效果更加匹配。对于新的页面,或者如果你愿意做一些工作,有一些很棒的工具可以用来创建完全符合Lightning Experience的页面。

影响标准件的样式

Visualforce为调整或覆盖标准组件的样式提供了一系列选项。如果您的目标是对这些组件的外观进行适度的更改,则使用这些选项的努力也是同样适度的。我们来看看可用于影响样式的一些工具。

个别组件样式

生成HTML的Visualforce组件具有传递样式和styleClass属性。这些属性允许您使用自己的样式和样式类来控制生成的HTML的外观和风格。样式允许您直接在组件上设置样式,而styleClass则允许您为别处定义的样式附加类。例如,以下代码设置<apex:outputText>的类并应用样式。

<apex:page>

    <style type="text/css">
        .asideText { font-style: italic; }
    </style>

    <apex:outputText style="font-weight: bold;" 
        value="This text is styled directly."/>

    <apex:outputText styleClass="asideText" 
        value="This text is styled via a stylesheet class."/>

</apex:page>

添加一个自定义样式表

您可以使用静态资源和<apex:stylesheet>标记将自己的自定义样式表添加到任何Visualforce页面。例如,要添加一个样式表作为名为“AppStylesheet”的静态资源上传,请在页面中添加以下内容。

<apex:stylesheet value="{!$Resource.AppStylesheet}"/>

然后,您可以引用样式表中包含的任何样式,并在Visualforce标记styleClass属性中引用它们,就像我们之前使用asideText样式所做的那样。
这是将CSS样式定义添加到Visualforce页面的推荐方法,因为它共享页面之间的样式表,并最小化您需要添加到每个页面的标记。

Salesforce Lightning设计系统是用于添加样式表的这种方法的例外。闪电设计系统是一个梦幻般的全新的网页造型工具,我们将在不久后详细讨论。

尽管您可以将Lightning设计系统作为静态资源上传,并使用<apex:stylesheet>进行引用,但还有一种更简单的方法:只需在页面标记中的任意位置包含<apex:slds />即可。

闪电体验中的不同风格

仅当您的页面在Lightning Experience中运行时才加载自定义样式表,请使用以下标记。这与在经典和闪电体验之间共享Visualforce页面中的Visualforce标记示例类似。

<apex:page standardController="Account">

    <!-- 基础样式 -->
    <apex:stylesheet value="{!URLFOR($Resource.AppStyles, 'app-styles.css')}" />
    
    <!-- 闪电桌面额外的样式 -->
    <apex:variable var="uiTheme" value="lightningDesktop" 
        rendered="{!$User.UIThemeDisplayed == 'Theme4d'}">
        <apex:stylesheet value="{!URLFOR($Resource.AppStyles, 'lightning-styling.css')}" />
    </apex:variable>

    <!-- 其余的页面 -->
    
</apex:page>
好的,这些都是工具。让我们看看接下来使用它们的一些技巧。

样式的策略和建议

要创建与Lightning Experience可视化设计相匹配的Visualforce页面,请使用Lightning Design System创建新页面。在Visualforce页面中使用Lightning Design System有两种方法。
在详细讨论之前,我们先考虑一下更高的层次,并考虑将闪电体验风格应用于网页的不同策略。具体来说,我们来谈谈你现有的页面。
有两种方法可以影响现有页面的样式,使其看起来更像闪电体验。

  • 更改标记以在页面中应用新的样式更改。
  • 更改样式表中现有标记更改的样式规则。

这些都不是/或。您可以单独使用或组合使用它们。

正确使用Lightning Design System意味着使用Lightning Design System样式表和全新的标记来显示Visualforce页面。同样,这是匹配Lightning Experience可视化设计的唯一支持的方法。

要做到这一点,您可以从他们的网站下载Lightning Design System样式表,并像使用其他样式表一样使用它们,也可以将<apex:slds>组件添加到Visualforce页面的标记中。 <apex:slds>组件允许您引用Lightning Design System样式表,而无需将其上载为静态资源,从而简化了页面的语法并防止您达到250 MB的静态资源限制。

使用<apex:slds>带有自己的一套准则和注意事项。如果您想了解更多信息,请参阅Lightning Design System徽章或查看参考资料部分的Visualforce Developer Guide链接。

也可以添加Lightning Design System样式表,并修改您的页面以使用它们。这是多少工作取决于你想如何紧密匹配Lightning Experience以及代码中的特定标记和组件。虽然以这种方式取得体面的结果是可能的,但是,这不是我们推荐的方法。闪电设计系统被设计用于特定的标记,这根本不是Visualforce发出的。有一个“阻抗不匹配”,虽然不是致命的,但当你走这条路时,肯定是你的鞋子里的一块严重的石头。

最后,还有另外一种方法:为现有的(或新的)样式表添加新的规则和样式,使现有的标记看起来更像Lightning Experience。如果您的页面已经大部分使用自己的样式表,那么这种方法可能适合您。相反,如果您主要使用内置的Visualforce组件和Salesforce Classic样式,则需要覆盖Salesforce Classic样式表中的样式。

尽管这在技术上是可行的,但我们希望阻止您采取这种方法。它将依赖关系引入到您不想拥有的标记和样式中。这些依赖关系位于由内置的Visualforce组件呈现的HTML的结构,ID和类中。我们希望在这里真正清楚:由内置的Visualforce组件呈现的HTML是内部实现细节,如有更改,恕不另行通知。如果您在自己的样式表中有依赖关系,那么您的样式将最终中断。

Salesforce闪电设计系统

闪电设计系统是用于构建类似闪电体验的企业应用程序的设计框架。它包含一个复杂的CSS框架,一组图形资产和Salesforce Sans字体。您可以使用闪电设计系统构建看起来非常华丽的页面和应用程序,并与Lightning Experience用户界面完美匹配。
闪电设计系统旨在使客户和合作伙伴轻松匹配闪电体验的外观和感觉。它还包括可以自定义外观和风格以匹配自己品牌颜色等的工具,同时仍然与整体Lightning Experience设计保持一致。

闪电设计系统如此之大,如此令人兴奋……我们不打算详细介绍在这里使用闪电设计系统。因为我们已经写了一个关于使用它的整个模块,Lightning Design System。它解释了如何获得Lightning Design System,使用它来设计页面的基本概念,以及如何将这些概念应用于使用Visualforce构建Lightning Experience应用程序。

闪电设计系统是一个很大的模块,你必须努力获得这个徽章。虽然我们希望保存这个模块的细节,但是我们不想让你完全挂在这里。那么,让我们介绍一下如何在Visualforce中使用Lightning Design System。

首先要知道的是,闪电设计系统采用了新的标记结构和样式类。出于这个原因,最好使用新的页面和应用程序。它建立在现代浏览器功能的基础上,并利用最新的标记和样式最佳实践。尽管我们都喜欢它,Visualforce已经有一段时间了。在它生成的HTML和客户页面中的静态代码之间,大多数组织将发现将Lightning设计系统应用于现有页面具有挑战性。

闪电设计系统模块专注于创建新的页面和应用程序,并且对该徽章进行评分是了解它的最佳方式。完成该模块后,您将了解如何使用Lightning设计系统,以及如何计划周围的开发。

Lightning-Visualforce(5)

学习目标

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

  • 描述用于控制从一个页面到另一个页面的三种“经典”Visualforce机制。
  • 描述哪些技术在Lightning Experience中不起作用。
  • 列出至少三个闪电体验导航事件,以及如何发送。

管理导航

应用程序流和导航在许多方面是应用程序设计的核心。 Visualforce提供了多种添加导航元素和指导应用程序流的方法。 Lightning Experience添加了自己的应用程序流程,导航元素和机制,以影响用户在使用Salesforce时的位置。
好消息是,“经典”的Visualforce导航继续工作。更好的消息是您的Visualforce页面也可以利用新的Lightning Experience机制。

导航闪电的经验

在我们谈论Visualforce导航的细节之前,以及如何创建它以使其在Salesforce Classic和Lightning Experience中起作用之前,我们先谈谈导航。 “导航”我们究竟意味着什么?
我们可能通过导航的第一件事是屏幕上的用户界面元素。你点击一下,发生什么事情。例如,您可以单击导航菜单中的“客户”项目,然后转到“客户”对象主页。您单击新建按钮,并出现记录条目窗体。您从快速操作菜单中选择自定义操作,然后启动自定义过程。等等。这些按钮和菜单项是导航元素。

导航系统(Lightning Experience中的用户界面)的设计与Salesforce Classic有很大不同。我们不打算在这里谈论这些差异,但是当您在两种用户体验之间切换时,您会希望熟悉所有事情的动向。您可以在Trailhead的导航闪电体验和设置单元中了解更多关于此的信息。

另一种较不明显的导航类型是上述“发生了什么”部分。在幕后,Salesforce决定在菜单中选择某个项目时发生的情况,或者单击链接或按钮。大部分导航已经内置到Salesforce中,而其他方面则是可定制的 – 例如,使用Visualforce页面覆盖操作。但是,所有这些导航都是由Salesforce编写的代码管理的。

然后在自己的应用程序中导航 – 使用您的代码来控制应用程序流的应用程序。当你的自定义操作打开一个表单,用户点击保存,你去哪里?当你的运行代码决定下一个用户应该去哪里,并在那里发送它们。这就是我们要在这个单元里讨论的内容。

经典的Visualforce导航
“经典”Visualforce导航可以归结为“在操作方法结束时会发生什么”。操作方法返回PageReference对象,其中包含用户要导航到的位置的详细信息,然后Visualforce框架将处理将正确的响应发送回用户的浏览器。而且,好消息,这一切仍然有效。
还要记住标准控制器从其操作方法中返回一个PageReference。因此,无论您使用的是标准控制器还是您自己的自定义控制器代码,您的现有导航都可以按照您的预期继续工作。

经典Visualforce导航

“经典”Visualforce导航可以归结为“在操作方法结束时会发生什么”。操作方法返回PageReference对象,其中包含用户要导航到的位置的详细信息,然后Visualforce框架将处理将正确的响应发送回用户的浏览器。而且,好消息,这一切仍然有效。
还要记住标准控制器从其操作方法中返回一个PageReference。因此,无论您使用的是标准控制器还是您自己的自定义控制器代码,您的现有导航都可以按照您的预期继续工作。

现代Visualforce导航

所以,如果经典的Visualforce导航工作,为什么我们仍然在谈论这个?我们甚至在谈论什么?我们只想对你说一个字。只是一个字。你在听么? …“JavaScript”。
JavaScript有一个很好的未来,而今天就在这里。许多Visualforce开发人员在他们的应用程序中大量使用JavaScript,并且这种使用持续增长。经典的Visualforce工作,并将继续工作很长一段时间。但是,随着开发人员采用远程对象和JavaScript远程等Visualforce功能,他们更多的应用程序的行为从服务器端迁移到浏览器和JavaScript,在那里没有PageReference这样的事情。

在Lightning Experience(和Salesforce应用程序)世界中,有一些用于在JavaScript中构建导航的规则和工具。我们将会介绍一些规则,主要是关于不该做的事情。先谈谈正确的做事方式吧。

Lightning Experience使用事件管理导航。导航事件框架作为一个JavaScript实用程序对象提供,该对象提供了许多可以直接创建程序化导航的功能。在运行Lightning Experience时,sforce.one对象会自动添加到Visualforce页面。这个对象提供了一些函数调用函数时触发导航事件。要使用这些功能,您可以直接从页面的JavaScript代码中调用它们,也可以将调用作为单击(或其他)处理程序附加到页面上的元素。

重要

sforce.one对象在Salesforce Classic中不可用。任何使用它的代码都应该首先测试sforce.one的存在。

sforce.one对象提供以下功能。引用来自sforce.one对象的使用虚线符号的函数。例如:sforce.one.navigateToSObject(…)。

功能 描述
back(​[refresh]) 导航到sforce.one历史中保存的以前的状态。这相当于单击浏览器的“后退”按钮。
navigateToSObject(​recordId​[, view]) 导航到由recordId指定的sObject记录。
navigateToURL(​url​[, isredirect]) 浏览到指定的网址。
navigateToFeed(​subjectIdtype) 导航到指定类型的供稿,其范围为subjectId。
navigateToFeedItemDetail(​feedItemId) 导航到特定的feed项目,feedItemId和任何关联的注释。
navigateToRelatedList(​relatedListIdparentRecordId) 导航到parentRecordId的相关列表。
navigateToList(​listViewId​, listViewNamescope) 导航到listViewId指定的列表视图,listViewId是要显示的列表视图的ID。
createRecord(​entityName​[, recordTypeId]) 打开页面为指定的entityName创建新记录,例如“Account”或 “MyObject__c”。
editRecord(​recordId) 打开页面以编辑由recordId指定的记录。

有关使用这些函数的更多详细信息及其接受的参数,请参阅本机资源中的使用sforce.one对象的导航。

导航问题,以及如何解决这些问题

在JavaScript中构建Visualforce导航的第一条规则是:不要直接设置window.location。在JavaScript中构建Visualforce导航的第二个规则是:不要直接设置window.location。

不要直接设置window.location

好的,无偿的重复和电影的参考,这里有什么大不了的?这很简单。当在闪电体验您的网页没有window.location设置!还记得之前关于Visualforce“容器”的讨论,还有一个关于iframe的内容,Lightning Experience是某种健康俱乐部? (SPA单页申请。)这是其中的一个东西。 Visualforce iframe不能直接访问window.location的值,所以你不能设置它。如果你的代码依赖于设置,它会中断。也就是说,通过设置window.location来触发导航的动作将停止导航到您期望的任何位置。

实际上有一个解决这个限制的方法,但是你不应该使用它。原因是,如果绕过sforce.one中的导航功能,您的导航事件将不会在Lightning Experience导航堆栈中进行跟踪。该堆栈提供了有用的功能,例如说明重定向等的后退按钮。 Lightning Experience(特别是Salesforce应用程序)中的许多功能都依赖于包含所有导航事件的堆栈。确保您正确使用它是值得的。

Salesforce Classic问题

所以,是的,这是一个…的东西。不幸的是,当您的页面在Salesforce Classic中运行时,sforce.one实用程序对象不可用。在这种情况下,你必须使用window.location。好消息是,在Salesforce Classic中,window.location可用。坏消息是,这个限制意味着你将不得不添加一个丑陋的if块给你的代码。考虑将导航功能包装在处理这种复杂性的实用程序方法中,以便您的主要导航逻辑可以很简单。

静态网址

不要将静态URL用于Salesforce资源。也就是说,如果要添加一个链接来编辑联系人记录,请不要使用link =’/’+ accountId +’/ e’等静态模式创建链接。在某些情况下,这是有效的,但在其他情况下则不行。相反,尝试以下方法之一:

  • 在Visualforce标记中,使用 {!URLFOR($Action.Contact.Edit, recordId)}
  • 在JavaScript中,使用 navigateToSObject(recordId)

有查看,创建,编辑等操作和功能。使用它们,而不是URL字符串。

Lightning-Visualforce(4)

学习目标

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

  • 列出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()。

Lightning-Visualforce(3)

学习目标

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

  • 描述在Salesforce Classic中运行的Visualforce页面与在Lightning Experience中运行的相同页面之间的三个区别。
  • 描述需要更新才能在Lightning Experience中工作的两种常见代码模式。
  • 在Lightning Experience中运行时,列出对Visualforce页面默认值的两个更改。

探索Visualforce应用程序容器

Lightning Experience中的Visualforce与Salesforce Classic中的Visualforce最大的区别在于它运行的环境。在Salesforce Classic中,Visualforce“拥有”页面,请求和环境。 Visualforce是应用程序容器。但是在Lightning Experience中,Visualforce在包含在较大的Lightning Experience容器内的iframe中运行。
这种对执行上下文的更改对Visualforce页面可能影响整个Salesforce应用程序的方式有很多影响。我们将在这个单元中讨论这些变化,但是为他们自己的单位保存其中一些的全部细节。

注意

这个单位比其他的“在建”还要多一点。原因很简单:这里描述的问题的影响高度依赖于你的代码。我们非常努力地为你们“做好工作”,而且在大多数情况下,这里一点或者什么都不会显示出来。但我们无法预测您使用Visualforce的各种方式。这里我们概述Lightning Experience如何影响Visualforce的一般方面。当你和我们交谈时,当我们从你那里得到更多关于实际影响的信息时,我们可以提供关于如何解决具体问题的更多细节的解释。

外部闪电体验容器

我们从外部容器Lightning Experience应用程序开始。 Lightning Experience容器是一个“单页面应用程序”或SPA,可以在/one/one.app URL访问。 one.app页面加载,代码启动,应用程序代码接管环境。
单页面应用程序加载资源的过程(通常是静态的HTML shell和大量的JavaScript)既有趣又复杂。如果你已经使用了AngularJS或者React之类的JavaScript框架,那么你就非常熟悉以one.app的形式启动Lightning Experience的基础知识。说实话,完整的细节并不重要。你没有任何控制权,并且实现继续发展。

以下是重要的知识:Lightning Experience或one.app负责请求。您的Visualforce页面不是。您的页面需要在Lightning Experience强加给它的限制内工作。 Lightning Experience是父级环境,而Visualforce页面是子级环境。孩子需要服从父母。

其中一些约束条件(例如显示Visualforce页面的框架的大小)直接由Lightning Experience强制实施。他们更容易理解和使用,我们将在一分钟内讨论他们。

其他限制是隐含的,并不是由Lightning Experience实施,而是由运行它的浏览器实施。这些主要是安全性和JavaScript执行的限制。大多数页面不受这些安全约束的影响,而且那些通常会提前失败并带有明确的错误消息。 JavaScript错误很难发现和诊断,但是有一些通用的规则,我们稍后会介绍。

Visualforce iframe

当您的Visualforce页面在Lightning Experience中运行时,它将显示在HTML iframe中。一个iframe创建一个嵌入式浏览上下文,这个浏览上下文实际上是一个独立于主Lightning Experience浏览上下文的浏览器“窗口”。 iframe在Visualforce页面与其父项Lightning Experience应用程序之间创建一个边界。
在iframe中运行Visualforce页面的优点是,对于不需要访问或更改顶级浏览上下文的页面,在iframe中运行看起来几乎与Salesforce Classic中的页面一样运行。这就是为什么您不需要修改所有Visualforce页面以适应Lightning Experience的大量不同的幕后请求环境。这是支持Visualforce的“正常工作”策略的重要组成部分。

当然,另一方面是那些需要访问顶层浏览上下文的页面,还有一些需要改变的地方。我们将在下一节介绍一些细节。

如果您的页面与Salesforce之外的服务进行通信,则iframe边界也可能导致您需要更新组织的CORS设置,远程站点设置,clickjack设置或内容安全策略。由于这些依赖于Salesforce之外的安全策略和设置,因此我们无法提供特定更改的配方。我们在这里简单地提醒你注意。

新容器的影响

新的Visualforce容器 – 将Lightforce页面嵌入到Lightning Experience应用程序的iframe中 – 可以大致分为两类,我们将其称为安全性和范围。
再一次,我们要强调的是:很多,甚至是大多数Visualforce页面都不会受到这些问题的影响。但是对于那些,我们认为“预先警告已经被警告”。如果我们已经一起讨论过,你会更快地发现问题的根源。

安全影响

可能受到影响的安全要素包括以下内容。

  • 会话维护和更新
  • 认证
  • 跨域请求
  • 嵌入限制

我们已经简要地讨论了一些这些涉及跨域请求的项目。也就是说,当完整的浏览器窗口中的内容来自对不同服务器和服务的请求时,这些请求中的任何一个都可能会妨碍在没有准备好的情况下显示。如果有需要,您的任务就是准备这些服务,以处理在闪电体验范围内的要求。正如我们之前所说,细节有所不同,所以我们不能在这里提供具体的答案。

有一件事我们特别要提的是会话维护。我们这里所说的“会话”基本上就是您的浏览器从请求到请求重用的一种令牌,这样您就不必为每个请求输入用户名和密码。您经常需要使用全局变量$ Api.Session_ID访问当前会话。

这是要记住的事情。 $ Api.Session_ID根据请求的域返回不同的值。这是因为每当您跨越主机名边界(例如.salesforce.com到.visual.force.com)时,会话ID都会在会话期间发生变化。通常情况下,Salesforce会透明地处理域之间的会话切换,但是如果您要传递会话ID,请注意,您可能需要从正确的域重新访问$ Api.Session_ID以确保有效的会话ID。

Lightning Experience和Visualforce页面不仅保存在不同的浏览器上下文中,而且也来自不同的域。因此,尽管它全部显示在一个浏览器窗口中,但Visualforce iframe中的会话ID将不同于iframe之外的会话ID,而在Lightning Experience的另一部分中。 Salesforce和Lightning Experience在正常使用情况下处理此透明。但是如果你在派对上通过会议ID(通常不是一个好主意),你可能需要回顾一下你是如何处理它的。

范围影响

当我们谈论范围时,我们主要谈论以下几种事情。

  • DOM访问和修改
  • JavaScript范围,可见性和访问
  • JavaScript全局变量,如window.location

如果这个列表听起来很复杂或者令人困惑,不要担心,我们可以把它归结为简单易记的东西:不要碰别人的东西。具体而言,您的JavaScript代码(以及样式表规则)可能会影响页面浏览上下文中的元素(DOM节点,JavaScript变量等),但无法访问任何其他浏览上下文中的元素,闪电体验上下文。不要触摸其他上下文的东西!

实际上,你想要做这种事情的最常见的代码模式是操纵window.location导航到另一个页面。这是一个很常见的事情,我们已经写了关于这个特定问题的细节……当你完成这个模块的时候,你会厌倦听到这个问题,我们保证。

最后一个音符如果您是一位经验丰富的JavaScript开发人员,您可能已经在考虑通过使用contentWindow,window.parent等来了解如何处理“我无权访问父浏览上下文”问题。请不要。你可能会遇到同源策略(Visualforce和Lightning Experience来自不同的领域,请记住?)。即使你不这样做,你可能会用明显的,间歇性的错误代替显而易见的错误。你想在哪里花时间:做正确的事情,还是调试?

做正确的事情意味着调用我们在Visualforce页面中提供的API,主要用于导航。如果您确实需要跨越边界影响事物,请使用window.postMessage将消息发送到另一个帧中的接收代码。

Visualforce默认值和闪电体验的环境变化

当您的Visualforce页面在Lightning Experience中运行时,幕后会发生许多低级别更改。这些改变使得大多数页面在Lightning Experience容器中“正常工作”,有时你可以为他们在那里感到高兴。但是你仍然想知道他们正在发生,特别是当你在处理高级应用程序流时,或者解决一个棘手的问题时。
其中一些变化很简单,一旦你想到它们,就很明显。例如,在Lightning Experience中运行的Visualforce页面始终具有标准的Salesforce Classic标题和侧栏。其他变化并不明显,但同样有很大的影响。

<apex:page> showHeader和侧栏属性始终为false

这些属性会影响Visualforce页面上的Salesforce Classic标题和侧边栏。在Lightning Experience中运行页面时,Salesforce Classic标题和侧栏会始终被抑制,以支持Lightning Experience导航元素。没有相应的属性可以影响Lightning Experience标题或侧边栏,因为它们不能被抑制。
如果您的页面在Salesforce Classic和Lightning Experience之间共享,则仍然可以将这些属性设置为在Salesforce Classic中运行页面时要使用的值。

注意

确定是否包含或取消标准Salesforce Classic样式表的<apex:page>的standardStylesheets属性不受Lightning Experience的影响。也就是说,在Lightning Experience中它默认为true,但是你可以改变它。

sforce.one JavaScript工具对象

尽管sforce.one听起来像一个在Salesforce cantina中工作的机器人,但它实际上是一个实用程序对象,它提供了许多有用的功能,您可以在自己的JavaScript代码中使用这些功能。
在Lightning Experience或Salesforce应用程序中运行时,sforce.one会自动注入您的页面。您将在您的JavaScript调试器控制台和Web开发人员资源列表中看到它。没有什么需要添加的,也没办法压制它。 (可惜的是,在Salesforce Classic的Visualforce页面中无法获取sforce.one。)

sforce.one主要用于消除导航事件。完整的细节在即将到来的单位,管理导航。

____________________

*没有Salesforce酒吧。唉。