访问组件的最佳实践ID

要在 JavaScript 或其他支持 Web 的语言中引用 Visualforce 组件,您必须 为该组件的属性指定一个值。DOM ID 由组合构成 的属性 组件和属性 包含该元素的所有组件。ididid

使用全局变量简化 引用为 Visualforce 组件生成的 DOM ID,并减少对整个页面的一些依赖 结构。$Component要引用特定 Visualforce 组件的 DOM ID,请使用点表示法将组件路径说明符添加到 在页面的组件层次结构中分隔每个级别。例如 用。以引用 Visualforce 组件层次结构中同一级别的组件,或使用 …以指定更完整的组件路径。$Component$ComponentitemId$ComponentgrandparentIdparentIditemId路径说明符 与组件层次结构匹配:

$Component

  • 在组件层次结构的当前级别,其中使用;然后$Component
  • 在组件层次结构中每个连续的更高级别,直到 找到匹配项,或达到组件层次结构的顶层。

没有回溯,所以如果你尝试的 ID 匹配需要向上遍历然后向下遍历,它不会 火柴。以下示例说明了 的几种用法:

$Component

<apex:page >

    <style>
    .clicker { border: 1px solid #999; cursor: pointer;
        margin: .5em; padding: 1em; width: 10em; text-align: center; }
    </style>

    <apex:form id="theForm">
        <apex:pageBlock id="thePageBlock" title="Targeting IDs with $Component">
            <apex:pageBlockSection id="theSection">
                <apex:pageBlockSectionItem id="theSectionItem">
                    All the alerts refer to this component.
                    
                    <p>The full DOM ID resembles something like this:<br/>
                    j_id0:theForm:thePageBlock:theSection:theSectionItem</p>
                </apex:pageBlockSectionItem>

                <!-- Works because this outputPanel has a parent in common 
                     with "theSectionItem" component -->
                <apex:outputPanel layout="block" styleClass="clicker"
                    onclick="alert('{!$Component.theSectionItem}');">
                    First click here
                </apex:outputPanel>
            </apex:pageBlockSection>
            
            <apex:pageBlockButtons id="theButtons" location="bottom">
                <!-- Works because this outputPanel has a grandparent ("theSection")
                     in common with "theSectionItem" -->
                <apex:outputPanel layout="block" styleClass="clicker"
                    onclick="alert('{!$Component.theSection.theSectionItem}');">
                    Second click here
                </apex:outputPanel>

                <!-- Works because this outputPanel has a distant ancestor ("theForm")
                     in common with "theSectionItem" -->
                <apex:outputPanel layout="block" styleClass="clicker"
                    onclick="alert('
                    {!$Component.theForm.thePageBlock.theSection.theSectionItem}');">
                    Third click here
                </apex:outputPanel>
            </apex:pageBlockButtons>

        </apex:pageBlock>

        <!-- Works because this outputPanel is a sibling to "thePageBlock",
             and specifies the complete ID path from that sibling -->
        <apex:outputPanel layout="block" styleClass="clicker"
            onclick="alert('{!$Component.thePageBlock.theSection.theSectionItem}');">
            Fourth click here
        </apex:outputPanel>
        
        <hr/>
        
        <!-- Won't work because this outputPanel doesn't provide a path 
             that includes a sibling or common ancestor -->
        <apex:outputPanel layout="block" styleClass="clicker"
            onclick="alert('{!$Component.theSection.theSectionItem}');">
            This won't work
        </apex:outputPanel>

        <!-- Won't work because this outputPanel doesn't provide a path 
             that includes a sibling or common ancestor -->
        <apex:outputPanel layout="block" styleClass="clicker"
            onclick="alert('{!$Component.theSectionItem}');">
            Won't work either
        </apex:outputPanel>

    </apex:form>
</apex:page>

使用唯一 ID

在页面的每个层次结构段中,组件必须是唯一的。但是,Salesforce 建议 您使用的唯一 在页面上,您需要引用的每个组件以及任何组件 在引用它所需的组件层次结构中。idid

例如,假设您在一个页面中有两个数据表。 如果两个数据表都包含在同一个页块中,则它们必须 具有独特的属性。 如果每个都包含在单独的页面块中,则有可能 给他们相同的组件.但是,如果这样做,则引用特定数据的唯一方法 table 是将一个 to 每个组件,然后使用 完整的层次结构,而不是让 Visualforce 自动完成。如果页面层次结构发生更改,则程序 将不再起作用。ididid

使用组件 ID 进行迭代

某些组件(如表和列表)支持迭代 超过记录集合。为这些类型分配 ID 后 的组件,系统分配一个唯一的“复合 ID” 根据初始 ID 对组件进行每次迭代。例如,以下页面包含一个 ID 设置为 。

theTable

<apex:page standardController="Account" recordSetVar="accounts" id="thePage">
    <apex:dataTable value="{!accounts}" var="account" id="theTable">
        <apex:column id="firstColumn">
            <apex:outputText value="{!account.name}"/>
        </apex:column>
        <apex:column id="secondColumn">
            <apex:outputText value="{!account.owner.name}"/>
        </apex:column>
    </apex:dataTable>
</apex:page>

呈现页面时,组件结果 在以下 HTML 中:

<apex:dataTable>

<table id="thePage:theTable" border="0" cellpadding="0" cellspacing="0">
<colgroup span="2"/>
<tbody>
    <tr class="">
        <td id="thePage:theTable:0:firstColumn">
            <span id="thePage:theTable:0:accountName">Burlington Textiles</span>
        </td>
        <td id="thePage:theTable:0:secondColumn">
            <span id="thePage:theTable:0:accountOwner">Vforce Developer</span>
        </td>
    </tr>
    <tr class="">
        <td id="thePage:theTable:1:firstColumn">
            <span id="thePage:theTable:1:accountName">Dickenson</span>
        </td>
        <td id="thePage:theTable:1:secondColumn">
            <span id="thePage:theTable:1:accountOwner">Vforce Developer</span>
        </td>
    </tr>
</table>

每个表格单元格都有一个唯一的 ID,该 ID 基于 包含组件的 ID 值。中的第一个表单元格 第一行有 ID,第一行的第二个单元格有 ID,第一行有 ID 第二行中的单元格具有 ID,依此类推。

thePage:theTable:0:firstColumnthePage:theTable:0:secondColumnthePage:theTable:1:firstColumn

要引用列中的所有条目,您有 遍历表行,引用 ID 如下的每个元素 列的格式。<td>

相同类型的 ID 生成是 对表格单元格中的元素执行。例如,帐户 第一行中的 name 生成为 ID 为 。请注意,ID 不包括列的 ID 值 它进来了。spanthePage:theTable:0:accountName

静态资源的最佳实践

显示属性为 的静态资源的内容action<apex:page>您可以 使用属性 在组件上 从 Visualforce 页面重定向到静态资源。此功能允许您添加 为您的 Visualforce 页面提供丰富的自定义帮助。例如,要将用户重定向到 PDF:

action<apex:page>

  1. 将 PDF 作为名为 customhelp 的静态资源上传。
  2. 创建以下页面:<apex:page sidebar="false" showHeader="false" standardStylesheets="false" action="{!URLFOR($Resource.customhelp)}"> </apex:page>

请注意,静态资源引用包装在函数中。如果没有这个, 页面未正确重定向。

URLFOR此重定向是 不限于 PDF 文件。您还可以将页面重定向到内容 的任何静态资源。例如,您可以创建静态资源 其中包括一个由许多混合的 HTML 文件组成的整个帮助系统 使用 JavaScript、图像和其他多媒体文件。只要有 是单个入口点,重定向有效。例如:

  1. 创建一个包含帮助内容的 zip 文件。
  2. 将 zip 文件作为名为 customhelpsystem 的静态资源上传。
  3. 创建以下页面:<apex:page sidebar="false" showHeader="false" standardStylesheets="false" action="{!URLFOR($Resource.customhelpsystem, 'index.htm')}"> </apex:page>

当用户访问该页面时,将显示静态资源中的 index.htm 文件。

控制器和控制器的最佳实践 扩展

在控制器中强制执行共享规则像其他 Apex 一样 类、自定义控制器和控制器扩展在系统中运行 模式。通常,您需要一个 控制器或控制器扩展,以尊重用户的 组织范围的默认值、角色层次结构和共享规则。你可以做 通过在类定义中使用关键字。with sharing有关信息,请参阅《使用 、 和关键字》中的“使用 Apex 开发人员 指南。with sharingwithout sharinginherited sharing

注意

如果 控制器扩展扩展了一个标准控制器,逻辑来自 标准控制器不在系统模式下执行。相反,它 在用户模式下执行,其中权限、字段级安全性和 当前用户的共享规则适用。控制器构造函数在 Setter 方法之前进行计算不要依赖于在构造函数之前计算的 setter 方法。为 例如,在以下组件中,组件的控制器依赖于 被召唤的 setter 在构造函数方法之前:selectedValue

<apex:component controller="CustCmpCtrl">
      <apex:attribute name="value" description="" 
                      type="String" required="true" 
                      assignTo="{!selectedValue}">
      </apex:attribute>
      //...
      //...
</apex:component>
public class CustCmpCtrl {
      
      // Constructor method
      public CustCmpCtrl() {
            if (selectedValue != null) {
                EditMode = true;
            }
      }
      
      private Boolean EditMode = false;

      // Setter method
      public String selectedValue { get;set; }
}

因为 构造函数在 setter 之前调用,调用构造函数时将始终为 null。 因此,永远不会设置为 真。selectedValueEditMode方法可以多次评估 – 不要使用副作用方法,包括控制器中的方法、操作属性和表达式, 可以多次调用。不要依赖于评估顺序或副作用 在控制器或控制器扩展中创建自定义方法时。

使用组件分面的最佳实践

分面由区域中的内容组成 提供有关数据的上下文信息的 Visualforce 组件 这在组件中呈现。例如,支持分面 用于表格的页眉、页脚和标题,而仅支持 facet 用于列的页眉和页脚。该组件允许 您可以使用自己的内容覆盖 Visualforce 组件上的默认分面。分面只允许一个子项 在开始和结束标记中。

<apex:dataTable><apex:column><apex:facet>

注意

并非所有组件 支持分面。这些列在标准组件参考中。

定义 时,它始终用作另一个 Visualforce 组件的子组件。属性 在 Facet 上确定覆盖父组件的哪个区域。<apex:facet>name

示例:将 Facet 与 <apex:dataTable> 一起使用

以下标记显示了组件如何 修改为:

<apex:dataTable><apex:facet>

<apex:page standardController="Account">
    <apex:pageBlock>
        <apex:dataTable value="{!account}" var="a">
            <apex:facet name="caption"><h1>This is 
              {!account.name}</h1></apex:facet>
            <apex:facet name="footer"><p>Information 
              Accurate as of {!NOW()}</p></apex:facet>
            <apex:column>
                <apex:facet name="header">Name</apex:facet>
                <apex:outputText value="{!a.name}"/>
            </apex:column>
            
            <apex:column>
                <apex:facet 
              name="header">Owner</apex:facet>
                <apex:outputText value="{!a.owner.name}"/>
            </apex:column>
        </apex:dataTable>
    </apex:pageBlock>        
</apex:page>

注意

要使此页面显示帐户数据,ID 必须在 页面的 URL。例如:

https://Salesforce_instance/apex/facet?id=001D000000IRosz

页面显示如下:

使用 Facet 扩展 <apex:dataTable>分面示例

将 Facet 与 <apex:actionStatus 一起使用>

另一个可以使用分面的组件是 。组件 可以扩展为在刷新页面时显示指示器。 例如,您可以使用以下标记定义进度轮:

<apex:actionStatus><apex:actionStatus>

<apex:page controller="exampleCon">
    <apex:form >
        <apex:outputText value="Watch this counter: {!count}" id="counter"/>
        <apex:actionStatus id="counterStatus">
            <apex:facet name="start">
                 <img src="{!$Resource.spin}"/> <!-- A previously defined image -->
            </apex:facet>
        </apex:actionStatus>    
        <apex:actionPoller action="{!incrementCounter}" rerender="counter"
            status="counterStatus" interval="7"/>
    </apex:form>
</apex:page>

关联的控制器更新 计数器:

public class exampleCon {
    Integer count = 0;
                        
    public PageReference incrementCounter() {
            count++;
            return null;
    }
                        
    public Integer getCount() {
        return count;
    }
}

页面显示如下:

使用 Facet 扩展 <apex:actionStatus>

页面块组件的最佳实践

将两个以上的子组件添加到<apex:pageBlockSectionItem>一个组件最多只能有两个子组件。不过,有时候, 您想要添加一个额外的子组件。例如,您可能需要 在之前添加星号,并且仍然显示关联的输入文本字段。你可以这样做 通过将星号和输出标签包装在组件中, 如下:<apex:pageBlockSectionItem><apex:outputLabel><apex:outputPanel>

注意

要使此页面显示帐户数据,ID 必须在 页面的 URL。例如:

https://Salesforce_instance/apex/myPage?id=001D000000IRosz
<!-- Page: -->
<apex:page standardController="Account">
    <apex:form >
        <apex:pageBlock title="My Content" mode="edit">
            <apex:pageBlockSection title="My Content Section" columns="2">
                <apex:pageBlockSectionItem >
                    <apex:outputPanel>
                        <apex:outputText>*</apex:outputText>
                        <apex:outputLabel value="Account Name" for="account__name"/>
                    </apex:outputPanel>
                    <apex:inputText value="{!account.name}" id="account__name"/> 
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

渲染 PDF 文件的最佳实践

将 Visualforce 页面呈现为 PDF 文件是共享有关 Salesforce 组织信息的好方法。这里有一些最好的 供您考虑的做法。

为了在渲染 Visualforce 页面时获得更好的性能,请参考静态图像和 通过 $Resource 全局的样式表资源 变量。

警告

在远程服务器上引用静态资源会增加所需的时间 将 Visualforce 页面呈现为 PDF 文件。将远程服务器添加到允许的远程站点列表:在“设置”中,输入“快速查找”框,然后选择“远程站点设置”。使用时无法引用远程资源 Visualforce 以 顶点触发器。这样做会导致异常。Remote Sites Settings

<apex:panelbar> 的最佳实践

将子组件的集合添加到组件<apex:panelBarItem><apex:panelBar>一个组件只能有子组件。但是,有时您想要添加一个集合 的子组件。例如,您可能希望为 与一个帐户关联的每个联系人。您可以通过包装组件来执行此操作,如下所示:<apex:panelBar><apex:panelBarItem><apex:panelBarItem><apex:repeat>

注意

要使此页面显示帐户数据,有效帐户的 ID 记录必须指定为页面 URL 中的查询参数。 例如:https:// Salesforce_instance/apex/myPage?id=001D000000IRosz

<apex:page standardController="account">
  <apex:panelBar >
    <apex:repeat value="{!account.contacts}" var="c">
      <apex:panelBarItem label="{!c.firstname}">one</apex:panelBarItem>
    </apex:repeat>
  </apex:panelBar>
</apex:page>

文档排版约定

Apex 和 Visualforce 文档使用以下排版约定。

公约描述
Courier font在语法说明中,等宽字体表示应 如图所示键入,括号除外。例如:Public class HelloWorld
Italics在语法描述中,斜体表示变量。您提供 实际值。在以下示例中,需要提供三个值:datatype variable_name [ = value];如果语法为粗体和斜体,则文本 表示需要您提供的值的代码元素,例如 类名或变量 价值:public static class YourClassHere { ... }
Bold Courier font在代码示例和语法描述中,粗体 courier 字体强调 部分代码或语法。
< >在语法描述中,小于和大于符号 (< >) 是 键入的内容与所示完全相同。<apex:pageBlockTable value="{!account.Contacts}" var="contact"> <apex:column value="{!contact.Name}"/> <apex:column value="{!contact.MailingCity}"/> <apex:column value="{!contact.Phone}"/> </apex:pageBlockTable>
{ }在语法说明中,大括号 ({ }) 的键入方式与所示完全相同。<apex:page> Hello {!$User.FirstName}! </apex:page>
[ ]在语法描述中,括号中包含的任何内容都是可选的。在 以下示例中,指定 自选: valuedata_type variable_name [ = value];
|在语法描述中,竖线符号的意思是“或”。您可以 执行下列操作之一(不是全部)。在以下示例中,您可以创建一个 通过以下两种方式之一新建未填充的集,也可以填充集:Set<data_type> set_name [= new Set<data_type>();] | [= new Set<data_type{value [, value2. . .] };] | ;

Visualforce最佳实践

了解改进 Visualforce 页面的策略。

注意

在可能的情况下,我们更改了非包容性条款,以符合我们公司的平等价值观。 我们保留了某些条款,以避免对客户实施产生任何影响。

  • 提高 Visualforce 性能的最佳实践 了解提高 Visualforce 页面性能
    的策略。
  • 访问组件 ID 的最佳实践
  • 静态资源的最佳实践
  • 控制器和控制器扩展的最佳实践
  • 使用组件分面的最佳实践
  • 页面块组件的最佳实践
  • 渲染 PDF 文件的
    最佳实践 将 Visualforce 页面呈现为 PDF 文件是共享有关 Salesforce 组织信息的好方法。以下是一些可供您考虑的最佳做法。
  • <apex:panelbar> 最佳实践
  • 文档排版约定

提高 Visualforce 性能的最佳实践

学习提高性能的策略 视觉力 页面。

注意

在可能的情况下,我们更改了非包容性条款,以符合我们公司的平等价值观。 我们保留了某些条款,以避免对客户实施产生任何影响。

  • 调查性能问题
    Visualforce 旨在为开发人员提供与标准 Salesforce 页面的功能、行为和性能相匹配的能力。如果您的用户遇到延迟、意外行为或专门针对 Visualforce 的其他问题,请首先调查问题的可能来源。
  • 遵循 Visualforce 设计准则
    要优化 Visualforce 页面性能,请设计以任务为中心的页面,使用标准对象和声明性功能,并展平组件层次结构。
  • 控制数据大小
    Visualforce 页面的标准响应限制为 15 MB,较小的页面加载速度比较大的页面快。若要最大程度地缩短加载时间,请限制每个页面显示的数据量。
  • 缓存经常访问的数据 缓存任何经常访问的数据
    ,例如图标图形,并在自定义设置中缓存全局数据。
  • 延迟加载页面组件
    若要减少或延迟成本高昂的计算,请使用延迟加载。使用延迟加载时,页面首先加载其基本组件,并延迟其他功能,直到用户执行需要信息的操作。这种技术使用户能够更快地访问基本功能,并使大页面看起来更具响应性,即使整个页面的加载总时间相同。
  • 处理多个并发请求 并发请求
    是长时间运行的任务,可以阻止其他待处理的任务。若要减少延迟,请尽可能将代码移动到异步代码块,并确保使用该组件的操作方法是轻量级的。<apex:actionPoller>
  • 编写高效的 Apex 和 SOQL 要提高 Visualforce 页面的整体性能,请编写高效的 Apex 和 SOQL
  • 编写高效的 Getter 方法 Visualforce 请求评估表达式、操作属性和其他方法
    调用。表单提交等请求可以多次调用类中的 getter 方法。使用更高效的 getter 方法,可以防止对同一记录进行不必要的查找。
  • 优化列表和表格 要提高包含列表和表格
    的 Visualforce 页面的性能,请限制每页显示的数据量,并减少每个表格的可编辑字段数。您还可以实现分页或将组件替换为静态 HTML 表。<apex:pageBlockTable>
  • 优化视图状态
    为了维护 Visualforce 页面的视图状态,Lightning Platform 将组件、字段值和控制器的状态作为加密字符串存储在隐藏的表单元素中。视图状态的限制为 170 KB。大型视图状态需要更长的处理时间,包括序列化和反序列化时间,以及加密和解密时间。如果减小视图状态大小,则页面加载速度更快,停顿频率更低。
  • 优化 HTML 在 Visualforce 验证 HTML 的服务器端,优化的 HTML
    提高了处理效率。在客户端,优化的 HTML 使 Visualforce 页面在用户浏览器中的响应速度更快。
  • 优化 CSS 为确保高效交付给客户端,请在 Visualforce 页面中优化 CSS
    。优化的 CSS 还改进了缓存并减少了加载时间。
  • 优化 JavaScript 为确保高效交付给客户端,请在 Visualforce 页面中优化 JavaScript 优化的 JavaScript
    还改进了缓存并缩短了加载时间。
  • 优化图像 图像
    通常是网页中最大的组成部分,因此它们会显著影响 Visualforce 页面的性能。
  • 防止字段从页面上
    掉落 包含许多字段的 Visualforce 页面(尤其是那些具有大型文本区域字段或与其他实体具有主从关系的页面)可能无法显示请求的每个字段。由于批处理限制和对返回的数据大小的限制,可能会删除数据。为防止字段从页面上删除,请减少显示的字段数。或者,创建一个控制器扩展,该扩展可以查询子记录并在相关列表中显示结果。
  • 使用设置了属性的即时属性 Visualforce
    组件来执行操作,而无需处理页面上关联字段的任何验证规则。仅当组件执行在完成后导航离开页面的操作时,才应使用此属性。immediatetrue
  • Visualforce 性能案例研究
    要了解 Visualforce 性能优化如何协同工作,请研究如何减少具有大型数据网格和复杂对象层次结构的页面的加载时间。

调查性能问题

Visualforce 旨在为开发人员提供匹配 标准 Salesforce 页面的功能、行为和性能。如果您的用户体验 延迟、意外行为或专门针对 Visualforce 的其他问题,请首先进行调查 问题的可能根源。

验证您的网页是否遵循网页设计的一般最佳实践,例如:

  • JavaScript 和 CSS 缩小。
  • 网络图像优化。
  • 尽可能避免使用 iframe。

通过测试调查 Visualforce 性能

为确保问题不仅限于单个用户的计算机,请测试您的 Visualforce 页面 在多台机器上,使用多个浏览器。检查其他Salesforce的加载时间 页面和其他网站。如果 Salesforce 页面加载缓慢,请检查 Salesforce 服务器位于 https://status.salesforce.com。如果所有网页加载缓慢,请检查您的网络 配置。

测试并帮助阻止性能 回归:

  • 使用 Lightning 平台开发人员控制台调查 页面上的 Visualforce 标记和其他 Lightning Platform 功能。与开发人员 控制台中,您可以查看消耗系统资源的内容并识别代码中的问题。这 开发人员控制台有一个调试日志,其中详细说明了请求作为服务器的性能 处理它们。详细信息显示方法、查询、工作流、 标注、DML、验证、触发器和页面(按类型和时间)。
  • 使用 Selenium 等工具自动测试繁琐或复杂的工作流程,这些工作流程可以 产生不一致的结果。自动化测试可以点击链接、输入和检索数据、 并记录执行时间,以发现手动测试可以发现的瓶颈和缺陷 思念。
  • 在尽可能多的浏览器和尽可能多的版本中进行测试。
  • 使用大量数据进行测试。这些测试可以揭示数据偏斜的场景,其中某些 用户有权访问过多的记录。避免无界数据,实现分页,以及 仅显示相关数据。
  • 使用 HTML、CSS 和 JavaScript 分析和调试工具提供对网络的洞察 延迟、加载时间和代码效率。
  • 在真实的移动设备上进行测试,以发现开发人员不明显的性能问题 机器。由于处理器速度较慢,移动客户端具有不同的性能配置文件, 内存有限,网络连接速度较慢。

提示

将 WebPageTest 等工具用于初始移动浏览器 测试,但使用真实设备进行深入测试。

遵循 Visualforce 设计准则

要优化 Visualforce 页面性能,请设计以任务为中心的页面,请使用标准对象 和声明性功能,并扁平化组件层次结构。

设计以任务为中心的页面

围绕特定任务设计页面,具有逻辑工作流和清晰的导航 任务之间。不要用功能和数据使页面过载。Visualforce 页面 具有无界数据或大量组件、行和字段的差 可用性和性能。它们可能会达到视图状态和堆的调控器限制 大小,并且它们可能会超过记录检索限制和页面大小限制。推 回到包含非必要功能和构建原型的请求 验证性能问题。

尽可能使用标准功能

Lightning Platform 的编程功能使其易于自定义 功能性。但标准对象和声明性功能(如审批) 流程、流程和工作流规则 – 已经高度优化,但尚未优化 计入大多数调控器限制。标准功能简化了数据模型,并且经常 减少业务流程所需的 Visualforce 页面数量。

扁平化组件层次结构

扁平组件结构的处理速度比深层分层组件快 结构。限制自定义组件的嵌套以进行逻辑组织 功能,并且仅当该逻辑旨在重用时才使用自定义组件 或包含在另一个包中。庞大的层次结构增强了服务器端管理 和处理时间,因为 Visualforce 在整个过程中维护上下文 请求。组件层次结构的每次遍历都会消耗时间和资源。巨大 层次结构还使页面面临达到堆大小调控器限制的风险。

控制数据大小

Visualforce 页面的标准响应限制为 15 MB,较小的页面加载速度更快 比较大的页面。若要最大程度地减少加载时间,请限制每个页面的数据量 显示。

筛选查询结果

  • 使用筛选器限制 Salesforce 对象查询语言 (SOQL) 调用的数据,以及 Apex 控制器返回。例如,在子句中使用语句。还可以删除 null 查询结果。ANDWHERE
  • 创建 Apex 控制器时,请使用关键字仅检索用户可以访问的记录。with sharing
  • 首先在 SOQL 中过滤,然后在 Apex 中过滤,最后在 Visualforce 中过滤。

使用分页

  • 具有无限数据的页面可能会导致加载时间延长,达到调控器限制,并成为 随着数据集的增长而不可用。若要防止列表视图显示未绑定的数据,请执行以下操作: 使用列表控制器实现分页。默认情况下,列表控制器返回 20 每页记录数,但您可以将列表视图配置为一次最多显示 100 条记录。 要控制每个页面显示的记录数,请使用控制器扩展将 .pageSize
  • 使用 SOQL 子句编写逻辑 分页到 SOQL 中结果的特定子集。OFFSET

缓存经常访问的数据

缓存任何经常访问的数据,例如图标图形,并缓存全局数据 在自定义设置中。

Visualforce 页面有时会全局使用计算结果。页面使用相同的数据 跨用户和请求。要提高使用全局数据的页面的性能,请缓存 在自定义设置中计算结果,并定期刷新结果,而不是在 每个请求。自定义设置是应用程序缓存的一部分,不需要数据库 查询检索。在此方法与更新自定义缓存所需的时间之间取得平衡 数据。

延迟加载页面组件

若要减少或延迟成本高昂的计算,请使用延迟加载。使用延迟加载,页面 首先加载其基本组件,并延迟其他功能,直到用户执行 需要信息的操作。这种技术使用户能够更快地访问基本 功能,并使大页面看起来更具响应性,即使整个页面采用 加载的总时间相同。

要延迟加载 Visualforce 页面的某些部分,请使用以下技术:

  • 使用 Visualforce 组件上的 rerender 属性更新 组件,而不更新整个页面。
  • 使用 JavaScript 远程处理 调用控制器中的函数并检索辅助或静态数据。
  • 创建自定义组件以根据用户操作显示和隐藏数据。

延迟加载页面时,请考虑用户数和预期的数据量 页面。请注意并发 API 调用限制等限制。例如,如果 导航树仅根据需要加载元素,查询数量可能最终超出 与数据的比例。

处理多个并发请求

并发请求是长时间运行的任务,可以阻止其他待处理的任务。减少 延迟,尽可能将代码移动到异步代码块,并确保操作方法 使用组件是 轻。

<apex:actionPoller>

编写异步代码

若要卸载成本高昂的处理,请使用异步 JavaScript 和 XML (Ajax) 移动 异步代码块的非必要逻辑。例如,对于仅使用 同步代码,用户单击按钮并等待长时间运行的任务完成 在他们看到确认消息之前。相反,如果页面将长时间运行的 任务进行异步处理,则控件会立即返回给用户。您可以 将页面配置为在任务完成时通知用户。

保持 <apex:actionPoller> 轻量级

该组件是一个计时器,它 发出 Ajax 请求。使用该组件的页面在服务器上发出连续请求。如果 用户长时间打开页面或在同一页面上打开多个窗口 页面(例如,获取多个帐户的详细信息)会降低性能。<apex:actionPoller><apex:actionPoller>

为避免性能下降,请避免执行 DML、外部服务调用等 调用的操作方法中的资源密集型操作。仔细考虑重复调用的动作方法的效果 间隔。当该方法用于广泛分布或 持续活跃的页面。<apex:actionPoller><apex:actionPoller>

为避免达到调控器限制,请增加 Ajax 请求之间的时间间隔。组件的 interval 属性 以秒为单位测量 Ajax 更新请求之间的时间间隔。此值必须为 5 秒或更大,如果未指定,则默认为 60 秒。<apex:actionPoller>

该组件适用于 不需要昂贵处理的页面,但适用于计算需要的页面 更多的服务器时间,请考虑将组件与 JavaScript 一起使用 远程处理 相反。这种替代方法需要更多的代码,但提供了更大的灵活性和 效率。<apex:actionPoller><apex:actionFunction>

写入高效的 Apex 和 SOQL

自 提高整体性能 一个 视觉力 页面,写入效率高 Apex 和 SOQL。

当您编写 Apex 或 SOQL 以在 Visualforce 页面中使用时:

  • 尽可能在 SOQL 中执行计算,而不是在 Apex 中执行计算。
  • 切勿在循环中执行数据操作语言 (DML) 操作。
  • 首先在 SOQL 中过滤,然后在 Apex 中过滤,最后在 Visualforce 中过滤。

编写高效的 getter 方法

Visualforce 请求评估表达式、操作属性和其他方法调用。一个 表单提交等请求可以多次调用类中的 getter 方法。跟 更有效的 getter 方法,您可以防止不必要的查找 记录。

若要减少每个请求的处理负载,请缓存属性计算的值,以便 其他调用可以在不重新计算值的情况下访问该属性。

您还可以在 Apex 类中配置 getter 方法,使其仅在 object 为 null。例如,以下代码片段返回关联的客户记录 与页面。在第一次方法调用时,该方法查询记录,因为 MyAccount 对象为 null。在后续调用中,它将返回对象的存储值,该值 防止其他相同的查询:SELECT


 Account MyAccount;
 public Account getMyAccount() {
    if (MyAccount == null) {
            MyAccount = [SELECT name, annualRevenue FROM Account
            WHERE
            id = :ApexPages.currentPage().getParameters().
            get('id')];
        }
    return MyAccount;
 }

优化列表和表格

要提高包含列表和表格的 Visualforce 页面的性能,请限制数量 每页显示的数据,并减少每个表的可编辑字段数。您还可以 实现分页或将组件替换为静态 HTML 表。

<apex:pageBlockTable>

如果可能,避免使用数据网格

数据网格是显示具有可编辑字段的记录的表。数据网格频繁 扩展到页面上的数千个输入组件,并超过最大视图状态大小。 大型数据网格会导致 Visualforce 组件树的处理速度较慢。

如果您的 Visualforce 页面有数据网格:

  • 使用分页和过滤器。
  • 若要减小视图状态大小,请尽可能将数据设置为只读。
  • 仅显示给定记录的基本数据。提供指向基于 Ajax 的详细信息的链接 框或到单独的详细信息页面。

考虑静态 HTML 表格

具有迭代组件的 Visualforce 页面最多可以包含 1,000 个项目,或者当 页面以只读模式执行。但是,页面性能有时会在之前下降 如果包含具有 Render 属性的组件,则此限制 明确指定。<apex:pageBlockTable><apex:pageBlockTable><apex:column>

如果您的 Visualforce 页面包含大型表格,我们建议您实现分页。 或者,您可以使用静态 HTML 表而不是组件。在 HTML 表中,使用组件循环访问 HTML 行 元素。有关使用 的示例 HTML 表,请参阅 apex:repeat 组件 参考页面。<apex:pageBlockTable><apex:repeat><apex:repeat>

注意

与表格不同,静态 HTML 表没有标准的 Salesforce 样式。<apex:pageBlockTable>

优化视图状态

为了维护 Visualforce 页面的视图状态,Lightning Platform 会存储 组件、字段值和控制器作为隐藏表单元素中的加密字符串。 视图状态的限制为 170 KB。大型视图状态需要更长的处理时间 每个请求,包括序列化和反序列化时间,以及加密和解密 时间。如果减小视图状态大小,则页面加载速度更快,停滞更少 经常。

要检查 Visualforce 页面的视图状态,请设置“开发模式”和“显示视图状态” 在开发模式下的用户权限。开发模式页脚中的“视图状态”选项卡 显示视图状态的分布。确保您知道每个视图的状态大小 页面,并使用大量数据进行测试,以防止部署后可能出现的问题。

要减少视图状态,请执行以下操作:

  • 使用筛选器和分页来减少需要状态的数据。
  • 使用关键字声明实例变量 如果该变量仅对当前请求有用。不包括瞬态变量 在视图状态中。transient
  • 优化 SOQL 调用,以仅返回与 Visualforce 页面相关的数据。
  • 减少页面所依赖的组件数量。
  • 将数据设置为只读。使用组件而不是组件。<apex:outputText><apex:inputField>
  • 使用 JavaScript 远程处理。与组件不同,JavaScript 远程处理不需要组件。JavaScript 远程处理不会 降低页面的整体视图状态,但您的页面通常性能更好,而没有 需要传输、序列化和反序列化视图状态。权衡是损失 reRender 属性和其他 JavaScript 代码的必要性 处理回调。<apex:actionFunction><apex:form>

优化 HTML

在服务器上 Visualforce 验证 HTML 的一侧, 优化 HTML 改进了处理 效率。 在客户端,优化的 HTML 使 Visualforce 页面在用户的 浏览器。

要优化 Visualforce 中的 HTML 页:

  • 查看 Visualforce 组件生成的 HTML。Visualforce 页面更正无效 编译期间的 HTML,这可能会导致 这 HTML 以意外的方式呈现。例如,如果您的标签内有一个 or 标签,则 Visualforce 页面会在 运行时间。<head><body><apex:page>
  • 查看 Ajax 代码。 在阿贾克斯期间 请求,以确保响应正确地适合 DOM的, 服务器验证并更正入站 HTML。 处理时间 减少 如果 您的 Visualforce 页面包含有效的标记,以及是否需要更正。
  • 减少 HTML 膨胀。虽然浏览器缓存 HTML 和 编译的 Visualforce 标记,从缓存中检索它们会影响性能。必要 HTML 还增加了组件树的大小和 Ajax 的处理时间 请求。

优化 CSS

为确保 高效交付给客户, 优化 Visualforce 中的 CSS 页。 优化的 CSS 还改进了缓存并减少了 负荷 次。

改进 CSS 一个 Visualforce 页面:

  • 外部化样式表。从 Visualforce 页面中删除内联 CSS 代码,并将其放在 单独的 CSS 文件。这种做法会增加初始 HTTP 请求的数量,但会减少 单个页面的大小。浏览器缓存样式表后,整体请求 尺寸减小。
  • 将所有 CSS 文件合并到一个文件中,从而减少 HTTP 请求的数量。
  • 删除注释和多余的空格。压缩生成的文件以加快速度 下载。
  • 使用静态资源提供 CSS 文件。以这种方式提供的样式表受益于 缓存和 Salesforce 中内置的内容分发网络 (CDN)。
  • 对于不使用 Salesforce CSS 文件的页面,请将标记的 showHeaders 和 standardStylesheets 属性设置为 .这种做法排除了标准 生成的页眉中的 Salesforce CSS 文件。<apex:page>false

优化 JavaScript的

为确保 高效交付给客户, 优化 Visualforce 中的 JavaScript 页面 优化的 JavaScript 还改进了缓存和 减少 加载时间。

为了改进 JavaScript的 在 一个 Visualforce 页面:

  • 外部化 JavaScript 文件。此过程会增加初始 HTTP 请求的数量, 但它也减小了单个页面的大小并利用了浏览器 缓存。
  • 仅使用您需要的函数构建 JavaScript 库的自定义版本。这 进程显著减小了 JavaScript 文件的大小。许多开源 JavaScript 库(如 jQuery)提供此选项。
  • 减少 HTTP 请求者 结合 将所有 JavaScript 文件合并为一个 文件。 删除可能导致多个 HTTP 的重复函数 请求。
  • 删除注释和空格。压缩生成的文件以加快下载速度。
  • 使用静态资源提供 JavaScript 文件。JavaScript 以这种方式提供服务 好处 来自 Salesforce 内置的缓存和内容分发网络 (CDN)。
  • 将脚本放在页面底部。如果脚本直接在结束标记之前加载,则页面可以下载其他组件 首先,逐步呈现页面。</body>注意仅将 JavaScript 移动到页面底部 如果你确定 那 它没有任何不良影响 影响。 例如,不要从元素中移动需要 或 事件处理程序的 JavaScript 代码片段。document.write<head>
  • 而不是 使用标签, 考虑 直接在之前使用标准 HTML 标记 您的结束标签。 标记将 JavaScript 置于正确的位置 在关闭元素之前,这会导致 浏览器尝试加载 JavaScript,然后再在 页。<apex:includeScript><script></apex:page><apex:includeScript></head>

优化图像

图像通常是网页中最大的组成部分,因此它们会显着影响 Visualforce 页面的性能。

要最大程度地减少映像对性能的影响,请执行以下操作:

  • 使用更少的图像和较小的背景纹理。
  • 尽可能使用 CSS 而不是图像。
  • 使用 CSS 精灵而不是单个图像。使用 CSS 精灵,您可以将 将大小相似的图形(如按钮和图标)集合到单个文件中。然后 您可以使用 CSS background-image 和 background-position 属性来显示组合的部分 图像。由于此技术减少了使用的图像数量,因此 HTTP 请求的数量 发送量也减少。缓存一个 Sprite 文件也比缓存多个 Sprite 文件更有效 图像。
  • 使用静态资源提供图像。以这种方式提供的图像受益于缓存和 Salesforce 中内置的内容分发网络 (CDN)。
  • 压缩图像。图形工具通常使用默认设置,这些设置有利于视觉保真度 保存图像时压缩和添加元数据。图像压缩工具可以减少 在不降低视觉质量的情况下,图像的文件大小最多可减少 30%。

提示

要改进开发工作流程,请添加压缩图像的脚本 资产。

防止字段从页面上删除

具有许多字段的 Visualforce 页面,尤其是具有大型文本区域字段的页面 或 通过与其他实体的主从关系,可以 失败 以显示请求的每个字段。由于批处理限制和限制,数据可能会被删除 数据大小 返回。 为防止字段从页面上删除,请减少显示的字段数。 或者 创建一个 控制器扩展,可以查询子记录并在相关中显示结果 列表。

注意

在可能的情况下,我们更改了非包容性条款,以符合我们公司的平等价值观。 我们保留了某些条款,以避免对客户实施产生任何影响。

谨慎使用 immediate 属性

具有属性的 Visualforce 组件 设置为在不处理任何操作的情况下执行操作 页面上关联字段的验证规则。仅当以下情况时,才应使用此属性 该组件执行一个操作,该操作在完成后导航离开页面。

immediatetrue

当组件行为包含基本导航以外的内容时,会出现功能问题 功能性。因为不会更新 页面的数据模型,页面的数据模型不会反映在操作期间所做的任何更改。 这种差异可能会导致未定义的行为和可能的数据损坏。immediate=”true”

建议仅在取消时使用 immediate 属性 行动。下面的示例演示此属性的正确用法。当用户 单击“取消”,组件将立即执行,无需用户修复 验证错误。<apex:CommandLink>cancelApplication

<apex:CommandLink action="{!cancelApplication}" value="Cancel" styleClass="btn" id="btnCancel" immediate="true">

Visualforce 性能案例研究

要了解 Visualforce 性能优化如何协同工作,请查看以下方法 减少具有大型数据网格和复杂对象的页面的加载时间 等级制度。

想象一下,您有一个带有数据网格的 Visualforce 页面,用于收集销售预测。这 预测的数据模型包含多级对象层次结构。该页面还包含 用于显示透视数据的计算。对于普通用户,网格包含大约 1,500 个 单元格,这会导致页面加载缓慢并达到堆和视图状态限制。

要优化页面性能,您可以:

  • 使页面有针对性且以任务为中心。对输入和聚合使用同一页面 报告增加了不必要的复杂性。
  • 创建一个自定义对象来保存用于报告的聚合数据。删除所需的公式 显示聚合数据可减小堆大小。
  • 避免在单个页面上显示每个帐户。实现分页,从而改进页面 加载速度并减小视图状态的大小。
  • 将数据网格单元格设置为只读。让用户选择要编辑的单元格,然后使用 Ajax 保存用户的编辑。这些做法减小了视图状态的大小。

使用 Lightning 消息服务跨 DOM 进行通信

使用 Lightning 消息服务在 Lightning 页面内的 DOM 之间进行通信。 在同一 Lightning 页面中嵌入的 Visualforce 页面、Aura 组件和 Lightning Web 组件,包括实用程序栏中的组件和弹出式实用程序。选择 组件是订阅来自整个应用程序的消息,还是仅订阅来自活动应用程序的消息 面积。

如果您要从 Salesforce Classic 切换到 Lightning Experience,您现在可以构建 可以与现有 Visualforce 页面或 Aura 组件进行通信的 Lightning Web 组件。 您还可以使用 Lightning 消息服务通过 Open CTI 与软件电话进行通信。

重要

Lightning 消息服务在 Lightning Experience 中提供,并作为 Experience Builder 站点中使用的 Lightning 组件的测试版功能。

要在 Visualforce 中访问 Lightning 消息服务,请使用全局变量。消息是可序列化的 JSON 对象。示例 可以在消息中传递的数据包括字符串、数字、对象和布尔值。一条消息 不能包含函数和符号。全局变量仅在 Lightning Experience 中可用。$MessageChannel$MessageChannel

使用在组织内创建的消息通道

以下是使用在您的组织内开发的 Lightning 消息通道的示例。

<apex:page>
    <script>
        // Load the MessageChannel token in a variable
        var SAMPLEMC = "{!$MessageChannel.SampleMessageChannel__c}";
    </script>
</apex:page>

在这里,我们引用一个带有公式表达式的自定义消息通道。此表达式 创建一个我们分配给变量的令牌。 此令牌对于您的自定义消息通道是唯一的,可以在 Lightning 消息中使用 服务 API 方法。语法引用元数据类型的自定义实例。后缀表示它是自定义的, 但请注意,它不是自定义对象。有关详细信息,请参阅创建消息通道。{!$MessageChannel.SampleMessageChannel__c}SAMPLEMCSampleMessageChannel__cLightningMessageChannel__c

如果您的组织具有命名空间,请不要将其包含在消息通道表达式中。例如 如果组织的命名空间是 MyNamespace,则消息通道表达式仍为 。“{!$MessageChannel.SampleMessageChannel__c}”

使用在组织外部创建的消息通道

要使用来自组织外部的开发人员创建的包的消息通道,请参考 他们用语法。例如,如果不是您的组织的本地用户,并且来自 命名空间为 SamplePackageNamespace 的包,语法为 .{!$MessageChannel.Namespace_name__c}SampleMessageChannel{$MessageChannel.SamplePackageNamespace__SampleMessageChannel__c}

  • 创建消息通道
    要在组织中创建 Lightning 消息通道,请使用 LightningMessageChannel 元数据类型。
  • 在消息通道
    上发布 要从 Visualforce 页面在消息通道上发布,请在页面的 JavaScript 代码中包含全局变量,并编写调用 .$MessageChannelsforce.one.publish()
  • 订阅和取消订阅消息通道 若要订阅和取消订阅消息通道
    ,请使用 和 方法。sforce.one.subscribe()sforce.one.unsubscribe()
  • 注意事项和限制
    在 Visualforce 中使用 Lightning 消息服务时,请牢记这些注意事项和限制。

创建消息通道

要在您的组织中创建 Lightning 消息通道,请使用 LightningMessageChannel 元数据类型。

注意

请参阅《元数据 API 开发人员指南》中的 LightningMessageChannel(可以是 在发布预览期间已过时或不可用)。

要将 LightningMessageChannel 部署到您的组织中,请创建一个 SFDX 项目。包括 XML force-app/main/default/messageChannels/ 目录中的定义。这 LightningMessageChannel 文件名遵循 .messageChannel-meta.xml 格式。要将其添加到临时组织,请运行 。要将其添加到其他类型的组织, 例如沙盒或 Developer Edition 组织,请运行 .messageChannelNamesfdx force:source:pushsfdx force:source:deploy

在消息通道上发布

要从 Visualforce 页面在消息通道上发布,请在页面的 JavaScript 代码中包含全局变量 并编写一个调用 .

$MessageChannelsforce.one.publish()

github.com/trailheadapps/lwc-recipes 存储库中的页面演示如何发布 选择联系人时在 Lightning 页面上通知订阅者的消息。

lmsPublisherVisualforce

以下示例演练了 Visualforce 页面标记,以演示如何发布到 单击按钮时的消息通道。

在页面的 JavaScript 中,我们首先获取对自定义 Lightning 消息通道的引用 使用公式表达式 。此表达式创建一个令牌,该令牌 对于您的消息频道是唯一的。然后,我们将令牌作为字符串分配给变量 。{!$MessageChannel.SampleMessageChannel__c}SAMPLEMC

该函数包含以下消息 我们想要发布的内容。在这里,消息是值为 “some string” 和 ,其值是键值对值 value: “some value”。然后我们调用 Lightning 消息服务 API 的方法 对象。该函数采用两个参数,一个包含消息通道的字符串 令牌和消息负载。handleClick()recordIdrecordDatapublish()sforce.onepublish()

在页面标记中,我们创建一个按钮并调用它的方法。handleClick()onclick()

<apex:page >
    <script>
    // Load the MessageChannel token in a variable
    var SAMPLEMC = "{!$MessageChannel.SampleMessageChannel__c}";
    function handleClick() {
        const payload = {
            recordId: "some string",
            recordData: {value: "some value"}
        }
        sforce.one.publish(SAMPLEMC, payload);
      }
    </script>
    <div>
    <p>Publish SampleMessageChannel</p>
    <button onclick="handleClick()">Publish</button>
    </div>
</apex:page>

订阅和取消订阅消息通道

若要订阅和取消订阅消息通道,请使用 和 方法。

sforce.one.subscribe()sforce.one.unsubscribe()

github.com/trailheadapps/lwc-recipes 存储库中的页面显示如何订阅和取消订阅消息通道。

lmsSubscriberVisualforceRemoting

以下示例是“在消息通道上发布”中示例的延续,该示例允许您订阅和取消订阅 从消息通道中单击相应的按钮。在 JavaScript 中,我们 具有 and 方法,并使用消息输出填充subscribeMC()unsubscribeMC()onMCPublished()textarea

将自定义消息通道加载到变量 中。全局变量为 关联的消息通道。在 下 , 我们 声明 保存从该方法返回的 Subscription 对象。$MessageChannel.SampleMessageChannel__cSAMPLEMC$MessageChannelSAMPLEMCsubscriptionToMCsforce.one.subscribe()

该方法检查 订阅对象为空。如果是,则调用该方法。 有两个参数, 订阅消息通道,以及处理消息输出的方法。subscribeMC()sforce.one.subscribe()sforce.one.subscribe()onMCPublished()默认情况下,通过消息通道的通信只能在 活动导航选项卡、活动导航项或实用程序项。效用 项目始终处于活动状态。导航选项卡或项目在选中时处于活动状态。 导航选项卡和项目包括:

  • 标准导航选项卡
  • 控制台导航工作区选项卡
  • 控制台导航子选项卡
  • 控制台导航项

若要从应用程序中的任何位置接收消息通道上的消息,请使用该方法的可选 第四个参数 .将 设置为值。

sforce.one.subscribe()subscriberOptionsscopesubscriberOptions“APPLICATION”

sforce.one.subscribe(messageChannel, listener, {scope: "APPLICATION"});

该方法检查是否 有一个订阅对象。如果是这样,它将调用 并传入对象。然后,它清除对象。unsubscribeMC()sforce.one.unsubscribe()subscriptionToMCsubscriptionToMC

该方法将 消息有效负载从 JSON 对象转换为字符串。然后,它会在 ID 中显示消息。onMCPublished()textareaMCMessageTextArea

<apex:page >
    <div>
        <p>Subscribe to SampleMessageChannel </p>
        <button onclick="subscribeMC()">Subscribe</button>
        <p>Unsubscribe from subscription</p>
        <button onclick="unsubscribeMC()">Unsubscribe</button>
        <br/>
        <br/>
        <p>Received message:</p>
    <textarea id="MCMessageTextArea" rows="10" style="disabled:true;resize:none;width:100%;"/>
    </div>

    <script>
        // Load the MessageChannel token in a variable
        var SAMPLEMC = "{!$MessageChannel.SampleMessageChannel__c}";
        var subscriptionToMC;

        function onMCPublished(message) {
            var textArea = document.querySelector("#MCMessageTextArea");
            textArea.innerHTML = message ? JSON.stringify(message, null, '\t') : 'no message payload';
        }

        function subscribeMC() {
            if (!subscriptionToMC) {
                subscriptionToMC = sforce.one.subscribe(SAMPLEMC, onMCPublished);
            }
        }

        function unsubscribeMC() {
            if (subscriptionToMC) {
                sforce.one.unsubscribe(subscriptionToMC);
                subscriptionToMC = null;
            }
        }
    </script>

</apex:page>

注意事项和限制

在使用 Lightning 消息时,请牢记这些注意事项和限制 Visualforce 中的服务。考虑闪电消息服务不适用于 在 Chatter 中加载页面时的 Visualforce 库 使用 .请改用本机 Lightning Publisher。sforce.one<chatter:feed showPublisher=”true”/>Lightning 消息服务在 Lightning 中包含的 Visualforce 页面中不起作用 通过 iframe(包括 、 和标准 HTML 标记)进行体验。相反,请通过 Lightning 应用程序生成器或作为实用程序栏项目。<wave:dashboard><apex:iframe><iframe>Visualforce 仅支持 Lightning 消息通道,其中为 true。有关 LightningMessageChannel 的更多信息,请参阅元数据 API 开发人员指南。isExposed闪电消息服务在 Salesforce Classic 中或预览 Visualforce 时不起作用 从设置。局限性闪电消息服务仅支持这些 经验。

  • Lightning Experience 标准导航
  • Lightning Experience 控制台导航
  • 适用于 Aura 和 Lightning Web 组件的 Salesforce 移动应用程序,但不适用于 Visualforce 页面
  • Experience Builder 站点中使用的 Lightning 组件。对 Experience Builder 的支持 网站是测试版。注意闪电消息服务 不 在 Experience 中使用 Salesforce 选项卡 + Visualforce 站点或 Visualforce 页面 建设者网站。

Visualforce 远程对象

JavaScript 远程处理是一种常用、功能强大且高效的 Web 应用程序构建方法 使用 Visualforce,特别是用于创建用于 Salesforce 移动应用程序或工作的页面 使用 JavaScript 库,例如 jQuery 或 AngularJS。Visualforce 远程 对象是启用 直接从 JavaScript 对 sObject 执行基本 DML 操作。远程 对象消除了一些复杂性 通过减少对 Apex 控制器或扩展中方法的需求,从 JavaScript 远程处理。

@RemoteAction

幕后,远程 对象控制器处理共享规则、字段级安全性和其他数据可访问性 关注。使用远程的页面 对象受所有标准 Visualforce 限制的约束,但与 JavaScript 远程处理、远程处理一样 对象调用不计算在内 达到 API 请求限制。使用 Visualforce Remote 对象包括在同一页面上实现两个单独的功能。

  1. 访问定义,使用 Visualforce 和 Remote 编写 对象组件。这些组件会生成一组 JavaScript 代理对象,您可以 在步骤 2 中使用。
  2. 数据访问函数,用 JavaScript 编写。这些函数使用代理对象 由访问定义提供,以执行创建、检索、更新和 对数据执行删除操作。

然后,您的页面使用数据访问函数来响应用户交互,例如 表单提交或控制更改,或执行定期操作以响应计时器,或 你可以用 JavaScript 编写的大多数内容。

远程的简单示例 对象

这个简短的示例演示了您需要实现的两个功能 使用远程对象。此 Visualforce 页面检索一个 列出 10 条仓库记录,并在页面上显示它们以响应用户 单击〖检索仓库〗按钮。

<apex:page>
    
    <!-- Remote Objects definition to set accessible sObjects and fields -->
    <apex:remoteObjects >
        <apex:remoteObjectModel name="Warehouse__c" jsShorthand="Warehouse" 
            fields="Name,Id">
            <apex:remoteObjectField name="Phone__c" jsShorthand="Phone"/>
        </apex:remoteObjectModel>
    </apex:remoteObjects>

    <!-- JavaScript to make Remote Objects calls -->
    <script>
        var fetchWarehouses = function(){
            // Create a new Remote Object
            var wh = new SObjectModel.Warehouse();
            
            // Use the Remote Object to query for 10 warehouse records
            wh.retrieve({ limit: 10 }, function(err, records, event){
                if(err) {
                    alert(err.message);
                }
                else {
                    var ul = document.getElementById("warehousesList");
                    records.forEach(function(record) {
                        // Build the text for a warehouse line item
                        var whText = record.get("Name");
                        whText += " -- ";
                        whText += record.get("Phone");
                        
                        // Add the line item to the warehouses list
                        var li = document.createElement("li");
                        li.appendChild(document.createTextNode(whText));
                        ul.appendChild(li);
                    });
                }
            });
        };
    </script>

    <h1>Retrieve Warehouses via Remote Objects</h1>

    <p>Warehouses:</p>

    <ul id="warehousesList">
    </ul>
    <button onclick="fetchWarehouses()">Retrieve Warehouses</button>

</apex:page>

通知 此页面有些不寻常 – 没有控制器或控制器扩展。 所有数据访问均由远程处理 对象组件。此示例的第一部分是远程 对象组件,用于指定要在 页。

<apex:remoteObjects >
    <apex:remoteObjectModel name="Warehouse__c" jsShorthand="Warehouse" fields="Name,Id">
        <apex:remoteObjectField name="Phone__c" jsShorthand="Phone"/>
    </apex:remoteObjectModel>
</apex:remoteObjects>

这些 组件生成 JavaScript 模型类,访问中的每个 sObject 一个 规范,用于直接从 JavaScript 进行数据访问调用 法典。请注意该属性的使用,该属性将完整的 Salesforce API 名称映射到 在 JavaScript 代码中使用的更简单、更短的名称。如果您打算打包和 分发代码,设置为 必不可少,因为它消除了在 打包的代码。使用速记可以完成所有工作。

jsShorthandjsShorthand此示例的第二部分是一个 JavaScript 函数,该函数使用以下模型 由访问定义组件生成,用于检索一组要显示的记录 在 页。

<!-- JavaScript to make Remote Objects calls -->
<script>
    var fetchWarehouses = function(){
        // Create a new Remote Object
        var wh = new SObjectModel.Warehouse();
        
        // Use the Remote Object to query for 10 warehouse records
        wh.retrieve({ limit: 10 }, function(err, records, event){
            if(err) {
                alert(err.message);
            }
            else {
                var ul = document.getElementById("warehousesList");
                records.forEach(function(record) {
                    // Build the text for a warehouse line item
                    var whText = record.get("Name");
                    whText += " -- ";
                    whText += record.get("Phone");
                    
                    // Add the line item to the warehouses list
                    var li = document.createElement("li");
                    li.appendChild(document.createTextNode(whText));
                    ul.appendChild(li);
                });
            }
        });
    };
</script>

这 函数的第一行从模型创建一个 Warehouse 对象。请注意, 创建它的调用使用 for sObject 而不是对象的完整 API 名称。遵循此最佳实践 将 JavaScript 代码与组织命名空间的细节分离, sObject 和字段名称等,并使代码更加简洁明了。

jsShorthand

第二行使用新的 Warehouse 对象 来执行对 Warehouse 记录的查询。该调用提供两个 参数:一个简单的查询说明符和一个用于处理结果的匿名函数。这 函数是标准的 JavaScript。它循环访问结果并创建列表项以 追加到页面上的仓库列表。wh页面正文是静态的 HTML。

<h1>Retrieve Warehouses via Remote Objects</h1>

<p>Warehouses:</p>

<ul id="warehousesList">
</ul>
<button onclick="fetchWarehouses()">Retrieve Warehouses</button>

你 代码将结果添加到列表中。 加载页面时,列表为空。单击该按钮将触发 JavaScript 之前定义的函数,用于执行查询并添加结果。

warehousesList

使用远程 JavaScript 中的对象

远程生成的 JavaScript 模型 对象组件提供了一个 JavaScript API,用于为应用创建读取和保存的函数 值返回给 Salesforce。使用组件创建的基本模型进行实例化 相应 sObject 的特定模型。然后使用特定模型执行操作 在他们的 sObject 上,例如检索、创建、更新和删除。

<apex:remoteObjects>远程的基本型号 对象由组件创建。基本模型为 Remote 提供了一个伪命名空间 使用它创建的对象。默认情况下,基本模型是 命名的,但您可以使用该属性来设置名称。使用不同的底座 对相关远程进行分组的模型 沿功能线或包装线的对象。为 例:

<apex:remoteObjects>SObjectModeljsNamespace

<apex:remoteObjects jsNamespace="MyCorpModels">
    <apex:remoteObjectModel name="Contact" fields="FirstName,LastName"/>
</apex:remoteObjects>
<apex:remoteObjects jsNamespace="TrackerModels">
    <apex:remoteObjectModel name="Shipment__c" fields="Id,TrackNum__c"/>
</apex:remoteObjects>

特定型号

您通常不会自己创建基础模型,而是使用生成的 基础模型作为创建特定模型的工厂。例如,使用上面的 声明,在 JavaScript 中实例化一个 Contact 模型,例如 这:

var ct = new MyCorpModels.Contact();

注意 这是一个 JavaScript 模型 联系人对象,而不是特定的联系人记录。

ct

ct表示一个特定的对象,并提供 页面的 JavaScript 和 Salesforce 服务。 可用于执行基本的“CRUD” 对 Contact 对象执行的操作(创建、读取、更新和删除)在 数据库。Contactct在以下各节中,示例基于以下远程 对象声明,它使用所有三个 Remote 对象组件,并演示如何添加自定义字段、Notes__c、 带有“简写”名称,以便在 JavaScript 中访问它 自然的。

<apex:remoteObjects jsNamespace="RemoteObjectModel">
    <apex:remoteObjectModel name="Contact" fields="Id,FirstName,LastName,Phone">
        <apex:remoteObjectField name="Notes__c" jsShorthand="Notes"/>
    </apex:remoteObjectModel>
</apex:remoteObjects>

这 声明使您能够访问联系人记录上的五个字段。

实例化模型和访问字段

实例化设置了或未设置字段值的模型,具体取决于您的意图。 通常,当您想要将更改写入数据库时,您将设置字段 并在您只是阅读时省略字段。字段值是通过传入来设置的 一个 JSON 字符串,其中包含要在新模型上设置的字段的值。若要创建未设置字段的模型,请使用空参数创建该模型 列表。

var ct = new RemoteObjectModel.Contact();

要实例化设置了字段的模型,通常要创建新记录,请传入 包含字段名称和值对的对象。为 例:

var ct = new RemoteObjectModel.Contact({ 
    FirstName: "Aldo",
    LastName: "Michaels", 
    Phone: "(415) 555-1212"
});

远程 对象模型使用基本 和 方法来检索和设置字段值。 为 例:

get()set()

var ct = new RemoteObjectModel.Contact({ FirstName: "Aldo" });
ct.get('FirstName');  // 'Aldo'
ct.get('Phone'); // <undefined>
ct.set('FirstName', 'Benedict');
ct.set('Phone', '(415) 555-1212');

有 使用属性列表中的属性列表设置字段值之间没有功能差异 构造函数并使用 设置字段值。

set()

使用 Remote 创建记录 对象

通过调用 Remote 创建记录 对象模型实例。

create()

create()接受两个 参数,两者都是可选的。

RemoteObjectModel.create({field_values}, callback_function)

块 使您能够在一个语句中定义和创建记录。设置字段 值,就像使用 JSON 字符串创建模型时一样。为 例如,以下两个调用是等效的。

field_valuescreate()

var ctDetails = { FirstName: 'Marc', LastName: 'Benioff' };

// Call create() on an existing Contact model, with no arguments
var ct = new RemoteObjectModel.Contact(ctDetails);
ct.create();

// Call create() on an empty Contact model, passing in field values
var ct = new RemoteObjectModel.Contact();
ct.create(ctDetails);

create()不 直接返回结果。回调函数使您能够处理 异步服务器响应。

注意

所有服务器操作 使用远程 对象是异步执行的。任何依赖于 正在完成的请求(包括处理返回的结果)必须 放置在回调函数中。你的回调函数 最多可以接受三个参数。

function callback(Error error, Array results, Object event) { // ... }

查看远程 对象回调函数,了解有关编写 Remote 的详细信息 对象回调函数。该字段已设置为 远程对象作为成功调用的一部分。您可以访问此 字段。

Idcreate()

var ctDetails = { FirstName: 'Marc', LastName: 'Benioff' };
var ct = new RemoteObjectModel.Contact();
ct.create(ctDetails, function(err) {
    if(err) { 
        console.log(err);
        alert(err.message);
    }
    else {
        // this is the contact
        console.log(ct.log());     // Dump contact to log
        console.log(ct.get('Id')); // Id is set when create completes
    }
});

注意该函数的使用;它相当于 Remote 对象。

log()toString()

使用远程检索记录 对象

通过调用远程来检索记录 对象模型实例。

retrieve()

retrieve()需要两个参数,一个用于查询 条件,一个用于回调 处理器。

RemoteObjectModel.retrieve({criteria}, callback_function)

criteria可以是远程对象查询对象或返回对象的函数。以下两个调用是 等效。

var ct = new RemoteObjectModel();

// Empty callback functions for simplicity
ct.retrieve({where: {FirstName: {eq: 'Marc' }}}, function() {}); // query object

ct.retrieve(function(){
	return({where: {FirstName: {eq: 'Marc' }}});
}, function() {}); // function returning query object

请参阅远程的格式和选项 对象查询条件,用于查询对象的说明。

retrieve()不返回结果 径直。回调函数使您能够处理服务器响应 异步。

注意

所有服务器操作 使用远程 对象是异步执行的。任何依赖于 正在完成的请求(包括处理返回的结果)必须 放置在回调函数中。你的回调函数 最多可以接受三个参数。

function callback(Error error, Array results, Object event) { // ... }

查看远程 对象回调函数,了解有关编写 Remote 的详细信息 对象回调函数。

若要使用日期检索记录,请将 JavaScript date 对象传入查询。

var myDate = new Date('2017-01-20');
ct.retrieve({where: {CloseDate: {eq: myDate}}}, function() {});

使用远程更新记录 对象

通过调用远程来更新记录 对象模型实例。

update()

update()接受三个 参数,都是可选的,并且可以在 同时,具体取决于您提供的参数。

RemoteObjectModel.update([record_ids], {field_values}, callback_function)

record_ids是一个字符串数组,其中字符串是 要更新的记录数。如果这 参数,则在 使用远程对象实例。更新记录的最简单方法是调用 本身。

IdIdupdate()

ctDetails = {FirstName: "Marc", LastName: "Benioff"};
ct = new RemoteObjectModel.Contact(ctDetails);
ct.create();

// Later, in response to a page event...
ct.set('Phone', '555-1212');
ct.update();

更常见的是,您可能需要更新记录以响应表单提交。更新 记录可以像读取一些表单值一样简单,包括记录的 ,并将这些值传递给 。为 例:

Idupdate()

var record = new RemoteObjectModel.Contact();
record.update($j('#contactId').val(),
{
    FirstName: $j('#fName').val(),
    LastName: $j('#lName').val(),
    Phone: $j('#phone').val(),
    Notes: $j('#notes').val() 
});

可靠的代码包括用于处理错误的回调。以下 代码的完成方式与上一个示例相同,但已更改为使用 事件处理程序和回调函数。

// Handle the Save button
function updateContact(e){
    e.preventDefault();

    var record = new RemoteObjectModel.Contact({
        Id: $jQuery('#contactId').val(),
        FirstName: $jQuery('#fName').val(),
        LastName: $jQuery('#lName').val(),
        Phone: $jQuery('#phone').val(),
        Notes: $jQuery('#notes').val() 
    });
    record.update(updateCallback);
}

// Callback to handle DML Remote Objects calls
function updateCallback(err, ids){
    if (err) { 
        displayError(err); 
    } else {
        // Reload the contacts with current list
        getAllContacts();
        $jQuery.mobile.changePage('#listpage', {changeHash: true});
    }
}

您可以同时更新多条记录,只要更新 要执行的是统一的,即每条记录都相同。为 例如,您可能需要从 列表,将状态字段更改为“已存档”或 当前时间戳。若要更新一个请求中的记录,请传递数组 的 s 到 .要更新的字段 可以设置为远程对象模型本身的一部分,但它是 将它们直接传递给 更安全,如下所示:

Idupdate()update()

var ct = new RemoteObjectModel.Contact();
ct.update(
    ['003xxxxxxxxxxxxxxx', '003xxxxxxxxxxxxxxx'], 
    { FirstName: "George", LastName: "Foreman" },
    function(err, ids) {
        if (err) { 
            displayError(err); 
        } else {
            // Reload the contacts with current list
            getAllContacts();
            $jQuery('#status').html(ids.length + ' record(s) updated.');
            $jQuery.mobile.changePage('#listpage', {changeHash: true});
        }
});

注意

当您以这种方式更新多条记录时,所有 的记录在同一服务器端事务中更新。

使用远程更新插入记录 对象

通过调用远程来保存记录 对象模型实例。

upsert()

upsert()是一种便利 函数,如果记录存在,则更新记录,如果不存在,则创建记录。 在幕后委托给 或 .用于为 不受记录影响的页面或应用程序 来自新的输入表单或编辑记录页面。upsert()create()update()upsert()

upsert()接受两个参数, 两者都是可选的。

RemoteObjectModel.upsert({field_values}, callback_function)

块 使您能够设置值并将记录保存在一个语句中。 像创建模型时一样,使用 JSON 字符串设置字段值。 例如,以下两个调用是等效的。

field_valuesupsert()

// Call upsert() on a Contact model, with no arguments
// ct is a RemoteObjectModel.Contact that already has data
ct.set('Phone', '(415) 777-1212');
ct.upsert();

// Call upsert() on a Contact model, passing in field values
// ct is a RemoteObjectModel.Contact that already has data
ct.upsert({Phone: '(415) 777-1212'});

在前面的示例中,不清楚联系人是否 存在于数据库中,或者如果它是新联系人,则 来自输入表单。 处理细节。如果在联系人上设置了字段,则 联系方式将更新。如果没有,则创建一个新联系人。upsert()IdId

upsert()不 直接返回结果。回调函数使您能够处理 异步服务器响应。

注意

所有服务器操作 使用远程 对象是异步执行的。任何依赖于 正在完成的请求(包括处理返回的结果)必须 放置在回调函数中。你的回调函数 最多可以接受三个参数。

function callback(Error error, Array results, Object event) { // ... }

查看远程 对象回调函数,了解有关编写 Remote 的详细信息 对象回调函数。

使用 Remote 删除记录 对象

通过调用远程删除记录 对象模型实例。

del()

del()接受两个参数, 两者都是可选的,并且可以删除一条或多条记录,具体取决于 您提供的参数。

注意

为什么代替 ? 是保留字 在 JavaScript 中。del()delete()delete

RemoteObjectModel.del([record_ids], callback_function)

record_ids是一个字符串数组,其中字符串是 要删除的记录的 S。如果这 参数,则在 使用远程对象实例。删除记录的最简单方法是调用 本身。

IdIddel()

ctDetails = {FirstName: "Tobe", LastName: "Ornottobe"};
ct = new RemoteObjectModel.Contact(ctDetails);
ct.create();

// After some thought, and the async operation completes...
// It's not to be; delete the contact
ct.del();

更常见的是,您可能需要删除记录以响应 按钮单击。删除记录就像从页面中获取记录然后传递一样简单 的 到 .例如:

IdIddel()

var id = $jQuery('#contactId').val();
var ct = new RemoteObjectModel.Contact();
ct.del(id);

可靠的代码包括用于处理错误的回调。以下 代码的完成方式与上一个示例相同,但已更改为使用 事件处理程序和回调函数。

// Handle the delete button click
function deleteContact(e){
    e.preventDefault();
    var ct = new RemoteObjectModel.Contact();
    ct.del($jQuery('#contactId').val(), updateCallback);
}

// Callback to handle DML Remote Objects calls
function updateCallback(err, ids){
    if (err) { 
        displayError(err); 
    } else {
        // Reload the contacts with current list
        getAllContacts();
        $jQuery.mobile.changePage('#listpage', {changeHash: true});
    }
}

要删除一个请求中的多条记录,例如,选中 列表中的项目 – 将 s 数组传递给 。

Iddel()

var ct = new RemoteObjectModel.Contact();
ct.del(['003xxxxxxxxxxxxxxx', '003xxxxxxxxxxxxxxx'], function(err, ids) {
    if (err) { 
        displayError(err); 
    } else {
        // Reload the contacts with current list
        getAllContacts();
        $jQuery('#status').html(ids.length + ' record(s) deleted.');
        $jQuery.mobile.changePage('#listpage', {changeHash: true});
    }
});

注意

当您以这种方式删除多条记录时,所有 的记录将在同一服务器端事务中删除。

远程的格式和选项 对象查询条件

远程 对象使用对象来指定操作条件。使用此对象 指定查询的位置、限制和偏移条件。

retrieve()查询对象的结构化格式使 Visualforce 能够验证 标准,减少运行时错误的可能性。格式为 简单。

<apex:remoteObjectsjsNamespace="RemoteObjectModel">    
    <apex:remoteObjectModel name="Contact" fields="FirstName,LastName"/>  
</apex:remoteObjects>

<script>
var ct = new RemoteObjectModel.Contact();
ct.retrieve( 
    { where: { 
        FirstName: {eq: 'Marc'}, 
        LastName: {eq: 'Benioff'} 
      }, 
      orderby: [ {LastName: 'ASC'}, {FirstName: 'ASC'} ],
      limit: 1 },  

    function(err, records) { 
        if (err) { 
            alert(err); 
        } else { 
            console.log(records.length); 
            console.log(records[0]); 
        } 
    } 
);
</script>

这 查询条件:找到名为 Marc Benioff 的联系人,并将查询限制为单个 结果。

哪里条件

where条件使您能够筛选 检索操作的结果,与 SOQL 查询中的条件大致相同。可用的运算符 条件是:

WHEREwhere

  • eq:等于
  • ne:不等于
  • lt:小于
  • lte:小于或等于
  • gt:大于
  • gte:大于或等于
  • like:字符串匹配。与 SOQL,使用“%”作为通配符。
  • in:in,用于查找与一组 固定值。提供 值作为数组,例如 [‘Benioff’, ‘Jobs’, ‘盖茨’]。
  • nin:not in,用于查找与 一组固定值。提供 值作为数组,例如 [‘Benioff’, ‘Jobs’, ‘盖茨’]。
  • and:逻辑 AND,用于 组合条件
  • or:逻辑 OR,用于组合 条件

在对象中,添加字段名称和 条件对以创建复杂的条件。默认情况下,多个条件是 视为 AND 条件。您可以使用 和 创建其他条件条件。 为 例:

whereandor

{ 
where: 
    { 
    or: 
        {
        FirstName: { like: "M%" }, 
        Phone: { like: '(415)%' }
        }
    }
}

使用 和 的组合根据日期范围筛选结果。例如:ltegte

<apex:remoteObjects jsNamespace="RemoteObjectModel">
    <apex:remoteObjectModel name="Account" fields="Id,Name,CreatedDate"/>
</apex:remoteObjects>

<script>
    var account_created_from_date = new Date('2017-01-01');
    var account_created_to_date = new Date('2018-01-01');
    var clauses = {       
        'where': {
            'CreatedDate': { 'lte': account_created_to_date },
            'and': {
                'CreatedDate': { 'gte': account_created_from_date },
                'Id': { 'ne': '' }
            }
        }   
    };

    var ct = new RemoteObjectModel.Account();
    ct.retrieve(
        clauses,
        function(err, records) { 
            if (err) { 
                console.log(err); 
            } else { 
                console.log(records.length); 
                console.log(records[0]); 
            } 
        } 
    );
</script>

订购者条件

orderby使您能够 为结果设置排序顺序。您最多可以对三个字段进行排序。指定条件 作为包含名称/值对的 JavaScript 对象数组。这 要排序的字段是名称,排序说明是值。 排序说明使您能够按升序或降序排序,并 对 NULL 值进行排序。例如:

orderby

orderby: [ {Phone: "DESC NULLS LAST"} , {FirstName: "ASC"} ]

限制和偏移条件

limit并使您能够检索特定的 一次记录数,并翻阅一组扩展的 结果。offset

用于 指定在一批结果中要返回的记录数。默认 值为 20。最大值为 100。limit

用于指定要跳过的记录数 在将记录添加到返回结果之前的总体结果集。最小值为 1。 最大偏移量为 2,000 行。请求 大于 2,000 的偏移量会导致错误。offsetNUMBER_OUTSIDE_VALID_RANGE

远程 对象回调函数

远程 对象以异步方式将所有请求发送到 Salesforce 服务。代码处理响应 到远程 您提供的回调函数中的对象操作。回调函数处理更新 包含操作结果和返回的错误的页面。

回调函数是标准 JavaScript 中用于处理事件和异步操作的技术。远程 对象使用此模式来处理其异步操作的响应。当你 调用远程 对象操作,您可以提供操作的参数和回调(可选) 功能。调用操作后,JavaScript 代码将继续不间断地运行。 当远程操作完成并返回结果时,回调函数 被调用并接收操作的结果。远程 可以编写对象回调函数以接收最多三个参数。

function callback(Error error, Array results, Object event) { // ... }
名字类型描述
错误JavaScript Error 对象标准 JavaScript Error 对象。如果操作成功,则为 。用于检索 失败的原因。errornullerror.message
结果JavaScript 数组一个数组,包含操作的结果。如果 操作是一个, 结果是相应 Remote 的实例 对象。否则,数组包含表示受影响 记录。retrieve()Id
事件JavaScript 对象一个 JavaScript 对象,该对象提供传输 Remote 的 JavaScript 远程处理事件的详细信息 对象操作。

大多数回调函数会检查错误,然后使用 结果。通常使用该对象 仅在调试和复杂的错误管理方面。

event

下面是一个简单的回调函数,用于处理操作的结果。

retrieve()

function getAllContacts() {
    $j.mobile.showPageLoadingMsg();
    
    var c = new RemoteObjectModel.Contact();
    c.retrieve({ limit: 100 }, function (err, records) { 
        // Handle errors
        if (err) { 
            displayError(err); 
        } else {
            // Add the results to the page
            var list = $j(Config.Selectors.list).empty();
            $j.each(records, function() {
                var newLink = $j('<a>'+this.get('FirstName')+' '+this.get('LastName')+'</a>');
                newLink.appendTo(list).wrap('<li></li>');
            });
            
            $j.mobile.hidePageLoadingMsg();
            list.listview('refresh');
        }
    });
}

在 此示例调用匿名函数并将其传递为 回调。回调函数检查错误,然后使用 jQuery 循环访问结果记录数组,并将它们添加到页面中。一些细节 省略以专注于回调结构。请参阅使用远程的示例 带有 jQuery Mobile 的对象,用于获取完整的页面源代码。

getAllContacts()retrieve()

覆盖默认远程对象操作

覆盖默认的远程 使用您自己的 Apex 代码进行对象操作,以扩展或自定义 Remote 的行为 对象。

Remote 幕后花絮 对象、基本操作—, , ,和 —使用 Remote 对象控制器,相当于普通 Visualforce 页面的标准控制器。您可以 覆盖远程 用于扩展或替换此控制器的内置行为的对象操作。重写 远程的 对象操作是用 Apex 编写的,并通过将它们添加到页面的 Remote 中来生效 对象定义。create()retrieve()update()del()

注意

无法重写该操作。 它只是一个便利功能,在幕后它委托给 either 或 .重写其中任一方法时,重写的方法 被自动用作 适当。upsert()create()update()upsert()

远程 方法重写的对象访问定义

覆盖远程使用远程方法对对象进行操作,将操作的属性设置为方法 替换默认方法。例如,下面介绍如何使用远程覆盖联系人的操作 方法。

create()

<apex:remoteObjectModel name="Contact" fields="FirstName,LastName,Phone" 
    create="{!$RemoteAction.RemoteObjectContactOverride.create}"/>

该属性采用 视觉力 引用要用作内置操作重写的方法的表达式。@RemoteActioncreate()这 表达式采用 的形式,为 全局处理您的位置 组织命名空间,就像 JavaScript 远程处理一样。请注意, 包含该方法的类 需要设置为页面的控制器或控制器扩展 页。$RemoteAction.OverrideClassName.overrideMethodName$RemoteAction@RemoteAction

使用此声明,每当页面的 JavaScript 代码调用联系人远程对象的函数时, 而不是使用远程对象控制器,将调用您的远程方法。create()

远程 对象重写方法

远程 对象重写方法作为 Apex 类中的方法编写,您可以将其作为 控制器或控制器扩展。@RemoteAction重写的方法签名 方法 是:

@RemoteAction
public static Map<String,Object> methodName(String type, Map<String,Object> fields)

type 参数是正在执行操作的 sObject 类型, 字段映射是包含值的集合 在重写方法之前在远程对象上设置的 叫。

返回值是表示 Remote 结果的映射 对象操作。此映射通常包括调用结果、状态和 要作为自定义方法的一部分提供的任何自定义数据。这 构造有效返回映射的最简单方法是使用 .这是标准 为远程提供内置功能的控制器 对象,您可以通过以下方式将数据操作语言 (DML) 操作委托给它 传递方法的参数。例如,这里有一个方法,它只执行 的内置版本:

RemoteObjectControllercreate()create()

@RemoteAction
public static Map<String, Object> create(String type, Map<String, Object> fields) {
    Map<String, Object> result = RemoteObjectController.create(type, fields);
    return result;
}

这 方法实际上是一个空操作;也就是说,此方法执行完全相同的操作 内置版本本来可以做到的,不多也不少。您的覆盖方法 可以执行您需要的任何其他 Apex,包括日志记录、额外的 DML、 其他方法调用,依此类推。有关远程的更完整示例 对象重写方法以及使用它的页面,请参阅在远程中使用远程方法重写的示例 对象。

重要

标准控制器自动处理 共享 Remote 的规则、所有权和其他安全问题 对象。相比之下,自定义控制器或控制器扩展中的方法在 系统模式,允许对组织中的所有数据进行完全访问。 此行为与使用 自定义控制器或控制器扩展。当您编写控制器代码时,您 需要自己处理访问权限和其他问题。RemoteObjectController

最佳做法是使用 你的关键词 控制器或控制器扩展类,并尽可能多地委托给 .with sharingRemoteObjectController

在 Remote 中使用 Remote 方法重写的示例 对象

此示例代码演示如何为 Remote 创建远程方法重写 对象操作。该示例提供了一个排序的联系人列表和一个用于输入 新联系人。新的联系人操作将覆盖内置的远程 对象操作。该示例还演示了 混合远程具有多个 Web 开发库的对象,用于呈现适合移动设备的用户 接口。

create()

此示例使用 jQuery、Bootstrap 和 Mustache 工具包,从 外部内容分发网络 (CDN)。这是 Visualforce 页面,其中包含 大胆。

<apex:page showHeader="false" standardStylesheets="false" docType="html-5.0" 
    title="Contacts—RemoteObjects Style" controller="RemoteObjectContactOverride">

    <!-- Include in some mobile web libraries -->
    <apex:stylesheet value="//netdna.bootstrapcdn.com/bootswatch/3.1.1/superhero/bootstrap.min.css"/>
    <apex:includeScript value="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"/>
    <apex:includeScript value="//cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"/>

    <!-- Set up Remote Objects, with an override for create() method -->
    <apex:remoteObjects jsNamespace="$M">
        <apex:remoteObjectModel name="Contact" fields="FirstName,LastName,Phone" 
            create="{!$RemoteAction.RemoteObjectContactOverride.create}"/>
    </apex:remoteObjects>

    <!-- Page markup -->
    <div class="container">
        <div class="row">
            <div class="col-md-2"></div>
            <div class="col-md-8">
                <table id="myTable" 
                    class="table table-bordered table-striped table-condensed">
                    <colgroup>
                        <col class="col-md-3" />
                        <col class="col-md-3" />
                        <col class="col-md-3" />
                    </colgroup>
                    <caption>
                        Contact Data Order ([ {LastName: 'ASC'}, {FirstName: 'DESC'} ]) 
                        <button id="bRefresh" class="btn btn-success btn-sm" 
                            type="button">Refresh</button>
                    </caption>
                    <caption id="msgBox" class="alert alert-danger hidden"></caption>
                    <thead>
                        <tr><td>FirstName</td><td>LastName</td><td>Phone</td></tr>
                    </thead>
                    <tbody></tbody>
                    <tfoot>
                        <tr>
                        <td><input type="text" name="FirstName" id="iFirstName" 
                            placeholder="John" class="form-control" /></td>
                        <td><input type="text" name="LastName" id="iLastName" 
                            placeholder="Doe" class="form-control" /></td>
                        <td>
                            <div class="input-group">
                              <input type="text" name="Phone" id="iPhone" 
                                placeholder="(123) 456-7890" class="form-control" />
                              <span class="input-group-btn">
                                <button id="bAdd" class="btn btn-primary" 
                                    type="button">Save</button>
                              </span>
                            </div>
                        </td>
                        </tr>
                    </tfoot>
                </table>
                <div class="panel panel-default">
                  <div class="panel-heading">Log</div>
                  <div class="panel-body" id="log">
                  </div>
                </div>            
            </div>
            <div class="col-md-2"></div>
        </div>
    </div>

    <!-- Results template (table rows of Contacts) -->
    <script id="tmpl" type="x-tmpl-mustache">
        <tr><td>{{FirstName}}</td><td>{{LastName}}</td><td>{{Phone}}</td></tr>
    </script>

    <!-- Page functionality -->
    <script>
        var table = $('#myTable tbody');
        var template = $('#tmpl').html();
        Mustache.parse(template);

        // Retrieve all contacts and add to results table on page
        var fetchContacts = function() {
            (new $M.Contact()).retrieve({
                orderby: [ {LastName: 'ASC'}, {FirstName: 'DESC'} ],
            }, function(err, records) {
                if (!err) {
                    // Add some status messages to the log panel
                    $('#log')
                    .append('<p>Fetched contact records.</p>')
                    .append('<p>Records Size: '+ records.length + '!</p>');

                    // Update the table of contacts with fresh results
                    table.empty();
                    records.forEach(function(rec) {
                        table.append(Mustache.render(template, rec._props));                    
                    });                
                } else {
                    $('#msgBox').text(err.message).removeClass('hidden');            
                }
            });
        };
        
        var addContact = function() {
            // Create a new Remote Object from form values
            (new $M.Contact({
                FirstName: $('#iFirstName').val(),
                LastName: $('#iLastName').val(),
                Phone: $('#iPhone').val()
            })).create(function(err, record, event) {
                // New record created...
                if (!err) {
                    // Reset the New Record form fields, for the next create
                    $('input').each(function() {
                        $(this).val('');
                    });
                    
                    // Add some status messages to the log panel
                    $('#log')
                    .append('<p>Contact created!</p>')
                    // Custom data added to event.result by override function
                    .append('<p>Got custom data: ' + event.result.custom + '</p>'); 

                    // Redraw the results list with current contacts
                    fetchContacts();
                } else {
                    $('#msgBox').text(err.message).removeClass('hidden');            
                }
            });
        };

        // Bind application functions to UI events
        $('#bRefresh').click(fetchContacts);
        $('#bAdd').click(addContact);
        
        // Initial load of the contacts list
        fetchContacts();
    </script>
</apex:page>

前面示例中的关键代码行位于 Remote 对象访问定义。将单个属性添加到联系人远程对象定义 设置 覆盖:

create="{!$RemoteAction.RemoteObjectContactOverride.create}"

该属性采用 视觉力 引用要用作内置操作重写的方法的表达式。

@RemoteActioncreate()在本例中,引用的方法位于 Apex 类中,该类是页面的 控制器。重写方法的代码很简单。

public with sharing class RemoteObjectContactOverride {
     
    @RemoteAction
    public static Map<String, Object> create(String type, Map<String, Object> fields) {
        System.debug(LoggingLevel.INFO, 'Before calling create on: ' + type);
        
        // Invoke the standard create action
        // For when you want mostly-normal behavior, with a little something different
        Map<String, Object> result = RemoteObjectController.create(type, fields);

        System.debug(LoggingLevel.INFO, 'After calling create on: ' + type);
        System.debug(LoggingLevel.INFO, 'Result: ' + result);
        
        // Here's the little something different, adding extra data to the result
        Map<String, Object> customResult = 
            new Map<String, Object> {'custom' => 'my custom data' };
        customResult.putAll(result);
        
        return customResult;
    }
}

此方法记录调用,然后 使用标准调用来执行创建。它 执行相同的数据操作语言 (DML) 命令以创建记录 内置版本会,因为它使用的是内置版本。后 执行 create,该方法会执行更多的日志记录。最后,它增加了一些额外的内容 数据复制到 JavaScript 回调函数将接收的返回有效负载 Visualforce 页面。@RemoteActionRemoteObjectController.create()

它添加了有趣的额外数据,并使覆盖 内置方法很有用。前面的控制器添加的额外数据 是微不足道的,仅用于说明目的。实际覆盖可以包括 更复杂的逻辑 – 计算的结果、其他方法调用等。 需要了解的重要一点是,新的自定义替代方法可以执行 幕后的附加内容,并可以返回内置版本的额外数据 不能。

使用远程的示例 使用 jQuery Mobile 的对象

Visualforce 远程 Objects 旨在与 JavaScript 框架很好地“融合”。这个扩展但简单的例子 演示如何使用远程 使用 jQuery Mobile 查看联系人列表以及添加、编辑和删除的对象 他们。

此示例使用 Salesforce Mobile Pack 中的 jQuery Mobile,并且基于 在移动随附的示例代码上 打包用于 jQuery。远程 对象和 jQuery Mobile 可以很容易地为 电话。

具有远程对象和 jQuery Mobile 的简单联系人编辑器

<apex:page docType="html-5.0" showHeader="false" sidebar="false">          

	<!-- Include jQuery and jQuery Mobile from the Mobile Pack -->
    <apex:stylesheet value="{!URLFOR($Resource.MobilePack_jQuery, 
         'jquery.mobile-1.3.0.min.css')}"/>
    <apex:includeScript value="{!URLFOR($Resource.MobilePack_jQuery, 
         'jquery-1.9.1.min.js')}"/>
    <apex:includeScript value="{!URLFOR($Resource.MobilePack_jQuery, 
         'jquery.mobile-1.3.0.min.js')}"/>

    <!-- Remote Objects declaration -->
    <apex:remoteObjects jsNamespace="RemoteObjectModel">
        <apex:remoteObjectModel name="Contact" fields="Id,FirstName,LastName,Phone">
            <!-- Notes is a custom field added to the Contact object -->
            <apex:remoteObjectField name="Notes__c" jsShorthand="Notes"/>
        </apex:remoteObjectModel>
    </apex:remoteObjects>

    <head>
        <title>Contacts</title>
        <meta name="viewport" 
           content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        
        <script type="text/javascript">
            var $j = jQuery.noConflict(); 

            // Config object with commonly used data
            // This keeps some hard-coded HTML IDs out of the code
            var Config = {
                Selectors: {
                    list: '#cList',
                    detailFields: "#fName #lName #phone #notes #error #contactId".split(" ")
                },
                Data: {
                    contact: 'contact'
                }
            };
            
            // Get all contacts, and display them in a list 
            function getAllContacts() {
                $j.mobile.showPageLoadingMsg();
                
                var c = new RemoteObjectModel.Contact();
                // Use the 'limit' operator to increase the default limit of 20
                c.retrieve({ limit: 100 }, function (err, records) { 
                    // Handle any errors
                    if (err) { 
                        displayError(err); 
                    } else {
                        // Empty the current list
                        var list = $j(Config.Selectors.list).empty();
                        // Now add results records to list
                        $j.each(records, function() {
                            var newLink = $j('<a></a>', {
                                text: this.get('FirstName') + ' ' + this.get('LastName')
                            });
                            newLink.data(Config.Data.contact, this.get('Id'));
                            newLink.appendTo(list).wrap('<li></li>');
                        });
                        
                        $j.mobile.hidePageLoadingMsg();
                        list.listview('refresh');
                    }
                });
            }

            // Handle the Save button that appears on both
            // the Edit Contact and New Contact pages
            function addUpdateContact(e){
                e.preventDefault();

                var record = new RemoteObjectModel.Contact({
                    FirstName: $j('#fName').val(),
                    LastName: $j('#lName').val(),
                    Phone: $j('#phone').val(),
                    Notes: $j('#notes').val() 
                    // Note use of shortcut 'Notes' in place of Notes__c
                });

                var cId = $j('#contactId').val();
                if( !cId ) { // new record
                    record.create(updateCallback);
                } else { // update existing
                    record.set('Id', cId);
                    record.update(updateCallback);
                }
            }
            
            // Handle the delete button
            function deleteContact(e){
                e.preventDefault();
                var ct = new RemoteObjectModel.Contact();
                ct.del($j('#contactId').val(), updateCallback);
            }
            
            // Callback to handle DML Remote Objects calls
            function updateCallback(err, ids){
                if (err) { 
                    displayError(err); 
                } else {
                    // Reload the contacts with current list
                    getAllContacts();
                    $j.mobile.changePage('#listpage', {changeHash: true});
                }
            }

            // Utility function to log and display any errors
            function displayError(e){
                console && console.log(e);
                $j('#error').html(e.message);
            }
        
            // Attach functions to the buttons that trigger them
            function regBtnClickHandlers() {
                $j('#add').click(function(e) {
                    e.preventDefault();
                    $j.mobile.showPageLoadingMsg();
                    
                    // empty all the clic handlers                    
                    $j.each(Config.Selectors.detailFields, function(i, field) {
                        $j(field).val('');
                    });
                    
                    $j.mobile.changePage('#detailpage', {changeHash: true});
                    $j.mobile.hidePageLoadingMsg();
                });
        
                $j('#save').click(function(e) {
                   addUpdateContact(e);
                });
        
                $j('#delete').click(function(e) {
                   deleteContact(e);
                });
            }

            // Shows the contact detail view, 
            // including filling in form fields with current data
            function showDetailView(contact) {
                $j('#contactId').val(contact.get('Id'));
                $j('#fName').val(contact.get('FirstName'));
                $j('#lName').val(contact.get('LastName'));
                $j('#phone').val(contact.get('Phone'));
                $j('#notes').val(contact.get('Notes'));
                $j('#error').html('');
                $j.mobile.changePage('#detailpage', {changeHash: true});
            }

            // Register click handler for list view clicks
            // Note: One click handler handles the whole list	
            function regListViewClickHandler() {
                $j(Config.Selectors.list).on('click', 'li', function(e) {

                    // show loading message
                    $j.mobile.showPageLoadingMsg();

                    // get the contact data for item clicked
                    var id = $j(e.target).data(Config.Data.contact);

                    // retrieve latest details for this contact
                    var c = new RemoteObjectModel.Contact();
                    c.retrieve({ 
                        where: { Id: { eq: id } } 
                    }, function(err, records) { 
                        if(err) { 
                            displayError(err); 
                        } else {
                            showDetailView(records[0]);
                        }

                        // hide the loading message in either case
                        $j.mobile.hidePageLoadingMsg();
                    });
                });
            }

            // And, finally, run the page
            $j(document).ready(function() {
                regBtnClickHandlers();
                regListViewClickHandler();
                getAllContacts();
            });

        </script>    
    </head>

    <!-- HTML and jQuery Mobile markup for the list and detail screens -->
    <body>    
    
        <!-- This div is the list "page" -->
        <div data-role="page" data-theme="b" id="listpage">                
            <div data-role="header" data-position="fixed">
                <h2>Contacts</h2>
                <a href='#' id="add" class='ui-btn-right' data-icon='add' 
                   data-theme="b">Add</a>
            </div>
            <div data-role="content" id="contactList">            
                <ul id="cList" data-filter="true" data-inset="true" 
                    data-role="listview" data-theme="c" data-dividertheme="b">
                </ul>
            </div>
        </div>
                
        <!-- This div is the detail "page" -->
        <div data-role="page" data-theme="b" id="detailpage">
            <div data-role="header" data-position="fixed">
                <a href='#listpage' id="back2ContactList" class='ui-btn-left' 
                   data-icon='arrow-l' data-direction="reverse" 
                   data-transition="flip">Back</a>
                <h1>Contact Details</h1>
            </div>
            <div data-role="content">
                <div data-role="fieldcontain">
                    <label for="fName">First Name:</label>
                    <input name="fName" id="fName" />
                </div>
                <div data-role="fieldcontain">
                    <label for="lName">Last Name:</label>
                    <input name="lName" id="lName" />
                </div>
                <div data-role="fieldcontain">
                    <label for="phone">Phone:</label>
                    <input name="phone" id="phone"/>
                </div>
                <div data-role="fieldcontain">
                    <label for="notes">Notes:</label>
                    <textarea name="notes" id="notes"/>
                </div>

                <h2 style="color:red" id="error"></h2>

                <input type="hidden" id="contactId" />
                <button id="save" data-role="button" data-icon="check" 
                    data-inline="true" data-theme="b" class="save">Save</button>
                <button id="delete" data-role="button" data-icon="delete" 
                    data-inline="true" class="destroy">Delete</button>
            </div>
        </div>
    </body>
</apex:page>

请注意,虽然所有四个远程 演示了对象操作,只有三个回调处理程序。

  • getAllContacts()加载联系人列表的调用 并为回调提供匿名函数。回调检查 错误,然后循环访问结果,将它们添加到页面中。retrieve()
  • 同样,调用加载单个 联系详情页面,结果也由 匿名函数。showDetailView()retrieve()
  • addUpdateContact()并处理添加, 更新和删除联系人。这两种方法都作为回调传递 功能。 不使用远程的结果 对象操作。它只检查错误,将它们记录到控制台,然后 调用刷新 页面。deleteContact()updateCallback()updateCallback()getAllContacts()

使用远程的最佳实践 对象

Visualforce 远程Objects 是一个有效的工具,用于快速向 Visualforce 页面添加简单的数据操作。远程 Objects 易于使用,具有不需要 Apex 代码即可实现读取的轻量级组件 以及将数据写入 Salesforce 服务。远程 但是,对象并不总是适合这项工作的工具,因此了解远程操作非常重要 对象的工作原理以及何时使用其他工具,例如 JavaScript 远程处理。

字段级安全性

远程 对象遵循组织的字段级别安全设置。创建时请记住这一点 使用远程的页面 对象。查看页面的用户无法访问的字段显示为空白。操作 如果修改字段数据 (, , 和 ) 在请求中包含不可访问的字段,则它们将失败并显示错误。create()update()upsert()

交易边界

)

相比之下,JavaScript 远程处理事务 边界位于 Apex 方法上。它 在一种方法中轻松创建发票和相关行项目记录,其中自动 Apex 交易可确保所有 记录是一起创建的,或者根本不创建。@RemoteAction

业务逻辑的适当放置和测试

仔细考虑将应用程序的业务逻辑放在何处,尤其是在 这很复杂。当您创建支持创建、编辑和 删除单个对象,如使用 Remote 的示例 使用 jQuery Mobile 的对象,业务逻辑是最小的。将此业务逻辑放在客户端,在 远程 对象和 JavaScript,可以完全合适。当您有更复杂的业务规则和 但是,从客户端层中删除该逻辑可能更有效,并且 在服务器端构建它。在决定将组织的业务逻辑放在何处时,请考虑以下几点。

  • 安全性和一致性:请记住,用户可能会在 在事务中,或者改变页面的 JavaScript 使用 Firebug 和其他 工具。远程 对象强制执行验证规则、触发器、共享规则、字段级安全性等 数据访问限制,但如果您将业务规则放在 JavaScript 而不是 Salesforce 中,这些规则可能会被中断, 更改或绕过。
  • 可测试性:服务器端的业务逻辑可以使用 Salesforce 提供的许多工具进行测试。为 因此,我们鼓励您将复杂的行为放在 Apex 中,并使用 Apex 测试框架 以验证它是否按预期工作。
  • 性能:如果您的处理需要将许多记录作为事务的一部分进行查看,但 不会在浏览器中显示它们,我们建议您避免将该数据发送到客户端,并且 而是在服务器上“本地”处理数据。想想你的页面需要做什么数据 它的工作,并确保您不会不必要地通过网络复制它。

处理复杂性

应用程序需要仔细管理复杂性。简单的联系人管理器或商店定位器 页面管理起来并不复杂,但许多业务流程都有。 远程 对象与 jQuery 和 AngularJS 等 JavaScript 框架搭配得很好,这些框架可以帮助 应用程序用户界面的复杂性。始终考虑将关注点分开 您的应用程序分为多个层,并尽可能使它们离散。这称为 “关注点分离”,这是一种经典的软件模式和最佳实践。

请考虑将数据完整性规则放在触发器和验证规则中。还要考虑 将业务流程规则封装在 Apex 代码中,您可以通过可与 JavaScript 远程处理或 SOAP 或 REST 服务一起使用的方法访问这些规则 您可以在任何地方使用。@RemoteAction

远程的替代品 对象

远程 Objects 是一个有用的工具,用于快速创建具有基本数据操作的页面。当工作那 您的页面需要做的比这更大,考虑到 Salesforce 提供了许多替代方案 闪电平台开发人员。

  • 标准 Visualforce 可用于实现广泛的应用程序功能。 Visualforce 在使用标准控制器时提供了许多自动功能,并且 使用您自己的 Apex 代码支持完全自定义的功能。
  • JavaScript 远程处理也适用于 第三方 JavaScript 框架,使您能够访问 顶点。
  • Salesforce Mobile 允许您使用 声明性工具,而不是代码。

仔细考虑您的页面或应用程序需要做什么,然后选择正确的 工作工具。有时该工具是远程的 对象,有时是别的东西。

远程 对象限制

Visualforce 虽然远程 对象不受某些资源限制的约束,它有自己的限制。远程 对象受以下限制的约束。

  • 远程 对象不是规避 Salesforce 服务限制的方法。远程 对象调用不受 API 限制的约束,但使用远程的 Visualforce 页面不受限制 对象受所有标准 Visualforce 限制的约束。
  • 在单个请求中最多可以检索 100 行。要显示更多行,请提交 使用查询的其他请求 参数。OFFSET
  • 远程 对象不支持字段。您无法检索 或设置 类型为 的对象字段的值。BlobBlob
  • 将属性设置为“远程” 对象组件禁用为那些 Remote 生成 JavaScript 对象。依赖于未呈现的 Remote 的任何页面功能 还应禁用对象。renderedfalse

在 Visualforce Pages 中使用 JavaScript

在 Visualforce 中使用 JavaScript 页面允许您访问各种现有的 JavaScript 功能,例如 JavaScript 库,以及自定义页面功能的其他方法。操作标记(如 和 )支持 Ajax 请求。

<apex:actionFunction><apex:actionSupport>

警告

通过在页面中包含 JavaScript,您可以引入 使用 Visualforce 时没有的跨浏览器和维护问题。在编写任何 JavaScript 之前, 您应该确保没有现有的 Visualforce 组件可以解决您的 问题。在 Visualforce 页面中包含 JavaScript 的最佳方法是将 JavaScript 放在静态资源中,然后调用 它从那里开始。例如

<apex:includeScript value="{!$Resource.MyJavascriptFile}"/>

然后,您可以使用该 JavaScript 文件中定义的函数 在您的页面中使用标签。

<script>

提示

在表达式中使用 JavaScript 时,需要 使用反斜杠 (\) 对引号进行转义。例如

onclick="{!IF(false, 'javascript_call(\"js_string_parameter\")', 'else case')}"

使用 $Component JavaScript 中的参考组件

使用全局变量简化 引用为 Visualforce 组件生成的 DOM ID,并减少对整个页面的一些依赖 结构。

$Component

每个 Visualforce 标签都有一个属性。一个标签的属性可以由另一个标签用来绑定这两个标签 一起。例如,标签的属性可以与标签的属性一起使用。、 和其他面向操作的 和 属性 组件还使用其他组件的属性值。idid<apex:outputLabel>for<apex:inputField>idreRenderstatus<apex:actionFunction><apex:actionSupport>id

除了用于将 Visualforce 组件绑定在一起外,此 ID 还用于构成文档的一部分 呈现页面时组件的对象模型 (DOM) ID。

要在 JavaScript 或其他支持 Web 的语言中引用 Visualforce 组件,您必须 为该组件的属性指定一个值。DOM ID 由组合构成 的属性 组件和属性 包含该元素的所有组件。ididid

组件访问示例

以下示例将 DOM ID 用于标记。该页面包含两个面板:第一个面板 持有一个触发 DOM 事件的复选框,第二个复选框包含一些文本 为响应事件而更改。<apex:outputPanel>

页面顶部包括 JavaScript 包含在 HTML 标记中。它将触发事件的元素作为参数 () 和包含要影响文本的目标面板的 DOM ID()。<script>inputtextid

<apex:page id="thePage">
    <!-- A simple function for changing the font. -->
    <script>
        function changeFont(input, textid) {
            if(input.checked) {
                document.getElementById(textid).style.fontWeight = "bold";
            }
            else {
                document.getElementById(textid).style.fontWeight = "normal";
            }
        }
    </script>

    <!-- This outputPanel calls the function, passing in the
         checkbox itself, and the DOM ID of the target component. -->
    <apex:outputPanel layout="block">
        <label for="checkbox">Click this box to change text font:</label>
        <input id="checkbox" type="checkbox"
            onclick="changeFont(this,'{!$Component.thePanel}');"/>
    </apex:outputPanel>

    <!-- This outputPanel is the target, and contains 
         text that will be changed. -->
    <apex:outputPanel id="thePanel" layout="block">
        Change my font weight!
    </apex:outputPanel>
</apex:page>

该表达式用于获取 组件生成的 HTML 元素的 DOM ID。{!$Component.thePanel}<apex:outputPanel id=”thePanel”>

将 JavaScript 库与 Visualforce 结合使用

您可以在 Visualforce 页面中包含 JavaScript 库,以利用 这些库提供的功能。包含 JavaScript 库的最佳方式是 创建静态资源,然后通过向页面添加组件来包含库。

<apex:includeScript>例如,如果您使用的是 jQuery (https://js.foundation/),请从名为 jquery 的库创建静态资源,然后在类似 这:

<apex:page>
    <apex:includeScript value="{!$Resource.jquery}"/>
</apex:page>

你 然后,可以通过添加 to 调用在页面中使用它 函数。

<script>如果您在 Visualforce 页面中使用 JavaScript 库,并且该库 定义为特殊字符,您需要 修改 JavaScript 以覆盖此用法。例如,使用 jQuery,您可以覆盖 通过使用函数的定义。

$$jQuery.noConflict()

<apex:page >
<apex:includeScript value="{!$Resource.jquery}"/>
<html>
<head>
  <script>
    jQuery.noConflict();
    
    jQuery(document).ready(function() {    
        jQuery("a").click(function() {
          alert("Hello world, part 2!");
        });
    });
  </script>
</head>
...
</apex:page>

注意

  • 支持并鼓励使用第三方 JavaScript 库和框架 由 Salesforce 提供。但是,Salesforce 无法帮助您调试 JavaScript 代码,除非它 特别是与Salesforce功能有关。
  • 不要在使用 Chatter 组件、 、 或 .<apex:enhancedList><knowledge:articleCaseToolbar><knowledge:articleRendererToolbar>

适用于 Apex 的 JavaScript 远程处理 控制器

在 Visualforce 中使用 JavaScript 远程处理来调用 来自 JavaScript 的 Apex 控制器。创建具有复杂、动态行为的页面,但事实并非如此 可以使用标准的 Visualforce AJAX 组件。使用 JavaScript 远程处理实现的功能需要三个元素:

  • 您添加到 Visualforce 页面的远程方法调用,写在 JavaScript的。
  • Apex 控制器类中的远程方法定义。此方法 定义是用 Apex 编写的,但与 正常操作方法。
  • 您添加到或包含在 Visualforce 中的响应处理程序回调函数 页面,用 JavaScript 编写。

什么是 JavaScript Remoting?

JavaScript 远程处理是一种工具,可以 前端开发人员可以使用从 Visualforce 页面直接向 Apex 发出 AJAX 请求 控制器。JavaScript 远程处理允许您 通过将页面与控制器分离来运行异步操作,并在 页面,而无需重新加载整个页面。

此外,JavaScript 远程处理还可以提供帮助 缓解视图状态问题,同时仍在用户查看页面的上下文中执行。 JavaScript 远程处理是最有效的方法 调用控制器并从页面传入数据,因为您可以确保 每次进行调用时仅传递所需的数据。

何时使用 JavaScript 远程处理

JavaScript 远程处理是 针对移动网页和使用第三方 JavaScript 的网页进行了优化 图书馆。它支持动态的交互式页面,感觉比 传统 Visualforce 页面。

JavaScript 远程处理是 标准 Visualforce AJAX 组件和 Visualforce Remote 对象。它 提供了一种更惯用的方式,通过 JavaScript 与 Lightning 平台进行交互。JavaScript 远程处理允许您使用熟悉的 JavaScript 实践和结构,并利用其他 JavaScript 框架和工具 对于前端开发人员来说,套件更轻松。远程处理可创建响应速度更快的理想体验 对于移动页面或您的用例需要最高效率的任何其他页面,以及 性能。由于它是异步的,因此只能加载初始页面和 需要显示页面,然后延迟加载页面上可能未使用的其他数据 马上。您甚至可以使用此方法为用户的页面或视图预加载数据 尚未访问。

虽然 JavaScript 远程处理 可以提供高效、响应迅速且优化的用户体验,但事实并非如此 无限制。开发使用它的页面可能需要额外的时间,而您 需要改变你开发和思考页面流程的方式。因为你 未使用表单,并且没有与请求关联的视图状态,您有 在客户端自行管理页面的状态。另一方面 没有什么可以阻止您将 JavaScript 远程处理与 标准 Visualforce MVC 设计范式。一如既往,把你试图解决的问题放在首位 在确定您的设计时。JavaScript 远程处理是众多远程处理之一 可用的工具。

比较 JavaScript Remoting 和 <apex:actionFunction>

该组件还允许 通过 JavaScript 调用控制器操作方法。

<apex:actionFunction>

一般来说,更容易使用 并且需要更少的代码,而 JavaScript 远程处理提供了更大的灵活性。<apex:actionFunction>以下是两者之间的一些具体区别。

  • 标签:<apex:actionFunction>
    • 允许您指定重新渲染目标
    • 提交表格
    • 不需要你编写任何 JavaScript
  • JavaScript 远程处理:
    • 允许您传递参数
    • 提供回调
    • 需要你编写一些 JavaScript

比较 JavaScript 远程处理和远程处理 对象

JavaScript 远程处理和远程 对象提供类似的功能,两者都是用于创建动态响应式页面的有用工具。 它们有一些重要的区别,在选择哪个之前,您应该考虑这些差异 用。

一般来说,远程 Objects 非常适合只需要执行简单的 Create-Read-Update-Delete 或 “CRUD”,对象访问。JavaScript Remoting 更适合 到访问更高级别服务器操作的页面。远程 Objects 可让您快速启动和运行,而无需太多仪式,而 JavaScript Remoting 适合 更复杂的应用程序,需要一些前期 API 样式的设计工作。视觉力 远程 对象:

  • 使基本的“CRUD”对象访问变得容易
  • 不需要任何 Apex 代码
  • 支持最小的服务器端应用程序逻辑
  • 不提供自动关系遍历;您必须查找相关对象 你自己

JavaScript 远程处理:

  • 需要 JavaScript 和 Apex 代码
  • 支持复杂的服务器端应用程序逻辑
  • 更好地处理复杂的对象关系
  • 更有效地(甚至)使用网络连接

将 JavaScript 远程处理添加到 Visualforce 页面

要在 Visualforce 页面中使用 JavaScript 远程处理,请将请求添加为 JavaScript 函数调用。将 Apex 类作为自定义控制器或控制器扩展添加到 你 页。

<apex:page controller="MyController" extension="MyExtension">

警告

添加控制器或控制器扩展将授予对该 Apex 类中所有方法的访问权限,即使这些方法 页面中不使用方法。任何可以查看该页面的人都可以执行所有方法,并向 控制器。@RemoteAction@RemoteAction然后,将请求添加为 JavaScript 函数调用。一个简单的 JavaScript 远程调用采用以下方法 形式。

[namespace.]MyController.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);
元素描述
namespace控制器类的命名空间。如果出现以下情况,则 namespace 元素是必需的 您的组织定义了一个命名空间,或者该类是否来自已安装的 包。
MyController,MyExtensionApex 控制器或扩展的名称。
method要调用的 Apex 方法的名称。
parameters方法采用的参数的逗号分隔列表。
callbackFunction处理来自 控制器。还可以以内联方式声明匿名函数。 接收方法的状态 调用和结果作为参数。callbackFunction
configuration配置远程呼叫和响应的处理。使用此元素可以 更改远程处理调用的行为,例如是否转义 Apex 方法的响应。

远程方法调用同步执行,但不会等待响应 返回。当响应返回时,回调函数会异步处理它。有关详细信息,请参阅处理远程响应。

配置 JavaScript 远程处理请求

通过在以下情况下为对象提供配置设置来配置远程处理请求 声明远程处理请求。例如,默认配置参数如下所示 这:

{ buffer: true, escape: true, timeout: 30000 }

这些 配置参数没有顺序,您可以省略不想更改的参数 从默认值。JavaScript 远程处理支持以下功能 配置参数:

名字数据类型描述
缓冲区布尔是否将时间上彼此靠近执行的请求分组到单个请求中。 缺省值为 。trueJavaScript 远程处理 优化在时间上彼此接近执行的请求,并将调用分组到 单个请求。这种缓冲提高了整体请求和响应的效率 循环,但有时确保所有请求都执行很有用 独立地。
布尔是否转义 Apex 方法的响应。缺省值为 。true
超时整数请求的超时时间(以毫秒为单位)。默认值为 30,000 (30 秒)。最大值为 120,000 (120 秒,或 2 分钟)。

还可以为页面发出的所有请求配置请求超时,方法是将 使用 Visualforce 远程处理超时 对象:

<script type="text/javascript">

    Visualforce.remoting.timeout = 120000; // Set timeout at page level

    function getRemoteAccount() {
        var accountName = document.getElementById('acctSearch').value;

        // This remoting call will use the page's timeout value
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.AccountRemoter.getAccount}',
            accountName, 
            handleResult
        );
    }

    function handleResult(result, event) { ... }
</script>

通过在 该请求的配置对象,如上所述。

命名空间和 JavaScript 远程处理

您可以使用全局 自动解析远程操作的正确命名空间(如果有)。这使得它 更易于使用命名空间,尤其是对于对方法进行远程处理调用的页面 以包装形式提供。

$RemoteAction若要使用此工具,必须显式调用 JavaScript 远程处理。 执行此操作的模式 是:

Visualforce.remoting.Manager.invokeAction(
    'fully_qualified_remote_action', 
     invocation_parameters
);

完全限定的远程操作是一个字符串,表示 远程操作方法的完整路径,包括命名空间、基类等:. 在表达式中自动使用 解析命名空间,例如 .namespace[.BaseClass][.ContainingClass].ConcreteClass.Method$RemoteAction{!$RemoteAction.MyController.getAccount}调用参数是用于执行 远程方法调用,并且是用于进行标准远程处理调用的相同参数:

  • 要发送到方法的参数, 如果有的话。@RemoteAction
  • 回调函数,用于处理返回的结果。
  • 调用的配置详细信息(如果有)。

例如,您可以定义一个远程调用来检索 帐户喜欢 这:

<script type="text/javascript">
function getRemoteAccount() {
    var accountName = document.getElementById('acctSearch').value;

    Visualforce.remoting.Manager.invokeAction(
        '{!$RemoteAction.MyController.getAccount}', 
        accountName, 
        function(result, event){
            if (event.status) {
                document.getElementById('acctId').innerHTML = result.Id
                document.getElementById('acctName').innerHTML = result.Name;
            } else if (event.type === 'exception') {
                document.getElementById("responseErrors").innerHTML = event.message;
            } else {
                document.getElementById("responseErrors").innerHTML = event.message;
            }
        }, 
        {escape: true}
    );
}
</script>

这 JavaScript 远程处理调用不需要知道命名空间的详细信息,其中 控制器是定义的,无论是在你自己的命名空间中,还是在 已安装的软件包。它还可以处理您的组织没有 命名空间定义。

注意

调用时遇到的错误是 仅在 JavaScript 控制台中报告。例如,如果在多个命名空间中找到匹配的方法,则返回第一个匹配方法,并将警告记录到 JavaScript 控制台。如果未找到匹配的控制器或操作,则调用将失败,并且 错误将记录到 JavaScript 控制台。invokeAction$RemoteAction@RemoteAction

JavaScript 远程处理的 OAuth 2.0 身份验证

您可以使用 OAuth 2.0 对 JavaScript 远程处理请求进行身份验证,而不是要求 标准的用户名和密码登录过程。OAuth 允许跨应用程序和 使用标准无法安全地完成的跨组织集成 认证。

使用 OAuth 的 Visualforce 页面 身份验证在页面级别对其进行配置,并将 OAuth 用于所有 JavaScript 远程处理请求。以外 配置,使用 JavaScript 远程处理是 完全一样。为 JavaScript 远程处理配置 OAuth 从 Visualforce 页面获取 以后 形式:

<script type="text/javascript">

    Visualforce.remoting.oauthAccessToken = <access_token>;

    // ...
</script>

设置后,所有 JavaScript 远程处理请求都使用 OAuth。其余的 的 JavaScript 远程处理代码可以保留 相同。

oauthAccessToken

oauthAccessToken是 OAuth 身份验证令牌 由页面代码获取。获取和更新访问令牌是简单的 OAuth, 加一个。JavaScript 远程处理 OAuth 身份验证请求“VisualForce”范围,因此必须使用此令牌或 包含它的范围,包括“web”或“full”。在 OAuth 请求中设置(或“web”或“full”)。scope=visualforce

有关获取访问令牌以及将 OAuth 与 Lightning 平台结合使用的信息,请参阅《对远程访问应用程序进行身份验证》和《深入了解 OAuth 2.0》 Salesforce 联机帮助中的 Salesforce。

在 Apex 中声明远程方法

您几乎可以将任何 Apex 方法作为 JavaScript 远程处理远程操作调用。为此, 方法需要遵循一些简单的规则。在控制器中,Apex 方法声明前面有 注释喜欢 这:

@RemoteAction

@RemoteAction
global static String getItemId(String objectName) { ... }

Apex 方法必须是 和 或 。

@RemoteActionstaticglobalpublic

您的方法可以采用 Apex 基元、集合、类型化 和泛型 sObjects,以及用户定义的 Apex 类和接口作为参数。通用 sObjects 必须具有 ID 或 sobjectType 值才能标识实际类型。接口参数必须 有一个 apexType 来标识实际类型。

您的方法可以返回 Apex 基元、sObjects、 集合、用户定义的 Apex 类和枚举、 、 、 或 。SaveResultUpsertResultDeleteResultSelectOptionPageReference用于 JavaScript 远程处理的方法必须由名称和编号唯一标识 参数;无法重载。例如,使用上述方法,您不能同时拥有 一种方法。相反 声明具有不同名称的多个方法:

getItemId(Integer productNumber)

  • getItemIdFromName(String objectName)
  • getItemIdFromProductNumber(Integer productNumber)

@RemoteAction方法的范围和可见性

Apex 方法必须是 和 或 。@RemoteActionstaticglobalpublic不要使用全局公开的远程操作来执行敏感操作或公开 非公开数据。 远程操作只能调用 其他方法。您不能使用公共遥控器 组件或作用域中的操作。范围升级会导致编译器错误,或者 对于在运行时解析的引用,则为运行时失败。下表描述了这些限制。

Globalglobalglobalglobal

@RemoteAction范围Visualforce 页面非全局组件全局组件iframe跨包访问
全局远程方法允许允许允许允许允许
公共远程方法允许允许错误错误包必须共享命名空间。方法必须具有注释。@namespaceAccessible

注意

如果 @RemoteAction 方法位于托管包中并由 Visualforce Remoting 使用,则它必须 如果使用用户配置文件或权限集访问权限,则具有全局可见性。什么时候 远程操作通过标记访问,它们通过组件、或标记间接包含。远程方法的作用域是 结转到顶级容器(包含层次结构中的顶级项)中, 必须遵守范围升级规则。

<apex:include>,<apex:composition>

顶级容器
@RemoteAction访问自Visualforce 页面非全局组件全局组件iframe
全局组件允许允许允许允许
非全局组件允许允许仅当非全局组件不包括公共远程方法时才允许。仅当非全局组件不包括公共远程方法时才允许。
<apex:include> <apex:composition>允许在同一命名空间中;如果命名空间不同且包含的命名空间不同,则出错 Page 或其子层次结构包含公共远程方法。不适用不适用错误

远程方法和继承

您可以在 Apex 控制器上调用继承方法的远程操作。什么时候 查找或调用方法时,Visualforce 会检查页面 控制器的继承层次结构,并在控制器的祖先类中查找方法。

@RemoteAction@RemoteAction下面是一个演示此功能的示例。以下 Apex 类构成一个 三层继承 等级制度:

global with sharing class ChildRemoteController 
    extends ParentRemoteController { }
global virtual with sharing class ParentRemoteController 
    extends GrandparentRemoteController { }

global virtual with sharing class GrandparentRemoteController {
    @RemoteAction
    global static String sayHello(String helloTo) {
        return 'Hello ' + helloTo + ' from the Grandparent.';
    }
}

此 Visualforce 页面只需调用 遥控器 行动。

sayHello

<apex:page controller="ChildRemoteController" >
    <script type="text/javascript">
        function sayHello(helloTo) {
            ChildRemoteController.sayHello(helloTo, function(result, event){
                if(event.status) {
                    document.getElementById("result").innerHTML = result;
                }
            });
        }
    </script>

    <button onclick="sayHello('Jude');">Say Hello</button><br/>
    <div id="result">[Results]</div>
    
</apex:page>

这 类中不存在 Remote 方法。相反,它继承自 .

ChildRemoteControllerGrandparentRemoteController

使用接口参数声明远程方法

您可以使用以下命令声明方法 接口参数和返回类型,而不是局限于具体类。这,为了 例如,允许包提供程序打包远程方法和关联的接口,其中 订阅者组织可以从 Visualforce 页面调用,并传入自己的类 实现打包接口。

@RemoteAction这是一个简短的 例:

public class RemoteController {
    public interface MyInterface { String getMyString(); }
    public class MyClass implements MyInterface { 
        private String myString; 
        public String getMyString() { return myString; }
        public void setMyString(String s) { myString = s; }
    }
    
    @RemoteAction
    public static MyInterface setMessage(MyInterface i) {
        MyClass myC = new MyClass();
        myC.setMyString('MyClassified says "' + i.getMyString() + '".');
        return myC;
    }
}

从 JavaScript 远程处理调用发送到声明接口参数的对象必须包含一个值,该值必须是指向 具体类,即 . 例如,要对上述内容进行 JavaScript 远程处理调用 控制器:

@RemoteActionapexTypenamespace[.BaseClass][.ContainingClass].ConcreteClass

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.RemoteController.setMessage}',
    {'apexType':'thenamespace.RemoteController.MyClass', 'myString':'Lumos!'}, 
    handleResult
);

如果类定义位于您的组织内,则可以 简化远程处理调用,并使用默认命名空间:

c

RemoteController.setMessage(
    {'apexType':'c.RemoteController.MyClass', 'myString':'Lumos!'}, 
    handleResult
);

处理远程响应

异步处理对远程方法调用的响应 遥控器 方法回调 功能。您的回调函数接收 这 以下参数:

  • event表示 远程呼叫
  • result远程 Apex 返回的对象 方法。

你 函数可以根据数据更新页面上的信息和用户界面元素 返回。该对象提供允许您执行操作的值 在远程调用成功或失败时。

event

描述
event.statustrue成功时,错误时。false
event.type响应的类型:for a 调用成功,如果远程 方法抛出异常。rpcexception
event.message包含返回的任何错误消息。
event.where这 顶点 叠 trace 是 引用 如果它是由远程生成的 方法。

返回的 Apex 基元数据类型,例如 字符串或数字 – 转换为其 JavaScript 等效项。顶点对象 返回的对象将转换为 JavaScript 对象,而集合将转换为 JavaScript 数组。请记住,JavaScript 区分大小写,因此 、 和 被视为不同的字段。resultidIdID

作为 JavaScript 远程调用的一部分,如果 Apex 方法响应包含对 相同 对象。 这 对象是 不 在返回的 JavaScript 对象中重复。 相反 呈现的 JavaScript 对象包含 一个 参考 到同一个对象。一个示例是 Apex 方法,它返回一个包含相同 对象两次。

了解日期和时间序列化

日期和时间值在通过 Visualforce 传递时被序列化为纪元时间 远程处理。

Date, , 从函数返回的对象被序列化为长整数。DateTimeTimeRemoteAction

序列化日期时间值

[{
    "statusCode": 200,
    "type": "rpc",
    "tid": 8,
    "ref": false,
    "action": "DateTestController",
    "method": "add",
    "result": 1432047600000
}]

调试 JavaScript 远程处理

调试使用 JavaScript 远程处理的页面需要调试 Visualforce、Apex 和 JavaScript的。

重要

使用 JavaScript 远程处理时,在开发过程中保持 JavaScript 控制台处于打开状态。错误和 JavaScript 远程处理遇到的异常将记录到 JavaScript 控制台(如果已启用),否则会以静默方式忽略。

当方法引发异常时 由于编程错误或其他故障,Apex 堆栈跟踪将返回到 对象中的浏览器。检查 堆栈跟踪在 JavaScript 调试器控制台中,或将其用于 响应回调函数。@RemoteActionevent下面是一个回调函数,当有 例外。

<script type="text/javascript">
function getRemoteAccount() {
    var accountName = document.getElementById('acctSearch').value;

    Visualforce.remoting.Manager.invokeAction(
        '{!$RemoteAction.MyController.getAccount}', 
        accountName, 
        function(result, event){
            if (event.status) {
                document.getElementById('acctId').innerHTML = result.Id
                document.getElementById('acctName').innerHTML = result.Name;
            } else if (event.type === 'exception') {
                document.getElementById("responseErrors").innerHTML = 
                    event.message + "<br/>\n<pre>" + event.where + "</pre>";
            } else {
                document.getElementById("responseErrors").innerHTML = event.message;
            }
        }
    );
}
</script>

JavaScript 远程处理限制和 考虑

尽管 JavaScript 远程处理不受某些资源限制的约束,但它还有其他资源限制 限制。

JavaScript 远程处理无法避免 Salesforce 服务限制。JavaScript 远程处理调用不是 受 API 限制的约束,但使用 JavaScript 远程处理的 Visualforce 页面受所有 标准 Visualforce 限制。

默认情况下,远程呼叫响应必须在30 秒,之后调用超时。如果您的请求需要更长的时间才能完成, 配置更长的超时时间,最多 120 秒。

这 请求(包括标头)的最大大小为 4 MB。远程呼叫响应最大大小为

15兆字节.如果你的 JavaScript 远程处理代码超出此限制,您有多种选择。

  • 减小每个请求的响应大小。仅返回以下数据 必填。
  • 将大数据检索分解为返回较小块的请求。
  • 若要减小批处理大小,请更频繁地发出批处理请求。
  • 使用非批处理请求。在远程处理请求配置块中设置。{ buffer: false }

Salesforce 会记录某些 JavaScript 远程处理调用中的错误消息。您可以防止个人 由于未在错误消息中包含客户数据而记录的信息。相反,抓住 异常并记录完整消息。然后向 Visualforce 返回一条用户友好的消息 页。

发出 Visualforce Remoting 请求时,将使用 “会话设置设置”页面中的组织范围超时值。超时未刷新 后续请求。如果不需要超时,则可以使用用户配置文件访问权限或 权限集访问权限。

JavaScript 远程处理 例

下面是一个基本示例,演示如何在 Visualforce 页面中使用 JavaScript 远程处理。首先,创建一个名为 Apex 的控制器:

AccountRemoter

global with sharing class AccountRemoter {

    public String accountName { get; set; }
    public static Account account { get; set; }
    public AccountRemoter() { } // empty constructor
    
    @RemoteAction
    global static Account getAccount(String accountName) {
        account = [SELECT Id, Name, Phone, Type, NumberOfEmployees 
                   FROM Account WHERE Name = :accountName];
        return account;
    }
}

其他 比注释,这看起来 与任何其他控制器定义一样。

@RemoteAction要使用此远程方法,请创建一个如下所示的 Visualforce 页面 这:

<apex:page controller="AccountRemoter">
    <script type="text/javascript">
    function getRemoteAccount() {
        var accountName = document.getElementById('acctSearch').value;

        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.AccountRemoter.getAccount}',
            accountName, 
            function(result, event){
                if (event.status) {
                    // Get DOM IDs for HTML and Visualforce elements like this
                    document.getElementById('remoteAcctId').innerHTML = result.Id
                    document.getElementById(
                        "{!$Component.block.blockSection.secondItem.acctNumEmployees}"
                        ).innerHTML = result.NumberOfEmployees;
                } else if (event.type === 'exception') {
                    document.getElementById("responseErrors").innerHTML = 
                        event.message + "<br/>\n<pre>" + event.where + "</pre>";
                } else {
                    document.getElementById("responseErrors").innerHTML = event.message;
                }
            }, 
            {escape: true}
        );
    }
    </script>

    <input id="acctSearch" type="text"/>
    <button onclick="getRemoteAccount()">Get Account</button>
    <div id="responseErrors"></div>

    <apex:pageBlock id="block">
        <apex:pageBlockSection id="blockSection" columns="2">
            <apex:pageBlockSectionItem id="firstItem">
                <span id="remoteAcctId"/>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem id="secondItem">
                <apex:outputText id="acctNumEmployees"/>
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:page>

通知 关于此标记的以下内容:

  • JavaScript 使用显式远程处理调用,并利用全局来解析正确的 远程操作方法的命名空间。invokeAction$RemoteAction
  • 仅当调用成功时,该变量才可用。错误 示例所示的处理特意简单,并打印错误 分别来自 和 值的消息和堆栈跟踪。我们鼓励你实现更可靠的 方法调用未调用的请求的替代逻辑 成功。event.statustrueevent.messageevent.where
  • 该变量表示 从 Apex 方法返回的对象。resultgetAccount
  • 访问纯 HTML 元素的 DOM ID 很简单,只需使用 项目。
  • Visualforce 的 DOM ID 组件是动态生成的,以确保 ID 是唯一的。代码 以上使用了使用$Component 从 JavaScript 引用组件,通过全局变量访问组件来检索组件的 ID。$Component

常见 Visualforce JavaScript 远程处理 API 验证 错误

了解如何更正对 Visualforce JavaScript Remoting 的常见无效调用 应用程序代码中的 API。

以下错误消息适用于 Apex 控制器验证:

  • Apex 命名空间 “” 中的具体实现 “” 接口 “” 没有权限 要使用。无法实例化 Apex 对象。CLASSNAMESPACEMETHOD_PARAMETER
    • 反序列化对象没有权限 在关联的命名空间中使用。查看 Apex 类删除不兼容的注释或 满足必须满足的缺失安全要求 满意。
  • 命名空间 “” 中的具体实现 “” 没有 实现 Apex 接口 “.” 无法实例化 Apex 对象。CLASSNAMESPACEMETHOD_PARAMETER
    • 确保反序列化的数据类型 Apex 对象与输入参数类型相同 Apex 控制器方法所期望的。

这些错误消息适用于 CSRF 令牌验证:

  • 远程处理请求的授权无效。请稍后再试。
    • 检查CSRF令牌是否成功发行。 如果需要,请请求另一个令牌。
  • 远程处理请求授权已过期。刷新页面,然后重试。
    • CSRF 令牌已过期。请求另一个 令牌,然后对请求使用新令牌。
  • 无法授权远程处理请求。刷新页面并 再试一次。
    • CSRF 令牌期间发生意外故障 验证。查看控制台日志和服务器 日志以识别故障点并确定 如何更正问题。
  • 远程处理请求授权对请求的方法无效。
    • 确认 CSRF 令牌有效。然后 确保 CSRF 令牌用于 与发布验证的方法相同。
  • 您没有执行 您请求的操作。请联系所有者 记录或您的管理员(如果需要访问)。
    • 通过令牌或授权进行访问 不成功,但没有任何信息 关于授权失败的原因。检查 控制台日志和服务器日志,用于提供有关 如何更正问题。
  • 授权远程处理请求时出错。刷新 页面,然后重试。
    • 验证期间发生不明问题。 确认您的访问权限有效,然后检查 控制台日志和服务器日志,用于提供有关 如何更正问题。

已知的 Visualforce Mobile 问题

Salesforce 发布已知问题以增强信任并支持客户成功。

Salesforce 客户支持和工程部门自行决定发布已知问题 根据客户报告的数量、问题的严重性以及 解决方法。如果您遇到的问题未列出,则它可能不符合以下条件 在已知问题网站上发布。并非所有已知的 bug 都会发布;通常 bug 会得到解决 快速或不影响客户。

  • 访问或权限问题 访问和权限问题
    会影响用户在 Salesforce 应用程序中看到的页面和记录。
  • 设备传感器问题 设备传感器问题包括移动设备的摄像头、麦克风和地理位置问题
  • 输入问题
    输入问题会影响用户使用 Salesforce 移动应用程序中的输入字段和选择器输入信息的方式。
  • 加载和性能问题 加载和性能问题
    会影响 Salesforce 移动应用程序的响应速度和加载速度。
  • 导航问题
    这些问题会阻止用户导航到 Salesforce 移动应用程序中的某些页面。
  • 网络问题 网络问题
    会影响 Salesforce 移动应用程序的连接。
  • Salesforce Classic 与 Lightning Experience 问题
    这些问题是由 Salesforce Classic 和 Lightning Experience 之间的切换引起的。
  • 更新记录问题
    这些问题会影响尝试在 Salesforce 移动应用程序中更新记录的用户。
  • 用户界面问题
    这些问题会影响 Salesforce 移动应用程序的用户界面。

访问或权限问题

访问和权限问题会影响用户在 Salesforce 应用程序。

问题溶液
如果在 用户配置文件级别,用户无法访问 Visualforce 内容。而不是 Visualforce 内容, 用户会看到错误消息“您无法查看此页面,因为您没有 权限,或者因为该页面在移动设备上不受支持。本期独有 适用于 iOS 的 Salesforce 和适用于 Android 的 Salesforce。在用户配置文件上禁用高确定性,然后重新登录。在以下位置实现高保证 Salesforce 连接的应用程序级别,而不是用户配置文件级别,以继续强制执行 多重身份验证。
Experience Cloud 站点用户无法在转换操作中访问 Visualforce 覆盖 对于应用程序中的潜在客户。相反,他们会看到错误消息“您也无法查看此页面 因为您没有权限,或者因为该网页在移动设备上不受支持 设备。创建一个单独的 Visualforce 操作,用于使用相同的 Visualforce 转换潜在客户 页。

设备传感器问题

设备传感器问题包括移动设备的摄像头、麦克风和 地理位置。

问题溶液
移动设备的相机在输入字段的子浏览器窗口中不起作用。 相反,用户会看到黑屏。这个问题可能是 Apple 的错误,用于儿童浏览器 窗户。此问题仅适用于 iOS 版 Salesforce。SFSafariViewController在 Safari 移动浏览器中打开 Salesforce,方法是点击 Safari 中的图标 Salesforce 移动应用程序中子浏览器窗口的右下角。

输入问题

输入问题会影响用户如何使用 Salesforce 移动应用程序。

问题溶液
将显示使用 URL 内容源的列表按钮访问的 Visualforce 页面 输入选择器的样式不正确。例如,输入日期字段显示一个日历,其中 白底白枣。此问题仅适用于 Android 版 Salesforce。将列表视图 URL 按钮转换为具有 Visualforce 页面内容的列表视图按钮 源、Visualforce 选项卡或 Visualforce 操作。
如果用户点击输入字段,iOS 本机输入控件将保留在屏幕上 然后,标题后退箭头仍然激活这些控件。输入控件还可以 导航到其他页面时意外重新出现。此问题是 Salesforce 独有的 适用于 iOS。在 Salesforce 移动版中导航之前,请确保输入控件已关闭 应用程序。
在字段中长按后,Visualforce 输入字段冻结或不允许输入。 用户可以长按复制和粘贴、进行选择或更改光标位置。这 问题仅适用于 iOS 版 Salesforce。将以下 JavaScript 行添加到 Visualforce 页面的底部:window.onkeydown=function(){window.focus();}

加载和性能问题

加载和性能问题会影响 Salesforce 移动应用程序的响应速度,以及 加载速度有多快。

问题溶液
如果将 Visualforce 页面或 Lightning 选项卡设置为登录页面,则可能存在页面 加载错误和性能下降。错误消息“我们在加载时陷入了循环 该页面“或”加载此页面需要一段时间。您可以继续等待或重试” 出现。选择一个标准选项卡作为登录页面。
打开多个文件会导致 iOS 版 Salesforce 冻结。在 Safari 中使用 Salesforce 移动 Web。

导航问题

这些问题会阻止用户导航到 Salesforce 移动版中的某些页面 应用程序。

问题溶液
Visualforce 选项卡会在用户离开后加载登录页面。在切换应用或选择其他选项卡之前允许页面完全加载,或者选择 再次打开 Visualforce 页面选项卡。
用户在切换后返回 Canvas 页面时可能会看到空白页面 应用程序。重新加载 Canvas 应用。
如果发布者操作使用该函数对文件记录进行导航调用,则文件预览 窗口可能会在显示文件之前关闭。出现此问题的原因是该方法在导航调用之前触发 文件记录。此问题仅适用于 iOS 版 Salesforce。sforce.one.navigateToSObjectpublisher.close不要使用之前的通话 导航到文件记录。用户必须在以下时间后手动关闭发布者操作窗口 他们已完成对文件记录的处理。publisher.close
当用户强制退出应用时,记录类型选择页可能会显示不正确 从记录类型选择页面的正确版本。本期独有 适用于 iOS 的 Salesforce。导航回对象主页,然后再次点击新建。如果 问题仍然存在,请清除应用程序缓存或注销以重置行为。
使用导航到 iPad 上的对象主页。出现错误后,没有其他导航调用可以处理 页面,直到设备缓存被清除。清除缓存。
使用导航库从 Visualforce 页面按 ID 导航到笔记记录,然后点击取消或 保存会导致循环。如果此导航方法是从 Visualforce 操作调用的 记录详细信息页面,则“取消”、“保存”和“返回”按钮可能会返回到空白记录详细信息页面。sforce.one没有直接的解决方法。强制退出并重新启动应用程序。
视图状态 POST 请求存储在 Salesforce 移动应用程序导航历史记录中。 如果用户提交视图状态表单,导航到另一个 Visualforce 页面,然后单击 后退按钮,将再次处理 POST 请求。此问题会在 适用于 iOS 的 Salesforce,并导致浏览器应用程序中出现错误。

网络问题

网络问题会影响 Salesforce 移动应用程序的连接。

问题溶液
网络连接错误消息“请检查您的网络连接,然后重试” 当网络处于活动状态时,显示在 Visualforce 页面上。本期独有 适用于 iOS 的 Salesforce。关闭组织范围的设置“在 Salesforce 中启用缓存”。

Salesforce Classic 与 Lightning Experience 问题

这些问题是由 Salesforce Classic 和 Lightning 之间的切换引起的 经验。

问题溶液
UI 检查错误地返回,而不是在用户使用 移动设备上的经典 UI。Visualforce 全局和 Apex 类命令会出现此问题。Theme4tTheme3$User.UIThemeDisplayedUserInfo.getUiThemeDisplayed通过验证 JavaScript 对象是否可用来检查用户的当前 UI;此对象在 经典 UI。sforce.one

更新记录问题

这些问题会影响尝试在 Salesforce 移动版中更新记录的用户 应用程序。

问题溶液
用户无法在更新后立即保存 Salesforce 记录。相反 他们会看到错误消息“记录在编辑期间被 [当前用户] 修改 会期。记下您输入的数据,然后重新加载记录并输入您的更新 再次”。避免在立即调用 标准编辑页面。否则,用户必须等待 30 秒,直到缓存期过去, 在他们能够编辑记录之前。
编辑未读线索时会触发冲突检测。错误消息“这 记录在编辑会话期间被(用户更新潜在客户)修改。记下 您输入的数据,然后重新加载记录并再次输入您的更新”。打开记录并使用记录详细信息中的编辑按钮进行更改 页。
当 可选参数在 以下命令:.错误消息“查看此页上的错误。记录类型 ID: 此值对用户无效:可能会显示“[用户名]”。recordTypeIDrecordTypeIDsforce.one.createRecord(​entityName​[, recordTypeId]);修改对 要传入参数的 Visualforce 页面。sforce.one.createRecordrecordTypeId

用户界面问题

这些问题会影响 Salesforce 移动应用程序的用户界面。

问题溶液
Salesforce 移动应用程序标准后退导航按钮仅在点击时才会响应 两次。当编程调用(如 和)首先用于导航回一个页面,然后导航回用户时,会出现此问题 尝试使用标准的“后退”按钮返回另一页。本期独有 适用于 iOS 的 Salesforce。window.history.back()sforce.one.back()如果您的实现有多个页面,请将其设计为仅使用标准背面 按钮或仅编程调用。
当用户从记录详细信息页面中嵌入的 Visualforce 页面单击返回时, 页面不滚动。单击“返回”后等待几秒钟,离开记录并返回,或者 将设备从横向旋转到纵向。
某些 CSS 元素会导致 CancelPost 或 Save 按钮,或 用户界面,变得无响应。删除这些影响滚动的 CSS 元素:overflow-x: hidden;overflow-y: scroll;-webkit-overflow-scrolling: touch;
用户无法在 Salesforce for iOS 中创建和编辑页面的标准记录上滚动。 用户通常只有在链接到“创建”的 Visualforce 页面时才会遇到此问题 或“编辑”页面包含超出设备屏幕的内容。本期独有 适用于 iOS 的 Salesforce。减少 Visualforce 页面上包含“创建”或“编辑”的内容量 链接,以便用户无需在自定义页面上滚动
对象页面布局中嵌入的 Visualforce 页面不遵循用户定义的 高度。此问题仅适用于 iOS 版 Salesforce。从页面中取消选择嵌入式 Visualforce 页面上的显示滚动条选项 布局编辑器。
在 Safari 中滚动 Visualforce 页面时,页面会移动,但不会显示任何内容 新文本。此问题是 Apple 的错误。此问题仅适用于 iOS 版 Salesforce。UIWebView刷新页面。
Experience Cloud 站点用户会看到对象的标准新建按钮,即使页面 被 Visualforce 操作覆盖隐藏,并标记为不适用于移动设备。本期 是 iOS 版 Salesforce 独有的。没有已知的解决方案。在 Safari 中使用基于浏览器的 Salesforce 版本 iOS系统。
使用 Lightning 组件对对象进行视图覆盖会禁用滚动 通过父对象的相关列表访问时的整个页面。本期独有 适用于 iOS 的 Salesforce。没有已知的解决方案。通过对象主页选项卡或其他方式访问记录 手段,例如程序化导航。

在 Salesforce 中使用 Visualforce 的注意事项和限制 移动应用

Visualforce 允许开发人员构建复杂的自定义用户界面,这些用户界面可以 原生托管在闪电网络上。Visualforce 是 Salesforce 久经考验的模型, 使开发人员能够访问数据以及强大的工具和功能。有很多好处 在 Salesforce 移动应用程序中使用 Visualforce,但也有一些限制。

可用性

  • 易于实施,可提高生产率。
  • 以页面为中心的模型自然而然地将大型应用程序拆分为可管理的小页面。

与 Salesforce 平台和其他工具集成

  • 访问 Salesforce 丰富的元数据基础架构。
  • 标准控制器允许直接访问对象,也可以通过关系访问对象,而无需 执行单个查询。
  • Visualforce 页面可以充当 JavaScript 或第三方框架的容器,例如 AngularJS 或 React。

定制

  • 标准选项卡、自定义对象选项卡和列表视图,这些选项卡被 Visualforce 覆盖 页面在 Salesforce 移动应用程序中不受支持。移动用户将看到默认的 Salesforce 对象的页面 相反。

互动

  • 有限的交互性(除了您自己添加的 JavaScript 功能)。
  • 难以打造身临其境的用户体验。

速度

  • 延迟较高,这会降低移动性能。
  • 与计算能力有限的低端或较旧的移动设备不匹配 资源。
  • Visualforce 在 Salesforce 服务器上处理标记标签,从而提高响应速度 时间。

one.app 容器

在 Salesforce Classic 中,Visualforce “拥有”页面, 请求和环境。Visualforce 是应用程序容器。但是在Salesforce中 移动应用程序和 Lightning Experience,Visualforce 在较大的 /lightning 容器内的 iframe 内运行。如果您习惯于在 Salesforce Classic 中进行开发,则使用 one.app 容器需要进行一些调整,主要是范围和安全注意事项。查看更多 信息,请参阅了解 Salesforce 移动应用程序 容器。

针对 Visualforce 页面问题准备支持请求 Salesforce 应用程序

Salesforce 提供资源来帮助开发人员找到问题的答案,并 解决他们的问题。我们建议您先看一下开发者论坛, Salesforce Stack Exchange 和“已知问题”页面,查看您是否可以立即找到 解决您的问题。如果您的问题仍未得到解答,您可以将案例提交至 Salesforce 的支持团队,他们会将您的问题发送给最佳人选来回答 它。

Salesforce 开发人员论坛

Salesforce 开发人员社区的论坛是任何 有关Salesforce平台和工具的问题。在以下社区中提问和回答问题 400 万 Salesforce 开发人员。

Salesforce 堆栈交换

Salesforce Stack Exchange 是面向 Salesforce 管理员的问答网站, 实施专家和开发人员。任何人都可以提出和回答问题。

已知问题

Salesforce 发布已知问题,通过提供以下功能来增强信任和客户成功 对已知 bug 的可见性。Salesforce 客户支持和工程发布已知 基于客户报告数量、问题严重性和 变通办法的可用性。并非所有已知的 bug 都符合发布条件。

提交支持请求

如果您在论坛上找不到问题的答案,请 Stack Exchange 或“已知问题”页面,下一步是提交支持案例。

  1. 在“帮助和培训”门户上登录您的帐户。
  2. “联系支持人员”磁贴下,单击“创建” 案例
  3. 在“帮助查找器”页面上,选择“开发”,然后选择“Apex/Visualforce”。
  4. 在“问题”选项卡上,检查您的问题是否包含在常见问题中 问题和答案。如果没有,请按 Log a New Case 图标 页面底部。

提交案例需要以下信息:

  • 用户名
  • 首选电子邮件地址
  • 电话号码
  • 时区
  • 大体时间
  • 回电日期
  • 业务影响

为了帮助 Salesforce 的专家快速解决您的案例,请总结重现的步骤 “说明”框中的错误。使步骤尽可能清晰具体。提供 演示该问题的最简单、最短的代码示例。通常,开发人员会解决 他们在简化繁殖过程中的问题。

此外,请考虑向 Salesforce 支持团队授予登录访问权限,以帮助他们进行调查 你的情况。要授予登录访问权限,请转到您的姓名/设置/我的个人信息/授予登录访问权限/选择 Salesforce.com 支持

检查支持请求

您可以查看已提交的支持案例的进度。

  1. 在“帮助和培训”门户上登录您的帐户。
  2. 在“我的成功中心”磁贴下,单击“转到”。
  3. 单击左侧导航栏中的“支持案例”。

选择有效的页面布局

设计在 Salesforce 移动版中美观且运行良好的 Visualforce 页面 应用程序,使用适合使用页面的上下文的页面布局。添加的页面 作为主导航选项卡或操作栏中的自定义操作,几乎可以使用完整的 屏幕,并且可以垂直滚动,而 Visusalforce 添加到 对象的页面布局必须适合特定的有限空间。

一般来说,添加到页面布局的 Visualforce 在只读的情况下效果最好。 信息一目了然。放置需要用户交互的功能,例如多字段 表单,在全屏页面上,通过将它们添加为主导航中的选项卡或自定义 操作栏中的操作。

页面布局上的 Visualforce

添加到对象页面布局的 Visualforce 页面将显示在记录中 详细信息页面。您可以控制 Visualforce 元素在 移动记录详细信息屏幕,将字段和其他记录详细信息放在上面,并且 在它下面,通过更改其在对象页面布局上的位置。视觉力 以这种方式添加的页面遵循相同的规则来对字段和其他 元素确实如此。

注意

本页图片来自上一页 Salesforce 移动应用程序,而不是新的 Salesforce 移动应用程序。

  1. 加载记录时会显示记录标头,但可以向上滚动 并由用户离开屏幕。在屏幕上时,它总共有 158 像素高 设备,并占用屏幕的整个宽度。你无法控制 显示记录标头。
  2. 记录控件和详细信息,由 Salesforce 自动生成 移动应用程序。
  3. 添加到对象页面布局的 Visualforce 页面。
  4. 将宽度设置为 100%;元素自动调整大小,减去一些填充 在两边。
  5. 通过设置 页面布局编辑器中项目的高度(以像素为单位)。视觉力 元素正好使用该高度,即使内容较短。在那 情况下,额外的区域是空白的。如果页面内容较高,则 内容被剪裁。最佳做法是,不要设置内联 Visualforce 页面要比您打算的最小设备屏幕高 支持。

虽然您可以将多个内联 Visualforce 页面添加到页面布局中,但它可以快速 成为滚动浏览它们以查看页面其余部分的用户体验挑战。 最佳做法是永远不要在 一排;将 Visualforce 元素与常规页面元素(例如字段)分开。 如果您需要全屏显示页面,请考虑将其移至自定义页面 操作。添加到页面布局的 Visualforce 页面会自动具有普通的 Salesforce 删除了标题和侧边栏。您可能会发现显式地将它们和 在开发页面时,完整的 Salesforce 站点样式表会关闭。 此外,如果您的网页使用 Google Maps API,Google 建议使用 HTML5 文档类型。下面是一个执行所有这些操作的标签 事情:

<apex:page>

<apex:page standardController="Warehouse__c"
    docType="html-5.0" showHeader="false" standardStylesheets="false">

全屏布局

添加到 Salesforce 移动应用程序导航菜单或自定义的 Visualforce 页面 动作到动作栏,几乎可以使用整个屏幕,允许更多 信息,以及更复杂的用户界面。

注意

本页图片来自上一页 Salesforce 移动应用程序,而不是新的 Salesforce 移动应用程序。

  1. 提供对主导航菜单的访问的 Salesforce 标头是42像素高。 标头的内容无法更改。
  2. 设备屏幕的其余部分专用于您的 Visualforce 页面。

在 Salesforce 移动应用程序中显示时,标准 Salesforce 标头和 侧边栏会自动删除。但是,用作自定义操作的 Visualforce 页面 与完整的 Salesforce 站点共享,并将页面添加到 导航可能会共享,也可能不会共享。与完整 Salesforce 站点共享的页面 不应明确删除标准的 Salesforce 标题和侧边栏,除非 删除标题和侧边栏是所有 Visualforce 的标准做法 网站。

用户输入和交互

使用 、 属性和传递 HTML 属性 创建适合移动设备的表单和用户界面,这些表单和用户界面高效且可利用 本机移动浏览器功能。

<apex:input>type

如果没有键盘和鼠标,用户可能很难填写标准 HTML 表单 并在移动设备(尤其是手机)上进行交互。对于 Visualforce 页面,这 不要使用 JavaScript 远程处理来发出请求,请选择 Visualforce 组件 着眼于移动用户的表单输入。您可以对 Visualforce 页面进行的其他更改都不会有 与利用新的 HTML5 和移动浏览器功能相比,对可用性的影响更大 以改进窗体和用户界面控件。

选择高效的输入元素

用于获取用户输入 尽可能。 是一个 HTML5 就绪、适合移动设备的通用输入组件,可适应 表单字段所需的数据。它甚至更灵活,因为它使用该属性允许客户端浏览器 显示适合类型的用户输入小组件,例如日期选择器,或使用 特定于类型的键盘,使在移动设备上输入输入变得更加容易。<apex:input><apex:input><apex:inputField>type

您还可以使用 为与 Salesforce 上的字段对应的值创建 HTML 输入元素 对象。 改编 HTML 生成与基础 sObject 字段的数据类型相对应。通常 这是您想要的,但如果不是,请使用该属性覆盖自动数据 类型检测。但是,请注意,这会生成大量 HTML,并且需要额外的 要加载的资源,这意味着它不是最有效的组件 通过移动无线连接。<apex:inputField><apex:inputField>type<apex:inputField>

使用 type 属性创建 适合移动设备的输入元素

在组件上设置属性,并且 ,如果您使用 IT – 显示特定于数据类型的键盘和其他输入用户界面小部件 在触摸屏上更易于使用。该值将传递到生成的 HTML 元素,用于在 Salesforce 应用程序。type<apex:input><apex:inputField><input>

当用户单步执行表单元素时,该表单元素的输入方法会进行调整 对于预期的数据类型。文本字段显示标准键盘、电子邮件字段 显示带有“@”符号和 分配给键的“.com”,日期字段显示日期选择器,依此类推 上。下面是一个表单示例,说明了这是如何实现的 工程:

<apex:form >
  
    <apex:outputLabel value="Phone" for="phone"/>
    <apex:input id="phone" value="{!fPhone}" type="tel"/><br/>
      
    <apex:outputLabel value="Email" for="email"/>
    <apex:input id="email" value="{!fText}" type="email"/><br/>
      
    <apex:outputLabel value="That Number" for="num"/>
    <apex:input id="num" value="{!fNumber}" type="number"/><br/>
      
    <apex:outputLabel value="The Big Day" for="date"/>
    <apex:input id="date" value="{!fDate}" type="date"/><br/>
      
</apex:form>

如 用户在表单字段中移动,通过点击它们或点击“下一步”按钮,键盘会更改以匹配预期 字段的数据。

这些特定于类型的键盘使使用者更容易填写表单 他们的移动设备。

<apex:input>允许设置以下显式值:

type

  • 日期
  • 日期时间
  • 日期时间-本地
  • 时间
  • 电子邮件
  • 范围
  • 搜索
  • 电话
  • 发短信
  • 网址

您还可以设置为 auto,并且 使用关联的控制器属性或方法的数据类型。

type

HTML 属性,包括新的 HTML5 功能,是 HTML 的标准部分。有关属性的其他详细信息、可以使用它的用途以及用途 与移动开发相关,请参阅 WHATWG 的输入类型属性值和说明列表。 并非所有值都支持 Visualforce 输入 组件。如果要使用 Visualforce 不支持的值,请使用 static HTML 而不是 Visualforce 标记。typetype

使用 HTML5 传递属性进行客户端验证

将您和其他 Visualforce 组件上的直通属性设置为 启用其他 HTML5 功能,例如客户端验证。通过执行基本 验证,可以避免向服务器发送请求,并且 等待响应,当表单上有易于纠正的错误时。<apex:input>传递前缀为 到生成的 HTML,并删除前缀。启用客户端 验证,设置属性 要匹配预期的标记 表单值。这将向 生成的标记,启用 该字段的客户端验证。

html-html-pattern<apex:input>pattern<input>

注意

客户端验证要求 Visualforce 页面 设置为 API 版本 29.0 或更高版本,并将页面设置为 .docTypehtml-5.0验证模式是正则表达式。根据表单输入进行检查 表达式,如果匹配,则认为字段输入有效。如果它 不匹配,则输入被视为无效;错误消息是 ,表单将不会提交到服务器。这是一个 需要来自特定电子邮件地址的字段示例 域:

<apex:input id="email" value="{!fText}" type="email"
    html-placeholder="you@example.com"
    html-pattern="^[a-zA-Z0-9._-]+@example.com$"
    title="Please enter an example.com email address"/>

其他可设置为直通属性的有用 HTML5 属性包括:

  • placeholder(使用属性设置)- 添加 幻影文本添加到字段中,以向用户显示示例输入。html-placeholder
  • title(使用 on 的属性和组件的属性进行设置 without a title attribute) – 添加一条错误消息,以便在以下情况下使用:字段 客户端验证失败。title<apex:input>html-title

有关如何使用属性来增强 HTML 元素可用性的灵感,HTML5 表单简介和新增功能 Attributes 是对 HTML5 中新功能的一个很好的调查。为了进一步 详细信息,尤其是对于移动用户的详细信息,以及客户端表单验证的详细信息, 请参阅客户端表单验证和改善移动设备上的用户体验 WHATWG 的 HTML:生活标准中的设备。<input>

管理导航

Salesforce 移动应用程序使用事件管理导航。导航事件 framework 作为 JavaScript 对象提供,它提供了许多实用程序 使创建“正常工作”的编程导航变得轻而易举的功能。这 优势是更适合移动环境的导航体验。它还使 创建完成后导航,例如在订单完成后重定向到订单页面 成功提交,对 Salesforce 开发人员来说更容易。在 Salesforce 移动应用程序中,Visualforce 页面的程序化导航通常 工作原理如下:

  1. 用户调用 Visualforce 页面,通常从导航菜单或 操作栏中的操作。
  2. Visualforce 页面加载并运行,包括任何自定义控制器或 页面调用的扩展代码。
  3. 用户以某种方式与页面交互:例如,填写一些表单 值。
  4. 用户提交表单,或在页面上执行一些其他操作 提交更改。
  5. 控制器或扩展代码运行,将更改保存到 Salesforce,以及 返回操作的结果。
  6. Visualforce 页面使用 JavaScript 响应处理程序接收结果 操作,成功后,通过将用户重定向到新的 页面,显示其操作的结果。

应用的导航框架可以轻松处理此方案。

另一个常见的用例是简单地将链接或其他用户界面控件添加到 页面,从该 Visualforce 页面移动到应用程序中的另一个页面。此导航 也可以通过应用程序的导航框架轻松管理。

在这些情况下,导航由一个特殊的实用程序 JavaScript 对象 .当出现以下情况时,该对象会自动添加到所有 Visualforce 页面中 它们在 Salesforce 移动应用程序内运行。此对象提供了许多函数 在运行时触发导航事件。若要使用这些函数,可以调用它们 直接从页面的 JavaScript 代码中获取,也可以将调用作为点击处理程序附加到 元素。sforce.onesforce.one下面是一个 JavaScript 函数,用于创建要添加到 Google 的标记 地图。

function setupMarker(){ 

    // Use JavaScript nav function to determine if we are
    // in the Salesforce mobile app and set navigation link appropriately
    var warehouseNavUrl = 
        'sforce.one.navigateToSObject(\'' + warehouse.Id + '\')';

    // Wrap the warehouse details with the link to 
    // navigate to the warehouse details
    var warehouseDetails = 
        '<a href="javascript:' + warehouseNavUrl + '">' + 
        warehouse.Name + '</a><br/>' + 
        warehouse.Street_Address__c + '<br/>' + 
        warehouse.City__c + '<br/>' + 
        warehouse.Phone__c;
   
    // Create a panel that will appear when a marker is clicked
    var infowindow = new google.maps.InfoWindow({ 
        content: warehouseDetails
    });
   
    // ...
}

这 第一行构建一个字符串 ,当用作 JavaScript URL,导航到仓库的详细信息页面。链接已创建 ,并显示在单击标记时显示的信息面板(放在字符串中)。 单击仓库名称将带您进入该仓库的详细信息页面(省略 函数代码的一部分涉及 Google Maps API 调用以创建标记和 将其添加到地图中)。

warehouseNavUrlwarehouseDetails如果您有在 Salesforce 移动应用程序中运行的 JavaScript 代码或 HTML 标记, 请记住以下注意事项:

  • 不要使用 直接操作浏览器 URL。这与应用程序的 导航管理系统。window.location.href
  • 不要在导航 URL 中使用; 您无法在应用程序内打开新窗口。target=”_blank”

Canvas 框架中的导航方法

如果您使用的是 Canvas,则有一种更简单的方法来控制 Canvas 周围的导航 Salesforce 移动应用程序中的应用程序和画布个人应用程序。

您可以使用 Lightning Platform 方法控制应用程序中的导航。Canvas 框架中的这些方法是 驻留在 JavaScript 库中的事件。调用导航方法之一时 从画布代码中,您将一个事件发送到 Salesforce,该事件读取有效负载并指示 用户到指定目标。

将导航方法引用为事件变量,并使用 名称和有效负载。例如:

var event = {name:”s1.createRecord”, payload: {entityName: “Account”, recordTypeId: “00h300000001234”}};

有关使用新方法的更多信息,请参阅用于 Canvas 的 Salesforce 移动应用程序导航方法 Canvas 开发人员指南中的应用。

  • 使用 sforce.one 对象
    进行导航和消息传递 Salesforce Platform 包括用于导航和消息传递的事件机制。这在 Visualforce 中显示为名为 的 JavaScript 对象。它可以在 Salesforce 移动应用程序中显示的任何页面中使用。sforce.one
  • sforce.one 如何处理 API 版本 该对象在新版本
    中经常得到改进。为了保持向后兼容性,提供了特定于版本的行为,你可以在应用中使用特定版本的 。sforce.onesforce.onesforce.one

使用 sforce.one 对象进行导航和消息传递

Salesforce Platform 包括用于导航和消息传递的事件机制。这 在 Visualforce 中显示为名为 的 JavaScript 对象。它可在 Salesforce 移动版中显示的任何页面中使用 应用程序。

sforce.one

该对象提供以下功能 功能。使用对象中的点分表示法引用函数。例如:。sforce.onesforce.onesforce.one.navigateToSObject(recordId, view)

有关这些函数触发的基础事件的更多详细信息,请参阅Lightning Aura组件开发人员指南。

功能描述
back(​[refresh])导航到历史记录中保存的上一个状态。这相当于单击一个 浏览器的“后退”按钮。sforce.one刷新是可选的。默认情况下, 页面不刷新。通过刷新 页面(如果可能)。true
navigateToSObject(​recordId​[, view])导航到 sObject 记录, 由 recordId 指定。这个记录“home”有几个视图, 在 Salesforce 移动应用程序中以幻灯片形式提供,用户可以滑动 之间。view 是可选的,默认为 。视图指定幻灯片 在记录主页中最初显示。detail注意对应的记录 ID 不支持 ContentNote SObjects。可能的值如下所示。detail:记录详细信息幻灯片chatter:Chatter 幻灯片related:相关幻灯片的视图
navigateToURL(​url​[, isredirect])导航到指定的 URL。支持相对根 URL 和绝对 URL。相对 URL 是 相对于闪电网域根目录,并保留导航历史。例如 Visualforce 页面的相对根 URL 以正斜杠为前缀,例如 .支持或不支持的相对 URL。外部 URL(即 Lightning 域之外的 URL)在单独的浏览器中打开 窗。/apex/c__Listen../apex/c__Listenapex/c__Listen注意根据 用户的设备平台、设备设置、Salesforce 版本,以及 要打开的外部 URL 的身份验证要求,单独的 浏览器窗口可能需要身份验证或 重新身份验证。使用相对 URL 导航到 应用内的不同屏幕。使用外部 URL 允许用户访问 不同的网站或应用,他们可以在其中执行不需要保留的操作 在您的应用中。若要返回到应用,请返回由外部打开的单独窗口 当用户使用完其他应用时,必须关闭 URL。新窗口有 与应用不同的历史记录,当窗口 闭。这也意味着用户无法单击“后退”按钮返回到 应用程序;用户必须关闭新窗口。mailto:支持 、 、 和其他 URL 方案 启动外部应用程序并尝试“做正确的事”。但是,支持各不相同 通过移动平台和设备。 并且是可靠的,但我们建议您 在预期范围内测试任何其他 URL 设备。tel:geo:mailto:tel:isRedirect 是可选的,默认为 。将其设置为指示新 URL 应替换 导航历史记录。falsetrue当您从模式导航到 URL 时,例如从 为快速操作启用的组件,模态不会由 违约。要在导航时自动关闭模式,请设置为 。isredirecttrue注意在 or any 的处理程序中使用时要小心。即使 ,命令按钮的默认单击操作是 表单发布。在此方案中,命令按钮执行窗体发布和操作,要求用户 单击“后退”按钮两次以导航到上一页。为了防止 默认的单击操作,将处理程序配置为调用或返回 。navigateToURLonClick<apex:commandButton><button type=”submit”><input type=”submit”>isredirect=truenavigateToURLonClickevent.preventDefault()false注意与 ContentNote SObjects 对应的 URL 不是 支持。
navigateToFeed(​subjectId, type)导航到 指定的类型,范围限定为 subjectId。为 某些 Feed 类型,则 subjectId 是必需的 但被忽略了。对于这些 Feed 类型,请将当前用户的 ID 作为 subjectId。type 是源类型。这 可能的值如下所示。BOOKMARKS:包含保存为书签的所有提要项 由上下文用户提供。传递当前用户的 ID 作为 subjectId。COMPANY:包含除 类型。查看源 项,则用户必须具有对其父级的共享访问权限。 传递当前用户的 ID 作为 subjectId。TrackedChangeFILES:包含包含文件的所有源项 由上下文用户关注的人员或组发布。 传递当前用户的 ID 作为 subjectId。GROUPS:包含所有组中的所有源项 上下文用户拥有或属于其成员。 传递当前用户的 ID 作为 subjectId。NEWS:包含上下文用户关注的人员、用户所属组的所有更新 用户关注的成员,以及用户正在关注的归档和记录。包含所有更新 对于其父级为上下文用户的记录。 传递当前用户的 ID 作为 subjectId。PEOPLE:包含所有人发布的所有提要项 上下文用户跟随。 传递当前用户的 ID 作为 subjectId。RECORD:包含其父项为 指定的记录,可以是组、用户、对象、文件或任何其他 标准或自定义对象。当记录为组时,源还包含 提及该组的 Feed 项目。当记录是用户时,源 仅包含该用户的源项。您可以获取其他用户的记录 饲料。将记录的 ID 作为 subjectId 传递。TO:包含提及上下文用户的所有源项。包含饲料 上下文用户评论的项目和上下文用户创建的源项目 被评论。 传递当前用户的 ID 作为 subjectId。TOPICS:包含包含 指定的主题。将主题的 ID 作为 subjectId 传递。此值为 仅在 Salesforce 移动 Web 版中受支持。主题在 适用于 iOS 的 Salesforce 或适用于 Android 的 Salesforce。
navigateToFeedItemDetail(​feedItemId)导航到特定源 item、feedItemId 和任何关联的注释。
navigateToRelatedList(​relatedListId, parentRecordId)导航到相关列表 parentRecordId。例如,要显示 Warehouse 对象,parentRecordId 为 。Warehouse__c.IdrelatedListId 是要显示的相关列表的 API 名称或 ID。
navigateToList(​listViewId​, listViewName, scope)导航到以下列表视图 由 listViewId 指定,listViewId 是列表视图的 ID 显示。listViewName 设置列表视图的标题。它 不需要与为列表视图保存的实际名称匹配。要使用 保存名称,将 listViewName 设置为 null。将 scope 设置为视图中 sObject 的名称,例如, “帐户”或“MyObject__c”。
createRecord(​entityName​[, recordTypeId][, defaultFieldValues])打开页面以创建记录 对于指定的 entityName,例如,“Account”或 “MyObject__c”。recordTypeId 是可选的,并指定 所创建对象的记录类型。在不提供 recordTypeId 的情况下调用 createRecord 可能会导致 错误。defaultFieldValues 是可选的,如果提供, 在记录创建面板上预填充字段,包括未显示在 面板。用户必须对具有预填充值的字段具有创建访问权限。 保存期间由字段访问限制导致的错误不显示错误 消息。
editRecord(​recordId)打开页面以编辑记录 由 recordId 指定。
showToast({toastParams})显示 Toast。Toast 显示一条消息 在视图顶部的标题下方。该对象设置 Toast 的属性。使用任何属性 可用于 Aura 活动。为 例:toastParamsforce:showToastsforce.one.showToast({ "title": "Success!", "message": "The record was updated successfully." });
publish(​messageChannel,​message)使用 闪电消息服务。请参阅在消息通道上发布。
subscribe(​messageChannel,​function)使用 Lightning 消息服务订阅 messageChannel。当订阅上的消息时,提供的功能运行 消息通道已发布。该函数返回一个可与 一起使用的订阅对象。请参阅订阅和 取消订阅消息频道。subscribe()unsubscribe()
unsubscribe(​subscription)从消息通道中取消订阅对象的订阅。请参阅订阅和 取消订阅消息频道。

使用对象时,请记住以下几点:

sforce.one

  • 调用 可能会导致 如果 URL 引用了对象或 Chatter 的标准页面,则出现“不支持的页面”错误 页面。为避免此错误,请确保 URL 以正斜杠开头 (/_ui 而不是 _ui)。sforce.one.navigateToURL
  • 该方法不 尊重 Visualforce 覆盖标准操作。sforce.one.createRecord
  • 开发人员可以使用该类来 控制 Salesforce 移动应用程序的导航。某些操作及其关联的 URL 尚不完全受支持。 例如,使用操作 克隆或编辑可能无法按预期工作。计划在将来的版本中提供全面支持。pageReferencepageReferencestandard_recordPage

sforce.one 如何处理 API 版本

对象经常改进 在新版本中。为了保持向后兼容性,提供了特定于版本的行为,您可以使用特定的 版本。

sforce.onesforce.onesforce.one

默认情况下,使用与 请求的 Visualforce 页面的 API 版本。例如,如果 Visualforce 页面 API 版本为 30.0,该页面上默认使用的 JavaScript 使用 API 版本 30.0 的 。sforce.onesforce.onesforce.one

这意味着,当 Visualforce 页面更新到新的 API 版本时,该页面 自动使用 的更新版本。在前面的示例中,如果该 Visualforce 页面已更新 到 API 版本 31.0,则使用 API 版本 31.0 的应用功能。sforce.onesforce.onesforce.one如果新 API 版本中的更新行为导致页面功能出现兼容性问题,则 有三个选项来纠正问题。

sforce.one

  • 将 Visualforce 页面的 API 版本恢复到以前的版本。此操作 无需更改代码。
  • 更新页面功能的代码以解决问题。此解决方案是 最好,但它可能需要一些调试,并且肯定需要代码 变化。
  • 使用特定版本的 。 此解决方案通常需要最少的代码更改。sforce.one

注意

sforce.one在 14 年冬季添加 (API 版本 29.0),直到 14 年夏季(API 版本 31.0)才进行版本控制。版本 31.0 之前的所有版本都是 与版本 31.0 相同。您可以为对 Visualforce 有效的任何版本指定一个版本,即从 版本 15.0 设置为当前 API 版本。sforce.onesforce.one

使用特定版本的 sforce.one

要使用特定版本的 ,请使用 函数和 为其提供 API 版本和需要使用特定 的版本。适当的 的版本是自动的 由此调用加载。sforce.onesforce.one.getVersion()sforce.onesforce.one签名为:

sforce.one.getVersion()

sforce.one.getVersion(versionString, callbackFunction);

versionString 是应用程序所需的 API 版本。 它始终是两位数、句点和一位数字,例如“30.0”。无效的调用 版本字符串以静默方式失败。

callbackFunction 是一个 JavaScript 函数,它使用特定的 的版本。 经营 异步,回调函数在完成加载 请求的 版本。你 callback 函数接收单个参数,即指定 API 版本的对象。使用传递的对象 在而不是全局制作 对符合 API 的调用 应用所需的版本。sforce.onesforce.one.getVersion()sforce.onesforce.onesforce.onesforce.one

使用特定版本的 sforce.one 的示例

接下来的示例都将 Create Account 函数添加到以下输入中 按钮:

<input type="button" value="Create Account" onclick="btnCreateAccount()" id="btnCreateAcct"/>

默认为 Visualforce 页面的 API 版本应使用默认版本的应用代码 – 与 Visualforce 页面的 API 相对应的版本 版本 – 不需要请求版本。使用该版本会自动发生, 代码是 简单。

sforce.one

<script>
    function MyApp() {
        this.createAccount = function() {
            sforce.one.navigateToURL("/001/e");
        };
    } 

    var app = new MyApp();

    function btnCreateAccount() {
        app.createAccount();
    }
</script>

应用功能是在对象中创建的,然后事件处理函数在该事件 发生按钮咔嗒声。将应用程序功能与应用程序事件分离 处理是最佳做法,它为您设置了使用特定于版本的版本 之。MyAppsforce.one

使用特定的 sforce.one API 版本 (简单)要使用特定版本的 ,请获取 并保存对对象的版本化实例的引用。然后使用此对象可以 拨打电话。最简单的方法是 将其保存在对象中。在下一个 示例中,对 的版本化实例的引用位于 大胆。

sforce.onesforce.oneMyAppsforce.one

<script>
    function MyApp(sfone) {
        this.createAccount = function() {
            sfone.navigateToURL("/001/e");
        };
    } 
        
    var app30 = null;

    function btnCreateAccount() {
        // Create our app object if not already defined
        if(!app30) {
            // Create app object with versioned sforce.one
            sforce.one.getVersion("30.0", function(sfoneV30) {
                app30 = new MyApp(sfoneV30);
                app30.createAccount();
            });
            return;
        }
        app30.createAccount();        
    }
</script>

在前面的示例中,事件处理函数是从第一个扩展而来的 示例,以包括创建特定于版本的 实例。如果您的应用需要混合多个 versions,您可以创建多个具有适当版本和名称的实例。不过,不止一两个是 管理起来很麻烦。我们建议改用下一种方法。sforce.oneMyApp

使用特定的 sforce.one API 版本 (最佳)组织应用代码的更好方法是在应用初始化中创建特定于版本的实例 代码块,以便您可以保留事件处理 分开。

sforce.one

<script>
    function MyApp(sfone) {
        this.createAccount = function() {
            sfone.navigateToURL("/001/e");
        };
    } 
        
    var app30 = null;

    // Initialize app: get versioned API, wire up clicks
    sforce.one.getVersion("30.0", function(sfoneV30) {
        // Create app object with versioned sforce.one
        app30 = new MyApp(sfoneV30);

        // Wire up button event
        var btn = document.getElementById("btnCreateAcct");
        btn.onclick = btnCreateAccount;
    });

    // Events handling functions
    // Can't be fired until app is defined
    function btnCreateAccount() {
        app30.createAccount();
    }
</script>

在此示例中,应用初始化仅由空格和注释分隔。 但是你可以把它分成几个函数,以便更好地封装。

使用特定的 sforce.one API 版本 (同步)您可以通过在页面上手动包含特定版本的 JavaScript 来触发同步模式。这 库 URL 的格式 是:/sforce/one/sforceOneVersion/api.js。 下面是一个示例:

sforce.onesforce.one

<script src="/sforce/one/30.0/api.js"></script>
<script>
    function MyApp(sfone) {
        this.createAccount = function() {
            sfone.navigateToURL("/001/e");
        };
    } 
        
    var app = null;

    sforce.one.getVersion("30.0", function(sfoneV30) {
        app = new MyApp(sfoneV30);
    });

    // Events handling function
    // Can't be fired until app is defined
    function btnCreateAccount() {
        app.createAccount();
    }
</script>

虽然有些情况需要同步模式,但异步版本是 首选。如果您忘记手动包含正确版本的库,您的代码将包含错误 难以诊断。sforce.one

Salesforce Lightning Design System 简介

Salesforce Lightning Design System (SLDS) 可帮助您构建具有外观的应用程序 和 Lightning Experience 的感觉,无需编写任何一行 CSS。SLDS 是一个 CSS 框架 这使您可以访问我们的开发人员用于创建的图标、调色板和字体 闪电体验。

Lightning Experience UI 核心原则

SLDS 所代表的 Lightning Experience UI 采用四核设计精心打造 原则。我们鼓励您在开发应用程序时牢记这些内容。

  • 清晰度 — 消除歧义。使人们能够看到、理解和行动 信心。
  • 效率 — 简化和优化工作流程。智能预测需求 帮助人们更好、更智能、更快速地工作。
  • 一致性 — 通过应用相同的方法创造熟悉感并加强直觉 解决同一问题。
  • 美丽 — 通过深思熟虑和 优雅的工艺。

SLDS 的优势

SLDS 为您提供了创建符合原则、设计语言和 Lightning Experience 的最佳实践。以下是使 SLDS 如此有用的优点:

  • 在扩展现有功能时,它提供了统一的体验和简化的工作流程 或与外部系统集成。
  • 它不会过度强制执行默认值,例如填充和边距。
  • 它会不断更新。只要您使用的是最新版本的 SLDS,您的页面 与 Lightning Experience 一致。
  • 它包括 CSS 框架中的可访问性。
  • 它适用于其他 CSS 框架,如 Bootstrap。
  • 将 SLDS 应用于 Visualforce 页面 您可以使用 Lightning 设计系统 (SLDS) 构建与 Salesforce 移动应用程序的外观相匹配的 Visualforce 页面
    。要使用 SLDS,需要对代码进行一些调整,并需要记住一些事项。在大多数情况下,使用 SLDS 的 Visualforce 代码可以正常工作。
  • 在 Visualforce
    中使用 SLDS 图标 Lightning 设计系统 (SLDS) 包括 PNG 和 SVG(包括个人和 spritemap)版本的动作、自定义、文档类型、标准和实用程序图标。
  • 使用 SLDS 为 Salesforce 移动应用程序创建 Visualforce 页面 让我们创建一个 Visualforce 页面,该页面显示您最近访问的帐户,并使用 Lightning 设计系统 (SLDS) 进行样式设置,并将其添加到移动导航菜单中。
  • 使用 SLDS
    的响应式页面设计 响应式设计是一种网页设计方法,旨在创建在线用户界面,在各种屏幕尺寸上提供最佳的查看体验,包括轻松阅读和导航。

将 SLDS 应用于 Visualforce 页面

您可以使用 Lightning 设计系统 (SLDS) 构建与 Salesforce 移动应用程序的外观。要使用 SLDS,需要对代码进行一些调整,然后 要记住的事情很少。在大多数情况下,使用 SLDS 的 Visualforce 代码无需 问题。

在 Visualforce Pages 中使用 SLDS

每次使用 SLDS 时,请添加到 页面并将代码包装在范围类中。<apex:slds /><div class=”slds-scope”>…</div>

<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">

  <!-- Import the Design System style sheet -->
  <apex:slds />

    <!-- REQUIRED SLDS WRAPPER -->
    <div class="slds-scope">

我们讨论的 Visualforce 移动开发中的许多最佳实践都适用于此处 也。Apex 标记,例如 和 尚不支持使用 使用 SLDS。<apex:pageblock><apex:inputField>

SLDS 类命名

SLDS 使用称为 Block-Element-Modifier 语法 (BEM) 的标准类命名约定来 使类名不那么模棱两可。

  • 块表示高级组件(例如,)。car
  • 元素表示组件 () 的后代。car__door
  • 修饰符表示块或元素的特定状态或变体 ()。car__door–red

在 Visualforce 中使用 SLDS 图标

闪电设计系统 (SLDS) 包括 PNG 和 SVG(包括个人和 spritemap) 我们的 action、custom、doctype、standard 和 utility 图标的版本。

要在 Visualforce 页面中使用 SVG 精灵图图标,请将属性添加到标签中。xmlns=”http://www.w3.org/2000/svg” xmlns:xlink=”http://www.w3.org/1999/xlink”<html>

<span class="slds-icon_container slds-icon-standard-account" title="description of icon when needed">
 
  <svg aria-hidden="true" class="slds-icon">
 
    <use xlink:href="{!URLFOR($Asset.SLDS, 'assets/icons/standard-sprite/svg/symbols.svg#account')}"></use>
  </svg>
 
   <span class="slds-assistive-text">Icon Assistive Text</span>

</span>

由于图标是独立的并且具有含义,因此我们将其放置在带有 .slds-icon_container class

图标没有开箱即用的背景颜色。要设置背景颜色,我们应用第二个 类到跨度。要对特定图标使用默认颜色,请构造 图标的特定实用程序类,通过连接 、 Sprite 映射名称和 . 将该类应用于元素。在示例中 我们使用“标准”精灵映射和“帐户”图标,因此该类是 .slds-icon--icon<span>slds-icon-standard-account

在 中,我们有一个带有类的元素。元素反过来 包含一个 <use> 标记,该标记根据图标的属性指定要显示的图标。<span><svg>slds-icon<svg>xlink:href

要设置 xlink:href 路径:

  1. 从图标页面中选择要使用的图标。记下它属于哪个类别 (操作、自定义、文档类型、标准或实用程序)。
  2. 通过连接类别 sprite 来完成 xlink:href 属性 (例如,“standard-sprite”)、/svg/symbols.svg# 和特定图标 在其中(例如,“帐户”)。这为我们提供了路径 assets/icons/standard-sprite/svg/symbols.svg#account。

在标记后,辅助文本位于 span 与类。<svg>slds-assistive-text

使用 Salesforce 移动应用程序创建 Visualforce 页面 SLDS系列

让我们创建一个 Visualforce 页面,该页面显示您最近访问的帐户,并且是 使用闪电设计系统(SLDS)进行样式设计,并将其添加到移动导航中 菜单。

  1. 首先,让我们创建 Visualforce 页面。
  2. 打开开发者控制台,点击“文件”|”新品 |Visualforce 页面。输入 SLDSPage 作为页面名称。
  3. 在编辑器中,将任何标记替换为以下内容。<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0"> <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> <title>SLDS LatestAccounts Visualforce Page in Salesforce Mobile</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- Import the Design System style sheet --> <apex:slds /> </head> <apex:remoteObjects > <apex:remoteObjectModel name="Account" fields="Id,Name,LastModifiedDate"/> </apex:remoteObjects> <body> <!-- REQUIRED SLDS WRAPPER --> <div class="slds-scope"> <!-- PRIMARY CONTENT WRAPPER --> <div class="myapp"> <!-- ACCOUNT LIST TABLE --> <div id="account-list" class="slds-p-vertical--medium"></div> <!-- / ACCOUNT LIST TABLE --> </div> <!-- / PRIMARY CONTENT WRAPPER --> </div> <!-- / REQUIRED SLDS WRAPPER --> <!-- JAVASCRIPT --> <script> (function() { var outputDiv = document.getElementById('account-list'); var account = new SObjectModel.Account(); var updateOutputDiv = function() { account.retrieve( { orderby: [{ LastModifiedDate: 'DESC' }], limit: 10 }, function(error, records) { if (error) { alert(error.message); } else { // create data table var dataTable = document.createElement('table'); dataTable.className = 'slds-table slds-table--bordered slds-text-heading_small'; // add header row var tableHeader = dataTable.createTHead(); var tableHeaderRow = tableHeader.insertRow(); var tableHeaderRowCell1 = tableHeaderRow.insertCell(0); tableHeaderRowCell1.appendChild(document.createTextNode('Latest Accounts')); tableHeaderRowCell1.setAttribute('scope', 'col'); tableHeaderRowCell1.setAttribute('class', 'slds-text-heading_medium'); // build table body var tableBody = dataTable.appendChild(document.createElement('tbody')) var dataRow, dataRowCell1, recordName, data_id; records.forEach(function(record) { dataRow = tableBody.insertRow(); dataRowCell1 = dataRow.insertCell(0); recordName = document.createTextNode(record.get('Name')); dataRowCell1.appendChild(recordName); }); if (outputDiv.firstChild) { // replace table if it already exists // see later in tutorial outputDiv.replaceChild(dataTable, outputDiv.firstChild); } else { outputDiv.appendChild(dataTable); } } } ); } updateOutputDiv(); })(); </script> <!-- / JAVASCRIPT --> </body> </html> </apex:page>
    • 标签允许 访问 SLDS 样式表。该组件是 将 SLDS 作为静态资源上传并在 Visualforce 中使用它 页面。<apex:slds />
    • 包装器对于任何 SLDS 样式的内容都是必需的。仅限 SLDS 样式 应用于其中包含的元素。<div class=”slds-scope”>
  4. 此页面也适合移动设备使用。让我们将页面添加到 Salesforce 移动菜单。
  5. 为移动应用启用页面。
    1. 在“设置”中,在“快速”中输入 Visualforce 页面 “查找”框,然后选择“Visualforce 页面”。
    2. 单击列表中 SLDSPage Visualforce 页面旁边的编辑
    3. 选择可用于 Lightning Experience、Experience 构建器网站和移动应用程序
    4. 点击保存
  6. 为 Visualforce 页面创建一个选项卡。
    1. 在“设置”中,在“快速查找”框中输入“选项卡”, ,然后选择选项卡
    2. 在“Visualforce 选项卡”部分中,单击“新建”。
    3. 在“Visualforce 页面”下拉列表中,选择“SLDSPage”。
    4. 在选项卡标签字段中,输入 SLDS 页面。请注意,“选项卡名称”字段是自动填充的
    5. 单击“选项卡样式”字段,然后选择“菱形”样式。此样式的图标显示为 Salesforce 移动导航菜单。
    6. 单击“下一步”,然后单击“下一步”, 然后保存
  7. 将选项卡添加到移动导航菜单。
    1. 在“设置”中,在“快速查找”中输入“移动应用” 框中,然后选择 Salesforce 导航
    2. 选择“SLDS 页面”选项卡,然后单击“添加”。SLDS 项将添加到“已选择”(Selected) 的底部 列表。
    3. 点击保存

使用 SLDS 的响应式页面设计

响应式设计是一种网页设计方法,旨在创建在线用户界面 在各种屏幕上提供最佳的观看体验,包括轻松阅读和导航 大小。

响应式用户界面通过使用流畅的、 基于比例的网格、灵活的图像和 CSS3 媒体查询。使用响应式设计,您可以 可以创建看起来很棒且在手机和平板电脑上运行良好的 Visualforce 页面。

标准 Salesforce 应用程序页面使用响应式设计来提供设备优化的布局。这 主要技术是电话的堆叠单列布局,以及并排的两列布局 平板电脑的布局。该页面对于所有设备都是相同的,并适应其屏幕尺寸 显示上。

SLDS电网系统

闪电设计系统 (SLDS) 使用基于 Flexbox 的网格来提供灵活的、 移动优先、与设备无关的脚手架系统。网格系统可让您划分页面 分成行和列,并为不同尺寸的屏幕定义布局变化。网格可以是 嵌套以创建复杂的布局。

网格系统由两部分组成,网格包装器(类)和其中的列(类)。默认情况下,列的大小相对于其内容。slds-gridslds-col

您还可以使用 SLDS 中的大小调整帮助程序手动指定列大小。它们使用一种格式,其中 X 表示分数 总空间 Y。例如,表示为可用空间的 50% 的宽度。使用手动调整大小类 帮助程序,您可以在以下网格中指定列比 – 2、3、4、5、6 和 12.slds-size–X-of-Yslds-size–1-of-2

使用 SLDS 创建响应式设计页面

  1. 打开开发者控制台,点击“文件”|”新品 |Visualforce 页面。输入页面名称。SLDSResponsivePage
  2. 在编辑器中,将任何标记替换为 以后。<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0"> <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> <title>SLDS ResponsiveDesign Visualforce Page in Salesforce Mobile</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- Import the Design System style sheet --> <apex:slds /> </head> <body> <!-- REQUIRED SLDS WRAPPER --> <div class="slds-scope"> <!-- PRIMARY CONTENT WRAPPER --> <!-- RESPONSIVE GRID EXAMPLE --> <div class="myapp"> <div class="slds-grid slds-wrap"> <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--1-of-4"> <div class="slds-box slds-box_x-small slds-text-align_center slds-m-around--x-small">Box 1</div> </div> <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--3-of-4"> <div class="slds-box slds-box_x-small slds-text-align_center slds-m-around--x-small">Box 2</div> </div> </div> </div> <!-- / RESPONSIVE GRID EXAMPLE --> </div> </body> </html> </apex:page>这 代码创建一个两列网格,其中两列分别为:
    • 移动屏幕上的全宽和垂直
    • 大小为 1:1,在小屏幕上并排显示(超过 480 像素)
    • 大小为 3:1,在更大的屏幕上并排显示(超过 768 像素)

在桌面和移动设备上查看此页面,了解响应式设计的实际效果。

使用 Visualforce 页面作为自定义操作

如果您的 Visualforce 页面用作自定义操作,请将其设计为可执行操作 根据标准控制者提供的单个记录,或找到记录并对其采取行动,或 记录检索到的自定义控制器代码。

对对象的自定义操作

添加为自定义的 Visualforce 页面 在对象类型的记录的上下文中调用对对象的操作。这 自定义操作将传递一个特定的记录 ID,即用户正在查看的记录 当用户单击自定义操作时。设计页面以对该特定记录执行操作 类型。

用作对象自定义操作的 Visualforce 页面必须使用标准 该对象的控制器。使用控制器 用于添加自定义代码的扩展,包括可以使用 JavaScript 调用的方法 远程处理。@RemoteAction

您的自定义代码可以做的不仅仅是对 原始记录。例如,“创建快速订单”自定义操作 搜索匹配的商品。然后,它会创建发票和明细项,所有 作为为部件创建订单的一部分。该逻辑发生在 原始客户记录 – 发票与客户记录相关 调用快速订单操作的位置。

当您重定向到内部 URL 时 对于您的组织,操作对话框将在完成后关闭或以编程方式关闭 导航离开。如果将重定向设置为指向外部 URL,则 由于外部 URL 在新浏览器中打开,因此行为可能会有所不同 标签。

自定义全局操作

用作全局的 Visualforce 页面 操作可以在许多不同的位置调用,并且没有特定的记录 与他们相关。他们有完全的行动自由,这意味着这取决于你 来编写代码。

更具体地说,用作全局操作的 Visualforce 页面不能使用任何标准控制器。您必须编写一个自定义控制器来处理该页面。你 代码可能会创建一条或多条记录、修改找到的记录等。

当全局操作时 完成后,用户将被重定向到作为 该动作或返回到他们开始的地方。

Visualforce 页面的性能调整

性能是移动 Visualforce 页面的一个重要方面。Visualforce 有一个 缓存机制,帮助您调整页面的性能。

若要为页面启用缓存,请使用以下语句:

<apex:page cache="true" expires="600">

页面缓存的参数包括:

属性描述
缓存指定浏览器是否应缓存页面的布尔值。如果不是 指定,缺省为 .false
到期指定缓存周期(以秒为单位)的整数值。

有关更多信息,请参阅 Developerforce 上的 Force.com 站点最佳实践

更多资源

以下是一些可帮助您调整 Salesforce 应用程序性能的更多资源:

  • 深入了解 Force.com Query Optimizer(网络研讨会)
  • 最大限度地提高 Force.com SOQL、报告和列表视图的性能(博客文章)
  • Force.com SOQL 最佳实践:空值和公式字段(博客 帖子)

将 Visualforce 添加到 Salesforce AppExchange 应用程序

您可以在自己的应用程序中包含 Visualforce 页面、组件或自定义控制器 为 AppExchange 创建。

与 Apex 类不同,托管包中 Visualforce 页面的内容不是 安装包时隐藏。但是,自定义控制器、控制器扩展和 自定义组件是隐藏的。此外,可以使用该属性将自定义组件限制为仅在命名空间中运行。access

Salesforce 建议您仅使用托管软件包来分发任何 Visualforce 或 Apex 组件。之所以提出此建议,是因为托管包接收一个唯一的命名空间,该命名空间是 自动附加到页面、组件、类、方法、变量、 等等。此命名空间前缀有助于防止安装程序的 组织。使用 Visualforce 页面创建包时,应考虑以下注意事项:

  • 如果组件上的属性是 包含在托管包中设置为 ,是 请注意以下限制:accessglobal
    • 组件上的属性不能是 更改为 。accesspublic
    • 所有必需的子组件 (将 required 属性设置为 true 的属性)必须将属性设置为 global。<apex:attribute>access
    • 如果在必需的子项上设置了该属性,则无法删除该属性,或者 改变。default<apex:attribute>
    • 您无法添加新的必需子组件。<apex:attribute>
    • 如果子组件上的属性设置为 ,则无法将其更改为 。access<apex:attribute>globalpublic
    • 如果子组件上的属性设置为 ,则无法更改该属性。access<apex:attribute>globaltype
  • 安装具有非全局组件的包时,在 安装程序:请参阅“组件不是全局的”,而不是组件的内容。在 此外,该元件不包括在元件引用中。
  • 如果为正在安装软件包的组织启用了高级货币管理, 使用和不能使用的 Visualforce 页面 安装。<apex:inputField><apex:outputField>
  • 作为 Salesforce AppExchange 应用程序的一部分包含的任何 Apex 都必须至少具有 75% 累积测试覆盖率。当您将软件包上传到 AppExchange 时,将运行所有测试 以确保它们运行没有错误。当包 安装。
  • 从版本 16.0 开始,如果您将托管 Apex 类用作 Visualforce 控制器, 此外,还需要将以下方法和属性的访问级别设置为“访问级别”,以供订阅者使用 他们:globalglobal
    • 自定义控制器的构造函数
    • Getter 和 setter 方法,包括用于输入和输出组件的方法
    • 获取和设置属性的特性

提示

如果自定义标签有翻译,请包括 通过显式打包所需的语言,在包中进行翻译。

当包含 Visualforce 页面的软件包安装到 组织,则页面从 vf.force.com、visual.force.com 或 visualforce.com 域提供 而不是 salesforce.com 域。这是为了防止恶意代码 在包中,以免影响您的数据。

管理 Visualforce 页面和组件的软件包版本设置

如果 Visualforce 标记引用已安装的托管软件包,则每个软件包的版本设置 保存 Visualforce 标记引用的托管包以帮助向后兼容。 这可确保随着托管包中的组件在后续包版本中的发展,一个 页面仍绑定到具有特定已知行为的版本。

包版本是一个数字,用于标识包中上载的组件集。这 版本号的格式为 (对于 例如,2.1.3)。在每个主要数字中,主要数字和次要数字增加到选定的值 释放。仅针对补丁版本生成和更新。 发布者可以使用包版本来正常发展其托管包中的元素 通过发布后续软件包版本,而不破坏现有的客户集成,使用 包。majorNumber.minorNumber.patchNumberpatchNumber配置 Visualforce 的包版本设置 页面或自定义组件:

  1. 编辑 Visualforce 页面或组件,然后单击版本 设置
  2. 为 Visualforce 页面或组件。此版本的托管包将继续 如果托管包的更高版本是 已安装,除非您手动更新版本设置。若要将已安装的托管包添加到设置列表,请选择一个包 从可用软件包列表中。仅当有 尚未与 页面或组件。
  3. 点击保存

使用包版本设置时,请注意以下事项:

  • 如果保存引用托管的 Visualforce 页面或自定义组件 包,而不指定托管包的版本,页面或 组件与最新安装的托管软件包版本相关联 默认情况下。
  • 您无法移除 Visualforce 页面,或者 托管包的组件版本设置(如果该包是 由页面或组件引用。使用显示 用于查找托管包位置的依赖项 引用。
  • 包订阅者可以使用包版本来引用已删除的组件。 包中的 Visualforce 页面始终使用其包的最新 API 版本。他们无法访问已删除的组件。

不同软件包中 Visualforce 页面的访问控制器

要从不同软件包中的 Visualforce 页面访问 Apex 控制器,请执行以下操作: 在 自定义控制器类。在第一代封装中,您只能开发一个托管 具有给定命名空间的包。在第二代封装中,您可以开发超过 一个具有相同命名空间的托管(或已解锁)包。默认情况下,Visualforce 页面 安装在包中的包无法从另一个包中的 Apex 类调用公共 Apex 方法 包。即使两个包位于同一命名空间中,也是如此。

@namespaceAccessible

下面是如何在 Apex 代码中包含注释的示例。@namespaceAccessible

@namespaceAccessible
public virtual class NsController {
    private String message;
    @namespaceAccessible
    public NsController() {
        this.message = 'default'; // init to non-blank value
    }
    @namespaceAccessible
    public virtual String getMessage() {
       return this.message;
    }
    @namespaceAccessible
    public virtual void setMessage(String msg) {
        this.message = msg;
    }
}

首先,在控制器上方添加注释,使其从 命名空间。您必须对控制器进行注释,以便任何方法 您添加到注释中也是可见的。然后,对于您希望可见的每个方法 在命名空间中,在方法之前添加注释。仅使用注释 对于您希望在包外部可见但在同一包内的方法 命名空间。@namespaceAccessible

使用 Visualforce 开发 Salesforce 应用程序

开发人员可以使用 Visualforce 向 Salesforce 扩展和添加新功能 移动应用程序。 使用 Visualforce 进行 Salesforce 开发,您可以访问 Salesforce 数据 并创建在 Lightning 平台上运行的集成体验。您可以创建 在桌面和移动设备之间共享的 Visualforce 页面,或 移动应用程序独有。

注意

Lightning 不支持 Visualforce 页面和自定义 iframe 在 iPad Safari 上体验。

通过针对 Salesforce 进行开发,您可以灵活地使用流程和工具 自定义您的应用程序。例如,您可以使用 Salesforce Lightning 设计系统来 创建符合以下原则、设计语言和最佳实践的应用 闪电体验。或者将 JavaScript 工具和第三方框架合并到 创建交互式用户体验。

在本节中,我们将介绍在 Salesforce 移动应用程序和最佳实践,用于创建功能强大、复杂的应用程序。

Salesforce 平台开发流程

用于在 Lightning Experience 和 Salesforce 移动应用程序中进行开发的流程 都是一样的。如果您习惯于针对 Salesforce Classic 进行开发,则 Lightning Experience 和 Salesforce 移动应用程序有一些差异,但其中大部分是 你很熟悉。

为 Salesforce 移动应用程序创建 Visualforce 页面时,请务必进行设置 正确的工具和测试环境。在本节中,我们将介绍最好的 Salesforce 移动平台开发流程的实践。

设置开发系统

Salesforce 提供了几种不同的工具和方法来编写、编辑和查看您的 法典。

选择编辑器

首先设置用于编写代码的工具。开发人员控制台、Salesforce Visual Studio Code 的扩展和安装程序编辑器在针对 Salesforce 应用程序、Lightning Experience 和 Salesforce Classic。唯一的例外是 Visualforce 开发模式页脚,仅在 Salesforce Classic 中可用。

查看 Visualforce 页面

在 Salesforce Classic 中,您可以使用 https:// yourInstance.salesforce.com/apex/PageName URL 查看页面 模式。此方法不适用于在 Lightning Experience 中查看 Salesforce 应用程序页面。 因为您使用直接 URL 访问查看的页面始终显示在 Salesforce Classic 中。

要在 Lightning Experience 中查看您的页面,请转到 https:// yourInstance.salesforce.com/lightning。这 访问特定 Visualforce 页面的最简单方法是为其创建一个选项卡,然后导航 通过应用程序启动器中的“所有项目”部分添加到该选项卡。

对于更长期的方法,请创建一个“开发中”应用,并添加和删除您的 Visualforce 在您工作时会切换到它。

  1. 在“设置”中,在“快速查找”框中输入, ,然后选择应用程序管理器Apps
  2. 单击“新建 Lightning 应用程序”,然后为您的页面创建自定义应用程序 正在开发中。注意请考虑将应用限制为仅系统管理员或配置文件 您已为组织中的开发人员创建。
  3. 在“设置”中,输入“快速查找”框,然后选择“应用菜单”。App Menu
  4. 确保“开发中”应用设置为“在应用中可见” 发射。
  5. 在“设置”中,在“快速查找”框中输入, ,然后选择选项卡Tabs
  6. 单击“Visualforce 选项卡”部分中的“新建”,然后创建自定义 选项卡,以获取当前正在开发的页面。使选项卡仅对开发用户可见 配置文件,然后仅将选项卡添加到“开发中”应用。
  7. 对要添加到“开发中”应用的每个页面重复上述步骤。

您还可以将以下书签添加到浏览器的菜单或工具栏以进行导航 直接进入您的页面。此 JavaScript 触发 Lightning Experience 事件,相当于在 经典 /apex/PageName URL。navigateToURL

javascript:(function(){ 
  var pageName = prompt('Visualforce page name:'); 
  $A.get("e.force:navigateToURL").setParams(
    {"url": "/apex/" + pageName}).fire();})();

开发过程和测试的重要性

在将 Visualforce 页面部署到生产环境之前,对其进行测试非常重要。测试 跨不同环境、设备和用户的页面。

如果您正在开发需要支持各种可能性的功能, 测试计划应考虑在以下方面进行测试的需要:

  • 每个不同的受支持设备。
  • 每个不同的受支持操作系统。
  • 每个不同的受支持浏览器,包括 Salesforce 移动应用程序,它嵌入了 有。
  • 每个不同的受支持用户界面上下文(Lightning Experience、Salesforce Classic、 和 Salesforce 移动应用程序)。

正常使用不支持在模拟器中运行 Salesforce 移动应用程序。我们了解 设备仿真器很方便。但它们不能替代对你的全面测试 组织支持的移动设备上的自定义应用和页面。在开发过程中, 定期在要部署的每个设备和平台上测试应用。

在 Salesforce 移动应用程序中测试 Visualforce 页面

如果您要创建将在 Lightning Experience、Salesforce Classic 中使用的页面、 和 Salesforce 移动应用程序,在您处理页面时查看所有环境中的页面。 要进行全面测试,请使用多个浏览器甚至多个设备来查看您的页面。你会 还希望能够访问至少一个额外的测试用户。

下面是如何设置开发环境的示例。

主要开发环境

在此环境中,您可以在安装程序中对组织进行更改,例如添加 自定义对象和字段,以及编写实际代码的位置(如果您使用 Developer) 安慰。在此环境中,查看页面在 Salesforce Classic 中的设计和行为。

  • 浏览器:
  • 用户:开发人员用户
  • 用户界面设置:Salesforce 经典

Lightning Experience 审查环境

在此环境中,您可以在 Lightning 中检查页面的设计和行为 经验。

  • 浏览器:Safari 或 Firefox
  • 用户:测试用户Your test user
  • 用户界面设置:闪电体验

Salesforce App Review 环境

此环境用于在 Salesforce 移动版中检查页面的设计和行为 应用程序。

  • 设备:iOS 或 Android 手机或平板电脑
  • 浏览器:Salesforce 应用程序
  • 用户:测试用户Your test user
  • 用户界面设置:Lightning Experience 或 Salesforce Classic

了解 Salesforce 移动应用程序容器

在 Salesforce Classic 中,Visualforce “拥有”页面, 请求和环境。Visualforce 是应用程序容器。但是在Salesforce中 移动应用程序和 Lightning Experience,Visualforce 在较大的 /lightning 容器内的 iframe 内运行。

注意

Salesforce 移动应用程序和 Lightning Experience 容器是否相同?是的,也不是。双 Salesforce 移动应用程序和 Lightning Experience 容器是 /lightning 容器的分支,为一个容器编写的代码可在另一个容器中使用。 但是容器的幕后工作方式略有不同。Salesforce 移动版 应用程序在移动设备的移动浏览器中运行,而 Lightning Experience 应用程序在 标准桌面浏览器中的桌面计算机。我们优化了发送到每个上下文的 /lightning 版本,以及 它运行的也足够不同,足以引起注意。简而言之,将它们视为不同的容器 具有几乎相似的功能。

外部容器和内部 iframe

外部 Salesforce 应用程序容器是通过 /lightning URL 访问的单页应用程序。加载 /lightning 页面,启动其代码,然后 应用程序代码接管环境。

Visualforce 页面在 HTML iframe 中运行,这实质上是一个单独的浏览器 主 /lightning 浏览内容中的窗口。

Salesforce 应用程序是父上下文,Visualforce 页面是子上下文。那 意味着 Visualforce 页面在 /lightning 外部容器的约束下工作,同时仍与 iframe。

Visualforce for Salesforce 应用程序代码注意事项

如果可能,请创建无论用户界面如何都能正常运行的 Visualforce 页面 上下文。通常,您为 Salesforce Classic 编写的 Visualforce 代码在 Salesforce 移动应用程序。但是,在某些情况下,需要对 由于容器,适用于移动设备的 Visualforce 页面。

安全注意事项

可能受影响的安全元素包括:

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

特别是,请注意会话维护,或管理浏览器使用的令牌 为每个请求输入用户名和密码的位置。您经常需要访问 当前会话使用全局变量 $Api.Session_ID。$Api.Session_ID 返回不同的值,具体取决于 请求,并且 Salesforce 移动应用程序和 Visualforce 页面从不同的域提供。 由于 Visualforce iframe 内部的会话 ID 与外部的会话 ID 不同, 在 Salesforce 移动应用程序容器中,这可能会更改您管理会话 ID 的方式。

范围注意事项

以下范围元素可能需要调整:

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

简而言之,Visualforce 页面中的 JavaScript 代码只能影响 iframe 的浏览器上下文,而不是父上下文。

Salesforce 移动应用程序容器中要避免的功能

Salesforce 移动应用程序容器可防止选定数量的 Visualforce 组件 在 Salesforce 移动应用程序中按预期运行。

  • 避免在 Visualforce 页面上使用 在 Salesforce 移动应用程序容器中。仅当您真正了解 iframe 时才使用此标记 以及它们如何影响 DOM 和 JavaScript。<apex:iframe>
  • 避免使用 或 等元素来访问父浏览器上下文,因为 Visualforce 和 Salesforce 移动应用程序由不同的域提供服务。contentWindowwindow.parent
  • 避免直接设置,因为 Visualforce iframe 无法直接访问 .window.locationwindow.location
  • 避免使用硬编码的 URL 来访问使用静态模式构建的 Salesforce 资源,例如 link = ‘/’ + accountId + ‘/e’。相反,在 Visualforce 标记中,请使用 ,在 JavaScript 中,请使用 .{!URLFOR($Action.Contact.Edit, recordId)}navigateToSObject(recordId)

告诉我更多:Visualforce 页面可以在哪些方面发挥作用 显示在 Salesforce 移动应用程序中

创建 Visualforce 页面时,可以从以下几个位置访问该页面 在用户界面中的位置。

  • 默认导航菜单,称为“仅限移动设备”,在以下情况下可用 您点击Salesforce 移动应用程序导航菜单图标屏幕底部的导航栏
  • 操作栏和操作菜单 – 可从任何页面的顶部使用 支持操作

您还可以引用并链接到 Visualforce 标记使用带有 sforce.one 对象的导航中列出的导航调用。请务必 选择可用于 Lightning Experience、Experience Builder 站点和 多页面流程中所有页面的移动应用程序。

如果引用的页面没有“可用于 Lightning” 体验、Experience Builder 站点和所选的移动应用程序,它不会阻止显示引用页面或父页面。 但是,当用户尝试访问未启用移动设备的页面时,他们会收到一个 “不支持的页面”错误消息。

准则和最佳实践

在 Salesforce 移动应用程序中,Visualforce 页面不会自动适合移动设备。这 标准 Salesforce 标题和侧边栏被禁用,取而代之的是移动控件,并且 JavaScript API 可用于使 Visualforce 页面能够与移动设备连接 导航管理。在其他方面,页面保持原样,尽管可以在 应用程序,以桌面为中心的 Visualforce 页面将感觉以桌面为中心。

幸运的是,让您的应用程序在 Salesforce 移动应用程序中看起来很棒很简单。你 可以修改您的代码,以便您的页面在完整的 Salesforce 站点和 移动应用程序,或者您可以创建特定于移动设备的页面。

注意

Lightning 不支持 Visualforce 页面和自定义 iframe 在 iPad Safari 上体验。在本章中,你将了解如何执行以下操作的最佳实践:

  • 在移动设备和桌面设备之间共享 Visualforce 页面。
  • 从移动设备或桌面设备中排除 Visualforce。
  • 为您的 Visualforce 页面选择最佳架构。
  • 为您的页面选择有效的页面布局。
  • 管理用户输入和导航。
  • 使用 Visualforce 页面作为自定义操作。
  • 调整页面以获得最佳性能。
  • 在移动版和桌面
    版之间共享 Visualforce 页面 修订显示在 Salesforce 移动应用程序和完整 Salesforce 站点中的 Visualforce 页面,以支持这两种环境。这包括用作自定义操作的 Visualforce 页面和添加到标准页面布局的 Visualforce 页面。
  • 从移动版或桌面
    版中排除 Visualforce 页面 要将 Visualforce 页面添加到 Salesforce 移动应用程序或整个 Salesforce 站点,请使用选项卡和导航设置。
  • 创建在移动和桌面
    上工作的 Visualforce 页面 通过编写适应其运行环境的代码,创建在 Salesforce 移动应用程序和整个 Salesforce 站点中都能正常运行的 Visualforce 页面。
  • 在 Salesforce 移动应用程序
    中为 Visualforce 页面选择架构 有几种方法可以设计和构建 Visualforce 页面,每种方法在开发时间、所需的开发人员技能以及您希望自定义功能与 Salesforce 移动应用程序匹配的程度方面都有不同的权衡。
  • 在 Salesforce 移动应用程序
    中优化 Visualforce 页面的性能 Visualforce 旨在为开发人员提供与标准 Salesforce 页面的功能、行为和性能相匹配的能力。如果您的用户遇到延迟、意外行为或其他专门围绕 Visualforce 的问题,您可以采取一些措施来改善他们的体验,还可以改进编码。在 Salesforce 移动应用程序中,遵循优化的最佳实践非常重要。移动设备的计算资源更加有限,用户希望应用程序速度更快、响应迅速。
  • Salesforce 移动应用程序中应避免的 Visualforce 组件和功能
    大多数核心 Visualforce 组件(命名空间中的组件)在 Salesforce 移动应用程序中正常运行。不幸的是,这并不意味着它们针对移动设备进行了优化,或者每个功能都适用于该应用程序。您可以通过遵循一些简单的规则来改善 Visualforce 页面的移动用户体验。apex
  • 已知的 Visualforce Mobile 问题 Salesforce 发布已知问题
    以增强信任并支持客户成功。
  • 在 Salesforce 移动应用程序中使用 Visualforce 的注意事项和限制 Visualforce 允许开发人员构建复杂的自定义用户界面,这些用户界面可以本地托管在 Lightning 平台上。
    Visualforce 是 Salesforce 久经考验的模型,使开发人员能够访问数据以及强大的工具和功能。在 Salesforce 移动应用程序中使用 Visualforce 有很多好处,但也有一些限制。
  • 针对 Salesforce 应用程序中的 Visualforce 页面问题准备支持请求 Salesforce 提供资源来帮助开发人员找到问题的答案并解决问题。
    我们建议您首先查看开发人员论坛、Salesforce Stack Exchange 和已知问题页面,看看您是否可以立即找到问题的解决方案。如果您的问题仍未得到解答,您可以向 Salesforce 的支持团队提交案例,该团队会将您的问题发送给最合适的人来回答。
  • 选择有效的页面布局 通过使用适合页面使用上下文的页面布局
    ,设计在 Salesforce 移动应用程序中看起来不错且运行良好的 Visualforce 页面。添加为主导航选项卡或操作栏中的自定义操作的页面几乎可以使用设备的全屏,并且可以垂直滚动,而添加到对象页面布局的 Visusalforce 必须适合特定的有限空间。
  • 用户输入和交互
    使用 、 属性和传递 HTML 属性创建适合移动设备的表单和用户界面,这些表单和用户界面高效且利用本机移动浏览器功能。<apex:input>type
  • 管理导航
    Salesforce 移动应用程序使用事件管理导航。导航事件框架以 JavaScript 对象的形式提供,该对象提供了许多实用函数,使创建“正常工作”的编程导航变得轻而易举。其优点是导航体验对于移动环境来说更自然。它还使 Salesforce 开发人员可以更轻松地创建完成后导航,例如在成功提交订单后重定向到订单页面。
  • Salesforce Lightning Design System 简介 Salesforce Lightning Design System
    (SLDS) 可帮助您构建具有 Lightning Experience 外观的应用程序,而无需编写任何一行 CSS。SLDS 是一个 CSS 框架,可让您访问我们的开发人员用于创建 Lightning Experience 的图标、调色板和字体。
  • 将 Visualforce 页面用作自定义操作 如果您的 Visualforce 页面用作自定义操作,请将其设计为对标准控制器提供的单个记录执行操作
    ,或查找并执行记录,或记录检索到的自定义控制器代码。
  • Visualforce 页面的性能调整 性能是移动 Visualforce 页面
    的一个重要方面。Visualforce 具有缓存机制,可帮助您调整页面的性能。

在移动设备和桌面设备之间共享 Visualforce 页面

修改 Salesforce 移动应用程序中显示的 Visualforce 页面和完整版 Salesforce 站点支持这两种环境。这包括用作自定义的 Visualforce 页面 操作和 Visualforce 页面已添加到标准页面布局中。

需要在两种环境中工作的 Visualforce 页面包括:

  • 用作自定义操作的页面。自定义操作显示在 Salesforce 的操作栏中 移动应用程序,以及完整 Salesforce 站点的发布者菜单中。
  • 当可用于 Lightning Experience 时,添加到正常页面布局的页面, Experience Builder 站点,并且已为页面启用移动应用程序。
  • 添加到正常页面布局的自定义 Visualforce 按钮或链接。
  • 标准按钮覆盖 Visualforce 页面,用于新建、编辑、查看、删除和 克隆操作。应用中不支持重写标准列表和选项卡控件。按钮 覆盖不会出现在应用程序中,除非可用于 Lightning Experience, Experience Builder 站点,并且已为页面启用移动应用程序。注意被 Visualforce 页面覆盖的标准按钮将从记录中消失 应用程序中的详细信息页面和记录列表(如果可用于 Lightning) 未选择体验、Experience Builder 站点和移动应用程序 覆盖相应按钮的 Visualforce 页面。

注意

Lightning 不支持 Visualforce 页面和自定义 iframe 在 iPad Safari 上体验。

从移动设备或桌面设备中排除 Visualforce 页面

将 Visualforce 页面添加到 Salesforce 移动应用程序或整个 Salesforce 站点,使用选项卡和导航设置。

可配置为仅限桌面或仅限移动设备的 Visualforce 页面包括:

  • 当可用于 Lightning Experience 时,添加到正常页面布局的页面, Experience Builder 站点,并且该页面的移动应用程序已禁用。仅这些 显示在完整的 Salesforce 站点中。
  • Visualforce 选项卡中使用的页面。将选项卡添加到移动导航中与添加选项卡是分开的 到完整的 Salesforce 站点导航。

创建适用于移动设备和 桌面

创建在 Salesforce 移动应用程序和 通过编写适应其运行环境的代码来完整 Salesforce 站点。

Salesforce 移动应用程序提供了一个用于处理各种导航控件的框架 和事件。当 Visualforce 页面在 完整的 Salesforce 站点,因为对象 仅注入到应用程序内的页面上。这意味着,对于共享的页面 Salesforce 移动应用程序和完整的 Salesforce 站点,您需要编写以下代码: 在对象可用时使用它,并且 标准 Visualforce 导航,如果不是。sforcesforce例如,下面是在 JavaScript 远程处理请求之后运行的一些 JavaScript 从创建快速订单的方法成功返回。此代码来自用作 自定义操作,将其添加到 Salesforce 移动应用程序中的操作栏,然后 完整 Salesforce 站点中的发布者菜单。它需要在这两个地方工作。意图 的代码是导航到订单所在帐户的详细信息页面 放置:

@RemoteAction

// Go back to the Account detail page
if( (typeof sforce != 'undefined') && sforce && (!!sforce.one) ) {
    // Salesforce app navigation
    sforce.one.navigateToSObject(aId);
}
else {
    // Set the window's URL using a Visualforce expression
    window.location.href = 
        '{!URLFOR($Action.Account.View, account.Id)}';
}

该语句检查对象是否可用和可用。这只是 如果页面在应用内运行,则为 true。如果可用,则使用移动导航管理系统进行 到帐户的详细信息页面。ifsforcesforce

如果对象不可用,请尝试 使用它导航到任何地方会导致 JavaScript 错误,并且没有导航。所以 相反,该代码使用 Visualforce 表达式设置窗口的 URL,该表达式返回 帐户详细信息页面的 URL。您不想在应用程序中执行此操作,因为 框架将丢失导航事件,但在正常情况下是必需的 视觉力。sforce

注意

最佳做法是将此类常见测试分解到他们自己的帮助程序中 功能。您可以向 JavaScript 静态资源添加如下内容: 然后直接打电话 您的 if 条件。然后,如果检测逻辑发生变化,只需在 一 地方。ForceUI.isSalesforce1()

(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};

    myContext.ForceUI.isSalesforce1 = function() {
        return((typeof sforce != 'undefined') && sforce && (!!sforce.one));
    }
})(this);

在 Salesforce Mobile 中为 Visualforce 页面选择架构 应用程序

有几种方法可以设计和构建 Visualforce 页面,每种方法都有不同的 在开发时间、所需的开发人员技能以及您的彻底程度方面进行权衡 希望您的自定义功能与 Salesforce 移动应用程序相匹配。对页面结构使用以下方法之一:

  • 标准 Visualforce 页面
  • 混合 Visualforce 和 HTML
  • JavaScript 远程处理和静态 HTML
  • 标准 Visualforce 页面 普通 Visualforce 页面
    在移动浏览器上呈现良好,可以按原样使用,与移动优化的网页相比,用户体验略有降低。页面的显示方式与在完整 Salesforce 站点上的显示方式相同,在视觉上与其他 Salesforce 应用程序功能不匹配。
  • 混合 Visualforce 和 HTML 将表单元素和输出文本的 Visualforce 标记与页面结构的静态 HTML
    相结合,以创建更适合移动设备的页面,使其更接近 Salesforce 移动应用程序的视觉设计。对于仅限移动设备的页面,您可以快速转换现有的 Visualforce 页面,但这不适用于同时在 Salesforce 移动应用程序和整个 Salesforce 站点中使用的页面。
  • JavaScript 远程处理和静态 HTML 将 JavaScript 远程处理和静态 HTML
    相结合,以提供最佳的用户体验,并提供与 Salesforce 移动应用程序匹配的最佳性能和用户界面。此体系结构避免了大多数 Visualforce 标记,而是在 JavaScript 中呈现页面元素。此选项需要最多的开发人员专业知识,并且设置时间可能比标准 Visualforce 或混合 Visualforce 和 HTML 要长一些。使用 Salesforce Mobile Pack 快速入门,并使用最新的移动 Web 应用程序技术。

标准 Visualforce 页面

普通的 Visualforce 页面在移动浏览器上呈现良好,可以按原样使用,使用 与移动优化网页相比,用户体验略有降低。页面显示为 它们将出现在完整的 Salesforce 站点上,并且在视觉上与其他 Salesforce 应用程序不匹配 特征。

局限性

用户体验的局限性包括:

  • 点击目标(按钮、链接、表单域等)针对鼠标进行了优化 光标,并且很难用指尖准确击中。
  • 视觉设计保持不变,可能不适合针对移动设备优化的现代视觉效果 Salesforce 移动应用程序的设计。

如果开发时间线过于紧迫,可能会发现这些限制 可以接受。

标准 Visualforce 页面示例

以下代码提供了标准 Visualforce 页面的示例,该页面允许用户 编辑仓库记录。编辑功能由标准控制器提供,用于 对象。

<apex:page standardController="Warehouse__c">

<apex:form>

  <apex:pageBlock title="{! warehouse__c.Name }">

    <apex:pageBlockSection title="Warehouse Details" columns="1">
      <apex:inputField value="{! warehouse__c.Street_Address__c }"/>
      <apex:inputField value="{! warehouse__c.City__c }"/>
      <apex:inputField value="{! warehouse__c.Phone__c }"/>
    </apex:pageBlockSection>
        
    <apex:pageBlockButtons location="bottom">
      <apex:commandButton action="{! quickSave }" value="Save"/>
    </apex:pageBlockButtons>
    
  </apex:pageBlock>

</apex:form>

</apex:page>

这 页面可以在 Salesforce 移动应用程序和完整的 Salesforce 站点中使用。它显示 作为两种上下文中的标准桌面 Visualforce 页面。

混合 Visualforce 和 HTML

将表单元素的 Visualforce 标签和输出文本与页面的静态 HTML 相结合 结构来创建更适合移动设备的页面,这些页面更接近 Salesforce 移动应用程序。对于仅限移动设备的页面,您可以快速转换现有的 Visualforce 页面,但这不适用于同时用于 Salesforce 移动应用程序和完整的 Salesforce 站点。

以这种方式设计的 Visualforce 页面仍然是“标准”的 Visualforce,在 他们使用标准请求-响应周期,标准控制器功能,用于表单字段, POSTBACK 和视图状态等。与创作页面的主要区别 完整的 Salesforce 站点是减少或消除对 Visualforce 标签的使用添加 结构,以支持静态 HTML。也就是说,用 、 、 等替换 、 、 等。<apex:inputField><apex:pageBlock><apex:pageBlockSection><div><p><span>

这种方法还需要创建 CSS 样式表来管理 页面元素,而不是使用内置的、自动应用的样式,当 您使用 Visualforce 组件。虽然这可能需要一些时间,但它可以让您做很多事情 更接近 Salesforce 移动应用程序的视觉设计。这也意味着 以这种方式设计的页面在视觉上与整个 Salesforce 站点匹配。

将此方法应用于 Visualforce 页面

要使用此方法创建要在 Salesforce 移动应用程序中使用的页面,请按照 一般规则很少。

  • 请勿使用以下 Visualforce 标记:
    • <apex:pageBlock>
    • <apex:pageBlockButtons>
    • <apex:pageBlockSection>
    • <apex:pageBlockSectionItem>
    • <apex:pageBlockTable>
  • 将 、 或 和 用于表单。<apex:form><apex:inputField><apex:input><apex:outputLabel>
  • 使用或 Visualforce 对于不可编辑的文本。<apex:outputText>
  • 使用首选的 HTML 来构造页面的结构:、、、、等。<div><span><h1><p>
  • 使用 CSS 样式来应用您喜欢的视觉设计。

优点和局限性

这种方法的优点包括:

  • 相当快的开发时间,并且您使用普通的 Visualforce 开发工具和流程。
  • 重新利用现有页面相当容易。
  • 您可以更紧密地匹配 Salesforce 移动应用程序的外观。

要记住的一些限制:

  • 这种方法使通常的 Visualforce 请求往返,具有更大的 数据有效负载,与使用 JavaScript 远程处理的完全移动优化方法相比。
  • 添加自动替换样式的 CSS 样式是一项额外的工作 添加者 和 相关组件。<apex:pageBlock>

混合 Visualforce 和 HTML 页面的示例

以下代码示例显示了一个混合的 HTML 和 Visualforce 页面,该页面允许用户 编辑仓库记录。编辑功能由标准控制器提供 对于 对象。

<apex:page standardController="Warehouse__c">

<style>
    html, body, p { font-family: sans-serif; }
</style>

<apex:form >

    <h1>{!Warehouse__c.Name}</h1>

    <h2>Warehouse Details</h2>

    <div id="theForm">
        <div>
            <apex:outputLabel for="address" value="Street Address"/>
            <apex:inputField id="address" 
                value="{! warehouse__c.Street_Address__c}"/>
        </div>
        <div>
            <apex:outputLabel for="city" value="City"/>
            <apex:inputField id="city" 
                value="{! warehouse__c.City__c}"/>
        </div>
        <div>
            <apex:outputLabel for="phone" value="Phone"/>
            <apex:inputField id="phone" 
                value="{! warehouse__c.Phone__c}"/>
        </div>
    </div>

    <div id="formControls">
        <apex:commandButton action="{!quickSave}" value="Save"/>
    </div>

</apex:form>

</apex:page>

这 页面可以在 Salesforce 移动应用程序和完整的 Salesforce 站点中使用。它 在整个 Salesforce 站点上显示为标准页面,但没有完整的 表单的 Salesforce 样式。在 Salesforce 移动应用程序中,它大致显示 与 Salesforce 移动应用程序的视觉风格相匹配。使用其他样式,页面 可以近似两个版本的视觉样式。

JavaScript 远程处理和静态 HTML

将 JavaScript 远程处理和静态 HTML 相结合,以提供最佳的用户体验,同时 与 Salesforce 移动应用程序相匹配的最佳性能和用户界面。这 架构避免了大多数 Visualforce 标记,而是在 JavaScript 中呈现页面元素。 此选项需要最多的开发人员专业知识,并且可能需要更长的时间来设置。 比标准 Visualforce 或混合 Visualforce 和 HTML。将 Salesforce Mobile Pack 用于 快速起步,并使用最新的移动 Web 应用程序技术。

重要

在可能的情况下,我们更改了非包容性条款,以符合我们的 平等的公司价值观。我们保留了某些条款,以避免对 客户实施。

以这种方式设计的 Visualforce 页面避开了许多自动、简化的功能 标准 Visualforce,有利于对请求-响应周期进行更多控制, 并使用 JavaScript 而不是页面重新加载来执行页面更新。这可以 大幅提高页面的性能,尤其是在较低的带宽上, 更高延迟的无线网络连接,使移动设备变得如此移动。 缺点是要编写的代码更多,并且您需要 JavaScript 方面的专业知识, JavaScript 远程处理、HTML5、您的移动工具包和 CSS,以及 Apex 和 视觉力。缺点的优点是,您正在使用最新、最 用于移动开发的高级工具,以及您可以构建的页面是最好的,大多数 “管理单元”自定义功能的完整方式,该功能与 应用程序。

您可以使用此方法构建桌面 Visualforce 页面以及 Salesforce 移动应用程序。甚至可以在两者之间共享此类页面 环境,尽管要紧密匹配 完整的 Salesforce 网站外观。最重要的是,你设计的页面可以完全 响应迅速,适应各种设备和外形规格。

将此方法应用于 Visualforce 页面

要使用此方法为 Salesforce 移动应用程序创建页面,请按照以下步骤操作 一般流程:

  1. 将您首选的 Salesforce Mobile Pack(在 Salesforce 上提供)作为静态资源安装到您的组织中。
  2. 将页面的 docType 设置为 。 强烈建议禁用标准样式表和标头。例如:html-5.0<apex:page standardController="Warehouse__c" extensions="WarehouseEditor" showHeader="false" standardStylesheets="false" docType="html-5.0">
  3. 将所选移动工具包中的脚本和样式添加到页面中 Visualforce 资源标记。为 例:<apex:includeScript value="{!URLFOR( $Resource.Mobile_Design_Templates, 'Mobile-Design-Templates-master/common/js/ jQuery2.0.2.min.js' )}"/>
  4. 使用 HTML5 和移动工具包的标签和属性创建页面 骨架。
  5. 将 JavaScript 函数作为处理程序添加到页面以响应用户交互。 使用 JavaScript 远程处理可以 调用 Apex 方法 检索记录、执行 DML 等。@RemoteAction
  6. 添加其他 JavaScript 函数以处理用户操作和页面更新。 通过在 JavaScript 中构造 HTML 元素来执行页面更新,然后 将它们添加或附加到页面骨架中。

JavaScript 远程处理和静态 HTML 页面的示例

以下代码示例显示了一个远程处理 + HTML Visualforce 页面,该页面允许用户 编辑仓库记录。编辑功能由控制器扩展提供 使用响应 JavaScript 远程处理 请求。

@RemoteAction

<apex:page standardController="Warehouse__c" extensions="WarehouseEditor"
    showHeader="false" standardStylesheets="false"
    docType="html-5.0" applyHtmlTag="false" applyBodyTag="false">

    <!-- Include Mobile Toolkit styles and JavaScript -->
    <apex:stylesheet 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/css/app.min.css')}"/>
    <apex:includeScript 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/js/jQuery2.0.2.min.js')}"/>
    <apex:includeScript 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/js/jquery.touchwipe.min.js')}"/>
    <apex:includeScript 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/js/main.min.js')}"/>

<head>
<style>
    html, body, p { font-family: sans-serif; }
    input { display: block; }
</style>

<script>
    $(document).ready(function(){
        // Load the record
        loadWarehouse();
    });

    // Utility; parse out parameter by name from URL query string
    $.urlParam = function(name){
        var results = new RegExp('[\\?&]' + name + '=([^&#]*)')
            .exec(window.location.href);
        return results[1] || 0;
    }

    function loadWarehouse() {
        // Get the record Id from the GET query string
        warehouseId = $.urlParam('id');

        // Call the remote action to retrieve the record data
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.WarehouseEditor.getWarehouse}',
            warehouseId,
            function(result, event){;
                if(event.status){
                    console.log(warehouseId);
                    $('#warehouse_name').text(result.Name);
                    $('#warehouse_address').val(
                      result.Street_Address__c);
                    $('#warehouse_city').val(result.City__c);
                    $('#warehouse_phone').val(result.Phone__c);
                } else if (event.type === 'exception'){
                    console.log(result);
                } else {
                    // unexpected problem...
                }
        });
    }

    function updateWarehouse() {
        // Get the record Id from the GET query string
        warehouseId = $.urlParam('id');

        // Call the remote action to save the record data
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.WarehouseEditor.setWarehouse}',
            warehouseId, $('#warehouse_address').val(),
                $('#warehouse_city').val(), 
                $('#warehouse_phone').val(),
            function(result, event){;
                if(event.status){
                    console.log(warehouseId);
                    $('#action_status').text('Record updated.');
                } else if (event.type === 'exception'){
                    console.log(result);
                    $('#action_status').text(
                      'Problem saving record.');
                } else {
                    // unexpected problem...
                }
        });
    }

</script>
</head>

<body>

<div id="detailPage">
    <div class="list-view-header" id="warehouse_name"></div>
    <div id="action_status"></div>

    <section>
        <div class="content">
            <h3>Warehouse Details</h3>
            <div class="form-control-group">
                <div class="form-control form-control-text">
                    <label for="warehouse_address">
                        Street Address</label>
                    <input type="text" id="warehouse_address" />
                </div>
                <div class="form-control form-control-text">
                    <label for="warehouse_city">City</label>
                    <input type="text" id="warehouse_city" />
                </div>
                <div class="form-control form-control-text">
                    <label for="warehouse_phone">Phone</label>
                    <input type="text" id="warehouse_phone" />
                </div>
            </div>
        </div>
    </section>

    <section class="data-capture-buttons one-buttons">
        <div class="content">
            <section class="data-capture-buttons one-buttons">
                <a href="#" id="updateWarehouse"
                    onClick="updateWarehouse();">save</a>
            </section>
        </div>
    </section>
</div> <!-- end detail page -->

</body>

</apex:page>

这 静态 HTML 提供页面的 shell,包括空表单字段。JavaScript的 函数加载记录,填写表单字段,并发送回更新的表单数据 到 Salesforce。

虽然此页面可以在完整的 Salesforce 站点中使用,但它被设计为 Salesforce 应用程序页面,看起来与普通的 Visualforce 页面有很大不同。

JavaScript 远程处理和静态 HTML 控制器的示例

与创建移动页面的其他两种方法不同,远程处理 + HTML 方法不使用标准控制器功能从中检索数据 并将数据保存到 Salesforce。相反,您可以创建控制器扩展或自定义 控制器,用于添加任何方法 您的页面需要。下面是一个简化的控制器扩展,支持 以上 页。

@RemoteAction

global with sharing class WarehouseEditor {

    // Stub controller
    // We're only using RemoteActions, so this never runs
    public WarehouseEditor(ApexPages.StandardController ctl){ }

    @RemoteAction
    global static Warehouse__c getWarehouse(String warehouseId) {

        // Clean up the Id parameter, in case there are spaces
        warehouseId = warehouseId.trim();

        // Simple SOQL query to get the warehouse data we need
        Warehouse__c wh = [
            SELECT Id, Name, Street_Address__c, City__c, Phone__c
            FROM Warehouse__c
            WHERE Id = :warehouseId];

        return(wh);
    }

    @RemoteAction
    global static Boolean setWarehouse(
        String whId, String street, String city, String phone) {

        // Get the warehouse record for the Id
        Warehouse__c wh = WarehouseEditor.getWarehouse(whId);

        // Update fields
        // Note that we're not validating / sanitizing, for simplicity
        wh.Street_Address__c = street.trim();
        wh.City__c = city.trim();
        wh.Phone__c = phone.trim();

        // Save the updated record
        // This should be wrapped in an exception handler
        update wh;

        return true;
    }
}

在 Salesforce 中优化 Visualforce 页面的性能 移动应用

Visualforce 旨在为开发人员提供匹配 标准 Salesforce 页面的功能、行为和性能。如果您的用户体验 延迟、意外行为或其他专门围绕 Visualforce 的问题,有几个 您可以采取的措施不仅可以改善他们的体验,还可以改进编码。 在 Salesforce 移动应用程序中,遵循优化的最佳实践非常重要。移动 设备的计算资源更加有限,用户期望快速、响应迅速 应用。

有关更多指南,请参阅 Visualforce 性能:最佳实践指南。

视觉力

  • 请勿使用 或 ,这会增加页面的视图状态 大小。视图状态是维护 Visualforce 页面状态的加密数据。它已发送 在每个页面请求中来回切换,从而增加请求和响应的大小。大 视图状态会减慢页面的响应时间。<apex:form><apex:inputField>
  • 用于将必要的数据发送到 呈现页面时的浏览器。此过程可缩短页面加载时间。<apex:repeat>
  • 设置为 为 Visualforce 页面启用缓存。<apex:page cache=”true” expires=”600″>

CSS 和 JavaScript

  • 创建单页应用程序 (SPA) 而不是多页应用程序。考虑使用 用于构建 SPA 的 JavaScript 和第三方框架。
  • 使用压缩器缩小 CSS 和 JavaScript 代码。
  • 避免使用影响页面性能的 CSS 技术,例如投影或渐变。
  • 将语句移到 Visualforce 页面。通过在结束标记之前加载脚本,页面可以先下载其他组件并呈现页面 逐步。<script></body>

图像

  • 使用更少和更小的图像。
  • 压缩所有图像。
  • 使用 PNG 或 JPG 图像,而不是 GIF。
  • 使用 CSS 精灵而不是图像。

一般最佳实践

  • 使用延迟加载。延迟加载是一种首先加载页面关键功能的技术,并且 稍后或用户需要信息时剩余数据。
  • 使用无限滚动。无限滚动是一种加载其他页面内容的技术 仅当用户接近内容末尾时。

Salesforce 中应避免的 Visualforce 组件和功能 移动应用

大多数核心 Visualforce 组件(命名空间中的组件)在 Salesforce 中正常运行 移动应用程序。不幸的是,这并不意味着它们针对移动设备进行了优化,或者每个 功能适用于应用程序。您可以改善 Visualforce 的移动用户体验 页面遵循一些简单的规则。

apex

通常,避免使用结构组件(如子组件)以及其他模仿 Salesforce 外观,例如 .如果必须使用这些组件,请将它们设置为 一列,使用 ,而不是默认的两列。<apex:pageBlock><apex:pageBlockTable><apex:pageBlockSection columns=”1″>

避免使用较宽的非包装组件,尤其是 、 、 和 ,它们都不受支持。保持设备宽度 使用 创建表时要注意。<apex:detail><apex:enhancedList><apex:listViews><apex:relatedList><apex:dataTable>

避免使用 .内嵌 编辑是一种用户界面模式,适用于基于鼠标的桌面应用,但 在基于触摸的设备上很难使用,尤其是在屏幕所在的手机上 小。<apex:inlineEditSupport>

对字段使用是可以的 显示为基本输入字段,如文本、电子邮件和电话号码,但避免 将其用于使用输入小组件的字段类型,例如日期和查阅表格字段。<apex:inputField>

不要使用 . Salesforce 移动应用程序中的任何位置都不支持 sControls。<apex:scontrol>

通过设置 on 的 PDF 呈现不支持 中的页面 Salesforce 移动应用程序。renderAs=”PDF”<apex:page>

  • 不支持的 Visualforce 组件 以下是 Salesforce 移动应用程序不支持的 Visualforce 组件列表,这些组件
    不应在与 Salesforce 移动应用程序一起使用的 Visualforce 页面中使用。

不支持的 Visualforce 组件

以下是 Salesforce 移动版不支持的 Visualforce 组件列表 应用程序,并且不应用于将与 Salesforce 移动版一起使用的 Visualforce 页面 应用程序。

  • <analytics:reportChart>
  • <apex:detail>
  • <apex:emailPublisher>
  • <apex:enhancedList>
  • <apex:flash>
  • <apex:inputField>对于使用 用于输入的小部件,而不是基本的表单字段
  • <apex:listViews>
  • <apex:logCallPublisher>
  • <apex:relatedList>
  • <apex:scontrol>
  • <apex:sectionHeader>
  • <apex:selectList>对于选择列表字段
  • <apex:tabPanel>(因此,<apex:tab>)
  • <apex:vote>

警告

嵌入的 Visualforce 页面,即添加到页面的页面 布局 – 包含组件可能会导致 Salesforce 移动应用程序在 iOS 上崩溃。<apex:enhancedList>

命名空间外的标准组件,用于 示例、 、 等在应用中不受支持。apex<liveagent:*><chatter:*>

自定义组件可以在应用程序中的 Visualforce 中使用,只要它们本身不使用 不支持的组件。

使用 Visualforce 进行模板制作

Visualforce 提供了多种策略,用于在 多个 Visualforce 页面。您选择的方法取决于您需要的灵活性 重用模板。模板方法越灵活, 此外,还可以修改使用该方法的任何模板实现。 可以使用以下模板方法,按大多数顺序排列 最不灵活:定义自定义组件类似于封装一件作品的方式 代码,然后在 程序中,您可以将通用设计模式封装在自定义组件中 ,然后在一个或多个 Visualforce 页面中多次重复使用该组件。定义自定义组件是最灵活的模板 方法,因为它们可以包含任何有效的 Visualforce 标签,并且可以不受限制地导入到任何 Visualforce 页面中。但是,不应使用自定义组件来定义可重用的 Visualforce 页面。如果要重复使用整个 Visualforce 页面的内容,请选择其他两种模板方法之一。使用 <apex:composition> 定义模板如果要定义一个基本模板,该模板允许 要随每个实现而更改的模板,请使用组件。 此模板方法最适合需要维护的情况 一个页面的整体结构,但需要单个内容 页面要不同,比如一个网站的新闻文章不同 文章应以相同的页面布局显示。<apex:composition>通过这种技术,您还可以从 控制器返回的 PageReference。使用 <apex:include> 引用现有页面如果要将 Visualforce 页面的全部内容插入到另一个页面中,请使用该组件。这 模板方法最适合想要复制的情况 多个区域中的相同内容,例如显示的反馈表单 在网站的每个页面上。<apex:include>

仅当您想要引用已存在的 Visualforce 页面时,才应使用使用模板。如果只需要复制一组组件, 使用自定义组件。<apex:insert><apex:composition>

使用 <apex:composition> 定义模板

使用定义的所有模板都必须具有一个或多个子标记。标记指示导入模板的页面,该部分需要 一个定义。任何导入模板的 Visualforce 页面都必须用于指定模板每个部分的内容。<apex:composition><apex:insert><apex:insert><apex:composition><apex:define><apex:insert>

您可以创建一个框架模板,该模板允许后续 Visualforce 页面在同一标准结构中实现不同的内容。 为此,请使用该标记创建模板页面。<apex:composition>

下面的示例演示如何使用 、 和 实现框架 模板。<apex:composition><apex:insert><apex:define>首先,创建一个名为 的空页面,该页面使用名为

myFormCompositioncompositionExample

<apex:page controller="compositionExample">

</apex:page>

保存页面后,会出现一个提示,要求您创建 .使用以下命令 用于定义该自定义控制器的代码:

compositionExample

public class compositionExample{

    String name;
    Integer age;
    String meal;
    String color;
    
    Boolean showGreeting = false;
    
    public PageReference save() {
        showGreeting = true;
        return null;
    }
    
    public void setNameField(String nameField) {
        name = nameField;
    }
    
    public String getNameField() {
        return name;
    }
    
    public void setAgeField(Integer ageField) {
        age= ageField;
    }
    
    public Integer getAgeField() {
        return age;
    }
    
    public void setMealField(String mealField) {
        meal= mealField;
    }
    
    public String getMealField() {
        return meal;
    }   
         
    public void setColorField(String colorField) {
        color = colorField;
    }
    
    public String getColorField() {
        return color;
    }       
    
    public Boolean getShowGreeting() {
        return showGreeting;
    }
}

接下来,返回并创建骨架模板:

myFormComposition

<apex:page controller="compositionExample">
    <apex:form >
        <apex:outputLabel value="Enter your name: " for="nameField"/>
        <apex:inputText id="nameField" value="{!nameField}"/>
        <br />
        <apex:insert name="age" />
        <br />
        <apex:insert name="meal" />
        <br />      
        <p>That's everything, right?</p>
        <apex:commandButton action="{!save}" value="Save" id="saveButton"/>
    </apex:form>
</apex:page>

请注意,两个字段需要 和内容。这 这些字段的标记是在调用此合成的页面中定义的 模板。

<apex:insert>agemeal接下来,创建一个名为 的页面,该页面 定义 中的标签:

myFullForm<apex:insert>myFormComposition

<apex:page controller="compositionExample">
    <apex:messages/>
    <apex:composition template="myFormComposition">
    
    <apex:define name="meal">
        <apex:outputLabel value="Enter your favorite meal: " for="mealField"/>
        <apex:inputText id="mealField" value="{!mealField}"/>
    </apex:define>

    <apex:define name="age">
        <apex:outputLabel value="Enter your age: " for="ageField"/>
        <apex:inputText id="ageField" value="{!ageField}"/>
    </apex:define>
    
   <apex:outputLabel value="Enter your favorite color: " for="colorField"/>
   <apex:inputText id="colorField" value="{!colorField}"/>
    
    </apex:composition>
    
    <apex:outputText id="greeting" rendered="{!showGreeting}" value="Hello {!nameField}. 
    You look {!ageField} years old. Would you like some {!colorField} {!mealField}?"/>
</apex:page>

请注意有关标记的以下信息:

  • 保存时,将显示先前定义的标记和保存按钮。myFullForm<apex:inputText>
  • 由于组合页面需要 和 字段,因此定义它们 作为文本输入字段。它们在页面上的显示顺序确实如此 没关系; 指定该字段始终显示在该字段之前。agemealmyFullFormmyFormCompositionagemeal
  • 即使没有,该字段仍会导入 匹配字段。name<apex:define>
  • 即使该字段被忽略,即使 该字段存在控制器代码。这是因为成分 模板不需要任何名为 的字段。colorcolor
  • 和字段 不需要是文本输入。标记中的组件可以是任何有效的 Visualforce 标记。agemeal<apex:define>

要展示如何在标签中使用任何有效的 Visualforce,请执行以下操作: 创建一个新的 Visualforce 页面,该页面名为 并使用以下标记:

<apex:define>myAgelessForm

<apex:page controller="compositionExample">
    <apex:messages/>
    <apex:composition template="myFormComposition">
    
    <apex:define name="meal">
        <apex:outputLabel value="Enter your favorite meal: " for="mealField"/>
        <apex:inputText id="mealField" value="{!mealField}"/>
    </apex:define>

    <apex:define name="age">
        <p>You look great for your age!</p>
    </apex:define>

    </apex:composition>
    
    <apex:outputText id="greeting" rendered="{!showGreeting}" value="Hello {!nameField}. 
    Would you like some delicious {!mealField}?"/>
</apex:page>

请注意,只有合成模板 需要标记才能存在。在此示例中,定义 作为文本。

<apex:define>age

动态模板

动态模板允许您通过 PageReference 分配模板。 模板名称分配给返回的控制器方法 包含要使用的模板的 PageReference。为 示例,创建一个名为“定义框架模板”的页面:

myAppliedTemplate

<apex:page>
    <apex:insert name="name" />
</apex:page>

接下来,创建一个名为 将返回对此页面的引用的方法:

dynamicComposition

public class dynamicComposition {
    public PageReference getmyTemplate() {
        return Page.myAppliedTemplate;
    }
}

最后,创建一个名为实现 此控制器和动态模板:

myDynamicComposition

<apex:page controller="dynamicComposition">
    <apex:composition template="{!myTemplate}">
    <apex:define name="name">
        Hello {!$User.FirstName}, you look quite well.
    </apex:define>
    </apex:composition>
</apex:page>

使用 Visualforce 渲染流程

无法使用 流生成器。但是,在 Visualforce 页面中嵌入流程后,即可使用 Apex 代码 和 Visualforce 标记,用于在运行时配置流程,例如在 Visualforce 页面和流程,或在运行时自定义流程的外观。

是一个应用程序,用于收集、更新、编辑、 并创建 Salesforce 信息。

以下主题演示如何在 Visualforce 中嵌入和配置流程 页。

  • 在 Visualforce 页面中嵌入流程 要自定义流程的外观或增强其功能,请将其嵌入到 Visualforce 页面
    中。如果您的组织为站点和门户启用了流程,请使用 Visualforce 页面将流程传送到您的 Salesforce 站点、门户或 Experience Cloud 站点。
  • 使用 <flow:interview> 的高级示例
    该组件旨在使开发复杂的 Visualforce 交互变得容易。您可以通过创建自定义控制器来访问流中的其他功能。使用自定义控制器,您可以构建一个包含多个组件的页面,这些组件可以相互交互。组织内的任何流都可以由其自己的 Apex 类型单独引用,并且流中的变量可以作为成员变量进行访问。<flow:interview>
  • 从 Visualforce 页面设置流变量值 将流程嵌入 Visualforce 页面
    后,通过组件设置变量、记录变量、集合变量和记录集合变量的初始值。<apex:param>
  • 将流变量值获取到 Visualforce 页面 流变量值可以显示在 Visualforce 页面
    中。将流程嵌入 Visualforce 页面后,您可以使用 Visualforce 标记来获取变量的值或记录变量。若要显示集合变量或记录集合变量的值,可以使用 Visualforce 标记来获取集合中包含的各个值。
  • 控制用户是否可以暂停 Visualforce 页面中的流程 使用组件将流程嵌入到 Visualforce 页面中后,请考虑是否允许用户暂停该页面
    中的流程。将该属性设置为 false 可防止用户暂停。<flow:interview>allowShowPause
  • 自定义用户如何恢复暂停的流程面试 默认情况下,用户可以从其主页上的“暂停面试”组件恢复暂停的面试
    。如果要自定义用户恢复采访的方式和位置,请使用组件上的属性。pausedInterviewId<flow:interview>
  • 在流程中配置 finishLocation 属性 如果未指定,则单击“完成”的用户将开始新的面试,并看到流程的第一个屏幕。
    您可以使用函数、变量或控制器来调整用户在最后一个屏幕上单击“完成”时发生的情况。finishLocationURLFOR$Page
  • 自定义流程的用户界面
    在 Visualforce 页面中嵌入流程后,您可以通过使用 CSS 应用自定义样式来自定义流程在运行时的外观。使用流属性和 CSS 类的组合,您可以自定义流的各个部分,例如按钮位置、按钮样式、背景以及屏幕标签的外观。
  • 在 Visualforce 页面中渲染流的 Lightning 运行时 默认情况下,当您在 Visualforce 页面
    中嵌入流时,该流将在 Classic 运行时中呈现。顾名思义,Classic 运行时的外观和感觉类似于常规的 Visualforce 页面和 Salesforce Classic 桌面体验。如果您希望流程在 Visualforce 页面的 Lightning 运行时中呈现,请将流程 Aura 组件嵌入到 Visualforce 页面。

在 Visualforce Pages 中嵌入流程

要自定义流的外观或增强其功能,请将其嵌入到 Visualforce 页面。如果您的组织为站点和门户启用了流程,请使用 Visualforce 页面,将流程传送到您的 Salesforce 站点、门户或 Experience Cloud 网站。

注意

用户只能运行具有活动状态的流 版本。如果嵌入的流没有活动版本,用户会看到错误 消息。如果嵌入的流包含 Subflow 元素,则引用的流 并且由 Subflow 元素调用必须具有活动版本。要向 Visualforce 页面添加流程,请使用以下组件嵌入该流程:

<flow:interview>

  1. 查找流的 API 名称。
    1. 在“设置”中,输入“快速” “查找”框,然后选择“流”。Flows
    2. 单击要嵌入的流的名称。
  2. 定义新的 Visualforce 页面或打开要编辑的页面。
  3. 添加组件, 在标签之间的某个地方。<flow:interview><apex:page>
  4. 将属性设置为唯一属性 流的名称。例如:name<apex:page> <flow:interview name="flowAPIName"/> </apex:page>注意如果流程是 在托管包中,该属性必须是 采用以下格式:.namenamespace.flowuniquename
  5. 通过为 包含它的 Visualforce 页面。若要运行流,外部用户(例如 在 Experience Cloud 站点上)需要访问 Visualforce 页面。要运行 流程中,内部用户需要访问 Visualforce 页面,并且:
    • “运行流”权限
    • 在其用户上启用了 Flow User 字段 详情页面
    • 如果覆盖默认行为并限制对 已启用的配置文件或权限集被选中 单个流,用户按配置文件访问该流 或权限集

在流程中设置变量值

在这个 例如,我们将构建一个简单的流程,以允许客户支持代理 通过创建案例对调制解调器问题进行故障排除。您可以在启动流通过组件时设置变量的值。对于我们的 示例,设置使用初始值调用的案例编号变量 01212212当流加载时,请使用以下标记:

<apex:param>vaCaseNumber

<apex:page>
    <flow:interview name="ModemTroubleShooting">
        <apex:param name="vaCaseNumber" value="01212212"/>
    </flow:interview>
</apex:page>

您还可以使用标准 Visualforce 控制器设置变量。例如,如果 Visualforce页面正在使用控制器,可以增强页面传入标准中的数据 控制器。

standardCase

<apex:page standardController="Case" tabStyle="Case" >
    <flow:interview name="ModemTroubleShooting">
        <apex:param name="vaCaseNumber" value="{!Case.CaseNumber}"/>
    </flow:interview>
</apex:page>

有关设置变量值的更多示例,请参见从 Visualforce 页面设置流变量值。为 有关从流程中获取变量值以在 Visualforce 中显示的信息 页面上,请参阅将流变量值获取到 Visualforce 页面。

设置 finishLocation 属性

基于调制解调器故障排除示例,我们还将设置属性以将用户重定向到 Salesforce 主页 页面,当他们单击末尾的“完成”按钮时 流。

finishLocation

<apex:page standardController="Case" tabStyle="Case" >
    <flow:interview name="ModemTroubleShooting" finishLocation="{!URLFOR('/home/home.jsp')}">
        <apex:param name="vaCaseNumber" value="{!case.CaseNumber}"/>
    </flow:interview>
</apex:page>

有关设置的更多示例,请参阅在流中配置 finishLocation 属性。finishLocation

使用 <flow:interview> 的高级示例

组件设计 以便轻松开发复杂的 Visualforce 交互。您可以 通过创建自定义控制器来访问流程中的其他功能。带定制 控制器,您可以构建一个包含多个组件的页面,这些组件可以与每个组件进行交互 其他。组织内的任何流都可以由其自己的 Apex 单独引用 类型,并且流中的变量可以作为成员变量进行访问。

<flow:interview>

注意

你 只能设置允许输入访问的变量,并且只能获取 允许输出访问。对于不允许输入或输出访问的变量,尝试 get 变量被忽略,并且 Visualforce 页面、其组件或 Apex 类的编译可能会失败。<apex:page>在下一个示例中,API 名称为“ModemTroubleShooting”的流是 引用为 。标记演示了如何显示 不同部分的流变量的值 页:

Flow.Interview.ModemTroubleShooting

<apex:page Controller="ModemTroubleShootingCustomSimple" tabStyle="Case">
    <flow:interview name="ModemTroubleShooting" interview="{!myflow}"/>
    <apex:outputText value="Default Case Prioriy: {!casePriority}"/>
</apex:page>

注意

如果流程是 在托管包中,该属性必须是 采用以下格式:.namenamespace.flowuniquename上述标记的控制器如下所示 这:

public class ModemTroubleShootingCustomSimple {

    // You don't need to explicitly instantiate the Flow object;
    // the class constructor is invoked automatically

    public Flow.Interview.ModemTroubleShooting myflow { get; set; }
    public String casePriority;
    public String getCasePriority() {
        // Access flow variables as simple member variables with get/set methods
        if(myflow == null) return 'High';
        else return myflow.vaCasePriority;
    }
}

如果您使用的是自定义控制器,您还可以设置 流构造函数中流开头的变量。传入 使用构造函数的变量是可选的,如果是 使用标签来设置值。<apex:param>下面是一个自定义控制器的示例,该控制器在 一个 构造 函数。

public class ModemTroubleShootingCustomSetVariables {
    public Flow.Interview.ModemTroubleShooting myflow { get; set; }
 
    public ModemTroubleShootingCustomSetVariables() {
        Map<String, Object> myMap = new Map<String, Object>();
        myMap.put('vaCaseNumber','123456');
        myflow = new Flow.Interview.ModemTroubleShooting(myMap);
    }
 
    public String caseNumber { set; }
    public String getCaseNumber() {
        return myflow.vaCaseNumber;
    }
}

您可以使用类中的方法访问 流变量。变量可能位于 Visualforce 页面中嵌入的流程中,也可能位于 由 Subflow 元素调用的单独流。返回的变量值来了 无论面试当前从哪个流程运行。如果指定的变量 在该流中找不到,该方法将返回 。此方法在运行时检查变量是否存在 只是,而不是在编译时。getVariableValueFlow.Interviewnull此示例使用该方法获取痕迹导航 (导航)来自流的信息。如果该流包含子流元素,并且 每个引用的流还包含一个变量,您可以为用户提供痕迹导航,而不管哪个流 面试是 运行。

getVariableValuevaBreadCrumb

public class SampleController {

   //Instance of the flow
   public Flow.Interview.Flow_Template_Gallery myFlow {get; set;}

   public String getBreadCrumb() {
      String aBreadCrumb;
      if (myFlow==null) { return 'Home';}
      else aBreadCrumb = (String) myFlow.getVariableValue('vaBreadCrumb');

      return(aBreadCrumb==null ? 'Home': aBreadCrumb);

   }
}

下表显示了 流量和顶点。

顶点
发短信字符串
十进制
货币十进制
日期日期、日期时间
布尔布尔
使用指定对象进行记录指定对象的 API 名称,例如 Account 或 箱

由于针对 Apex 代码编写测试是一种很好的做法,因此以下是 编写测试类的简单示例:

ModemTroubleShootingCustomSetVariables

@isTest
private class ModemTroubleShootingCustomSetVariablesTest {

    static testmethod void ModemTroubleShootingCustomSetVariablestests() {
        PageReference pageRef = Page.ModemTroubleShootingSetVariables;
        Test.setCurrentPage(pageRef);
        ModemTroubleShootingCustomSetVariables mytestController = 
            new ModemTroubleShootingCustomSetVariables();
        System.assertEquals(mytestController.getcaseNumber(), '01212212');
    }
}

设置 reRender 属性

通过使用该属性,组件将重新呈现 在不刷新整个页面的情况:reRender<flow:interview />

<apex:page Controller="ModemTroubleShootingCustomSimple" tabStyle="Case">
    <flow:interview name="ModemTroubleShooting" interview="{!myflow}" 
     reRender="casePrioritySection"/>
    <apex:outputText id="casePrioritySection" 
     value="Default Case Prioriy: {!casePriority}"/>
</apex:page>

警告

如果未设置属性,则当您单击按钮导航到其他按钮时 屏幕中,整个 Visualforce 页面都会刷新, 不仅仅是组件。reRender<flow:interview>

从 Visualforce 页面设置流变量值

将流程嵌入 Visualforce 页面后,设置变量的初始值。 通过组件记录变量、集合变量和记录集合变量。

<apex:param>

注意

您只能在面试开始时设置变量。标签仅评估一次, 当流量 推出。<apex:param>

你 只能设置允许输入访问的变量。如果引用的变量 不允许输入访问,则忽略设置变量的尝试。 Visualforce 页面、其组件或 Apex 的编译可能会失败 类。<apex:page>

下表列出了设置流变量 record 的方法 变量,并使用 Visualforce 记录集合变量值。

方法变量记录变量集合变量记录集合变量
没有 控制器复选标记
与标准 控制器复选标记复选标记
使用标准列表 控制器复选标记
使用自定义 Apex 控制器复选标记复选标记复选标记复选标记
接受采访 地图复选标记复选标记复选标记复选标记

设置不带 控制器

此示例将 myVariable 设置为面试开始时的值。01010101

<apex:page>
    <flow:interview name="flowname">
        <apex:param name="myVariable" value="01010101"/>
    </flow:interview>
</apex:page>

使用标准设置变量值 控制器

您可以使用标准 Visualforce 控制器通过从记录传入数据来设置变量。此示例集 面试时 myVariable 到 Visualforce 表达式的初始值 开始。{!account}

<apex:page standardController="Account" tabStyle="Account">
    <flow:interview name="flowname">
        <apex:param name="myVariable" value="{!account}"/>
    </flow:interview>
</apex:page>

设置记录集合变量值 使用标准列表控制器

由于记录集合变量表示值数组,因此必须使用 标准列表控制器或自定义 Apex 控制器。此示例将 myCollection 设置为面试开始时的值。{!accounts}

<apex:page standardController="Account" tabStyle="Account" recordSetVar="accounts">
    <flow:interview name="flowname">
        <apex:param name="myCollection" value="{!accounts}"/>
    </flow:interview>
</apex:page>

使用自定义 Apex 设置变量值 控制器

要对 Visualforce 页面进行比标准控制器允许的更精细的控制,请编写 设置变量值的自定义 Apex 控制器,然后引用该变量值 控制器。此示例使用 Apex 将 myVariable 设置为特定帐户的 ID,当 面试开始。

public class MyCustomController {
    public Account apexVar {get; set;}

    public MyCustomController() {
        apexVar = [
            SELECT Id, Name FROM Account
            WHERE Name = 'Acme' LIMIT 1];
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname">
        <apex:param name="myVariable" value="{!apexVar}"/>
    </flow:interview>
</apex:page>

此示例使用 Apex 将记录集合变量 myAccount 设置为 Name 为 的每条记录的 Id 和 Name 字段值。Acme

public class MyCustomController {
    public Account[] myAccount { 
        get {
            return [ 
                SELECT Id, Name FROM account 
                WHERE Name = 'Acme'
                ORDER BY Id
            ] ;
        }
        set {
            myAccount = value;
        }
    }
    public MyCustomController () {
    }
}
<apex:page id="p" controller="MyCustomController">
    <flow:interview id="i" name="flowname">
        <apex:param name="accountColl" value="{!myAccount}"/>
    </flow:interview>
</apex:page>

在面试中设置变量值 地图

此示例使用面试映射在面试开始时将 accVar 的值设置为特定客户的 Id。

public class MyCustomController {
    public Flow.Interview.TestFlow myflow { get; set; }

     public MyCustomController() {
        Map<String, Object> myMap = new Map<String, Object>();
        myMap.put('accVar', [SELECT Id FROM Account 
                             WHERE Name = 'Acme' LIMIT 1]);
        myflow = new Flow.Interview.ModemTroubleShooting(myMap);
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname" interview="{!myflow}"/>
</apex:page>

下面是一个类似的示例,该示例在面试开始时将 accVar 的值设置为新帐户。

public class MyCustomController {
    public Flow.Interview.TestFlow myflow { get; set; }

     public MyCustomController() {
        Map<String, List<Object>> myMap = new Map<String, List<Object>>();
        myMap.put('accVar', new Account(name = 'Acme'));
        myflow = new Flow.Interview.ModemTroubleShooting(myMap);
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname" interview="{!myflow}"/>
</apex:page>

此示例使用映射将两个值添加到字符串集合变量 (stringCollVar) 和两个值到一个数字集合 变量 (numberCollVar)。

public class MyCustomController {
    public Flow.Interview.flowname MyInterview { get; set; }

    public MyCustomController() {
        String[] value1 = new String[]{'First', 'Second'};
        Double[] value2 = new Double[]{999.123456789, 666.123456789};
        Map<String, Object> myMap = new Map<String, Object>();
        myMap.put('stringCollVar', value1);
        myMap.put('numberCollVar', value2);
        MyInterview = new Flow.Interview.flowname(myMap);
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname" interview="{!MyInterview}" />
</apex:page>

将流变量值获取到 Visualforce 页面

流变量值可以显示在 Visualforce 页面中。一旦你 将您的流程嵌入到 Visualforce 页面中,您可以使用 Visualforce 标记 获取变量的值或记录变量。显示集合的值 变量或记录集合变量,您可以使用 Visualforce 标记来获取 集合中包含的单个值。

注意

你 只能获取允许输出访问的变量。如果引用不允许输出访问的变量, 获取变量的尝试将被忽略。Visualforce 页面、其组件或 顶点类。<apex:page>下面的示例使用 Apex 类从 flow,然后在 Visualforce 中显示它 页。

public class FlowController {
    public Flow.Interview.flowname myflow { get; set; }
    public Case apexCaseVar;
    public Case getApexCaseVar() {
        return myflow.caseVar;
    }
}
<apex:page controller="FlowController" tabStyle="Case">
    <flow:interview name="flowname" interview="{!myflow}"/>
    <apex:outputText value="Default Case Priority: {!apexCaseVar.Priority}"/>
</apex:page>

这 示例使用 Apex 类获取存储在字符串集合中的值 变量 (emailsCollVar)。然后,它使用 Visualforce 页面运行 流程面试。Visualforce 页面将循环访问流程的集合变量,并显示 中的每一项 收集。

public class FlowController {
    public Flow.Interview.flowname myflow { get; set; }

    public List<String> getVarValue() {
        if (myflow == null) { 
            return null; 
        }
        else {
            return (List<String>)myflow.emailsCollVar;
        }
    }
}
<apex:page controller="FlowController">
    <flow:interview name="flowname" interview="{!myflow}" />
        <apex:repeat value="{!varValue}" var="item">
        <apex:outputText value="{!item}"/><br/>
        </apex:repeat>
</apex:page>

这 以下示例使用 Apex 类将流程设置为,然后使用 Visualforce 页面运行 流程面试。Visualforce 页面使用数据表循环访问流程的记录集合 变量,并显示 收集。{!myflow}

public class MyCustomController {
   public Flow.Interview.flowname myflow { get; set; }
}
<apex:page controller="MyCustomController" tabStyle="Account">
   <flow:interview name="flowname" interview="{!myflow}" reRender="nameSection" />
    <!-- The data table iterates over the variable set in the "value" attribute and 
         sets that variable to the value for the "var" attribute, so that instead of 
         referencing {!myflow.collectionVariable} in each column, you can simply refer 
         to "account".-->
    <apex:dataTable value="{!myflow.collectionVariable}" var="account" 
        rowClasses="odd,even" border="1" cellpadding="4" id="nameSection">
        <!-- Add a column for each value that you want to display.-->
        <apex:column >
            <apex:facet name="header">Name</apex:facet>
            <apex:outputlink value="/{!account['Id']}">
                {!account['Name']}
            </apex:outputlink>
        </apex:column>
        <apex:column >
            <apex:facet name="header">Rating</apex:facet>
            <apex:outputText value="{!account['Rating']}"/>
        </apex:column>
        <apex:column >
            <apex:facet name="header">Billing City</apex:facet>
            <apex:outputText value="{!account['BillingCity']}"/>
        </apex:column>
        <apex:column >
            <apex:facet name="header">Employees</apex:facet>
            <apex:outputText value="{!account['NumberOfEmployees']}"/>
        </apex:column>
    </apex:dataTable>
</apex:page>

取决于 在流中记录集合变量的内容上,内容如下 该数据表如下所示。Example of a data table that displays the contents of a record collection variable

控制用户是否可以暂停来自 Visualforce 的流程 页

使用组件在 Visualforce 页面中嵌入流程后,请考虑是否要让用户暂停流程 那一页。将属性设置为 false 阻止用户暂停。

<flow:interview>allowShowPause是否显示“暂停”按钮取决于三个设置。

  • 组织的流程自动化设置必须启用允许用户暂停流程。
  • 为此,不能是假的。默认值为 真。<flow:interview>allowShowPause
  • 每个屏幕都必须配置为显示“暂停”按钮。

在 Visualforce 页面中,您嵌入了一个包含三个屏幕的流程。屏幕 1 是 配置为显示“暂停”按钮。屏幕 2 和 3 配置为 不显示“暂停”按钮。

允许用户暂停流(过程自动化设置)allowShowPause(视觉力 组件)结果暂停按钮
启用true或未设置暂停按钮仅出现在第一个屏幕上
启用false此 Visualforce 页面中的任何屏幕均未显示暂停按钮
未启用true或未设置暂停按钮不会出现在任何屏幕上

此示例将 MyUniqueFlow 流嵌入到 Visualforce 页面中,并且不允许“暂停”按钮 出现。

<apex:page>   
   <flow:interview name="MyUniqueFlow" allowShowPause="false" />
</apex:page>

自定义用户恢复暂停流程访谈的方式

默认情况下,用户可以从“暂停的面试”中恢复暂停的面试 组件。如果要自定义用户恢复其恢复的方式和位置 interviews,请使用组件上的属性。

pausedInterviewId<flow:interview>以下示例显示了如何恢复面试或开始新的面试 一个 – 从页面布局上的按钮。当用户单击“调查”时 客户从联系人记录中,Visualforce 页面执行以下两项操作之一: 取决于用户是否对“调查客户”流程进行了任何暂停的访谈。

  • 如果用户这样做,它将恢复第一个。
  • 如果用户不这样做,它会启动一个新的。

创建 Visualforce 和 Apex 控制器

因为 Visualforce 页面将是 在特定于联系人的按钮中引用,它必须使用该标准控制器。使用 控制器扩展,以使用 Apex 向页面添加更多逻辑,这是页面获取的位置 要恢复的面试的 ID。

<apex:page
   standardController="Contact" extensions="MyControllerExtension_SurveyCustomers">
   <flow:interview name="Survey_Customers" pausedInterviewId="{!pausedId}"/>
</apex:page>

此 Apex 控制器扩展执行 SOQL 查询以获取暂停访谈的列表。如果 查询中不返回任何内容,返回 null 值,并且 Visualforce 页面将开始新的面试。如果至少一个 interview 从查询中返回,则 Visualforce 页面将恢复 那个列表。getPausedId()

public class MyControllerExtension_SurveyCustomers {
    
    // Empty constructor, to allow use as a controller extension
    public MyControllerExtension_SurveyCustomers(
        ApexPages.StandardController stdController) { }
    
    // Flow support methods
    public String getInterviews() { return null; }
    
    public String showList { get; set; }
    
    public String getPausedId() {
        String currentUser = UserInfo.getUserId();
        List<FlowInterview> interviews = 
            [SELECT Id FROM FlowInterview WHERE CreatedById = :currentUser AND InterviewLabel LIKE '%Survey Customers%'];

        if (interviews == null || interviews.isEmpty()) {
            return null; // early out
        }
        
        // Return the ID for the first interview in the list
        return interviews.get(0).Id;
    }
}

参考 Visualforce 页面 从页面布局

为了实际暴露这个 Visualforce 页面,使其可从“联系人”页面布局中使用。

提示

如果将 Visualforce 页面直接嵌入到页面中 布局中,每次用户访问联系人时,他们都会自动恢复他们的第一个联系人 暂停采访——可能是无意的。用户最好将 有意识地选择开始或恢复面试,所以让我们使用自定义按钮。首先,为链接到 Visualforce 页面的 Contact 对象创建一个自定义按钮。使用这些字段 值来创建按钮。

价值
标签调查客户
显示类型“详情页面”按钮
内容源Visualforce 页面
内容YourVisualforcePage

最后,将按钮添加到“联系人”页面布局中。

在流中配置 finishLocation 属性

如果未指定,则用户 谁单击完成开始新的面试并看到 流。您可以确定当用户在最终阶段单击“完成”时会发生什么情况 使用函数、变量或控制器进行筛选。

finishLocationURLFOR$Page以下各节显示了配置组件属性的方法。

<flow:interview>finishLocation

  • 使用 URLFOR 函数设置 finishLocation
  • 使用 $Page 变量设置 finishLocation
  • 使用 Controller 设置 finishLocation

使用 URLFOR 函数设置 finishLocation

注意

  • 您无法将流用户重定向到符合以下条件的 URL Salesforce 组织外部。
  • 不要在同一流中调用方法和属性。 表示 Visualforce 页面登录流程结束。如果在同一流中,则在流中执行 启动,为用户提供对会话的完全访问权限。Auth.SessionManagement.finishLoginFlowfinishLocationAuth.SessionManagement.finishLoginFlowfinishLocationfinishLocation

将用户路由到 相对 URL 或特定记录或详细信息页面,使用其 ID 使用该函数。URLFOR此示例将用户路由到 Salesforce 主页 页。

<apex:page>
    <flow:interview name="MyUniqueFlow" finishLocation="{!URLFOR('/home/home.jsp')}"/>
</apex:page>

此示例将用户引导至详细信息页面 ID 为 001D000000IpE9X。

<apex:page>
    <flow:interview name="MyUniqueFlow" finishLocation="{!URLFOR('/001D000000IpE9X')}"/>
</apex:page>

有关 的详细信息,请参阅函数。URLFOR

使用 $Page 变量设置 finishLocation

要在不使用 的情况下将用户路由到另一个 Visualforce 页面,请设置为目标的名称 页面的格式为 .

URLFORfinishLocation{!$Page.pageName}

<apex:page>
    <flow:interview name="MyUniqueFlow" finishLocation="{!$Page.MyUniquePage}"/>
</apex:page>

有关 的详细信息,请参阅全局变量。$Page

使用 Controller 设置 finishLocation

您可以使用自定义控制器通过多种方式进行设置。finishLocation此示例控制器配置了一个 流动的完成行为在三种不同的 方式。

public class myFlowController {
    
    public PageReference getPageA() {
        return new PageReference('/300');
    }
    
    public String getPageB() {
        return '/300';
    }
    
    public String getPageC() {
        return '/apex/my_finish_page';
    }
}

下面是一个 Visualforce 示例 引用控制器并将流完成行为设置为第一个的页面 选择。

<apex:page controller="myFlowController">
    <h1>Congratulations!</h1> This is your new page.
    <flow:interview name="flowname" finishLocation="{!pageA}"/>
</apex:page>

如果使用标准控制器显示 在与流程相同的页面上进行记录,单击“完成”的用户将开始新的流程访谈。他们看到流的第一个屏幕,但没有 记录,因为查询字符串 参数不会保留在页面 URL 中。如果需要,请配置 将用户路由回 记录。idfinishLocation

自定义流的用户界面

在 Visualforce 页面中嵌入流程后,您可以通过应用 使用 CSS 的自定义样式。结合使用流属性和 CSS 类,您可以自定义流程的各个部分,例如 作为按钮位置、按钮样式、背景以及外观和 屏幕标签的感觉。

Flow 按钮属性

使用这些属性可以更改“下一步”、“上一个”、“完成”、“暂停”和“不暂停”按钮在 你的流程。

属性描述
buttonLocation定义导航按钮在流中的位置 用户界面。可用值为:topbottomboth例如:<apex:page> <flow:interview name="MyFlow" buttonLocation="bottom"/> </apex:page>注意如果未指定,则该值默认为 。buttonLocationboth
buttonStyle将样式作为一组分配给流导航按钮。能 仅用于内联样式,不用于 CSS 类。例如:<apex:page> <flow:interview name="MyFlow" buttonStyle="color:#050; background-color:#fed; border:1px solid;"/> </apex:page>

特定于流的 CSS 类

您可以覆盖这些预定义的流样式 具有您自己的 CSS 样式的类。

Flow Style 类适用于…
FlowContainer(流程容器)包含 流。<div>
FlowPageBlockBtns(流页块Btns)元素 包含流导航按钮。<apex:pageBlockButtons>注意防止流的 CSS 样式 导航按钮不被其他位置应用的按钮样式覆盖 系统中,我们建议您在每次应用 CSS 时指定此流样式类 流导航按钮的样式。例如,输入 ,而不是 。.FlowPreviousBtn {}.FlowPageBlockBtns .FlowPreviousBtn {}
FlowCancelBtn不暂停”按钮。
FlowPauseBtn“暂停”按钮。
流程上一页Btn“上一步”按钮。
流下一个BTN下一步”按钮。
FlowFinishBtn完成”按钮。
流文本文本字段标签。
FlowTextArea文本区域字段标签。
流量编号数字字段标签。
流日期日期字段标签。
流货币货币字段标签。
FlowPassword(流密码)密码字段标签。
FlowRadio(英语:FlowRadio单选按钮字段标签。
FlowDropdown选择列表标签。

在 Visualforce 页面中渲染流程的 Lightning 运行时

默认情况下,当您在 Visualforce 页面中嵌入流程时,该流程将以 Classic 格式呈现 运行。顾名思义,Classic 运行时的外观和感觉都像常规的 Visualforce 页面 以及 Salesforce Classic 桌面体验。如果您希望流程在 Lightning 中渲染 运行时,将流 Aura 组件嵌入到 Visualforce 页面。

  1. 创建一个声明对组件的依赖关系的 Lightning 应用程序。lightning:flow
  2. 添加 Lightning 组件 使用组件将 Visualforce JavaScript 库添加到您的 Visualforce 页面。<apex:includeLightning/>
  3. 在 Visualforce 页面中,引用依赖项应用。
  4. 编写一个 JavaScript 函数,该函数使用 在页面上创建组件。$Lightning.createComponent()

<aura:application access="global" extends="ltng:outApp" >
    <aura:dependency resource="lightning:flow"/>
</aura:application>
<apex:page >
   <html>
      <head>
         <apex:includeLightning />
      </head>
      <body class="slds-scope">
         <div id="flowContainer" />
         <script>
            var statusChange = function (event) {
               if(event.getParam("status") === "FINISHED") {
                  // Control what happens when the interview finishes
 
                  var outputVariables = event.getParam("outputVariables");
                  var key;
                  for(key in outputVariables) {
                     if(outputVariables[key].name === "myOutput") {
                        // Do something with an output variable
                     }
                  }
               }
            };
            $Lightning.use("c:lightningOutApp", function() {
               // Create the flow component and set the onstatuschange attribute
               $Lightning.createComponent("lightning:flow", {"onstatuschange":statusChange},
                  "flowContainer",
                  function (component) {
                     // Set the input variables
                     var inputVariables = [
                        {
                           name : 'myInput',
                           type : 'String',
                           value : "Hello, world"
                        }
                     ];
                     
                     // Start an interview in the flowContainer div, and 
                     // initializes the input variables.
                     component.startFlow("myFlowName", inputVariables);
                  }
               );
            });
         </script>
      </body>
   </html>
</apex:page>

使用 Visualforce 创建地图

地图传达的信息比单纯的信息更清晰 位置数据。Visualforce 映射 组件使创建使用第三方地图服务的地图变得简单。Visualforce 地图是交互式的, 基于 JavaScript 的地图,包括缩放、平移和基于 Salesforce 或其他数据的标记。创建独立版 地图页面、可插入页面布局的地图,甚至是 Salesforce 的移动地图 应用程序。

Visualforce 提供了一组相关的映射 组件。该组件定义地图 画布,包括大小、类型、中心点和初始缩放级别。子组件定义要放置的标记 在地图上按地址或地理位置(纬度和经度)。您可以使用该组件添加可自定义的 单击或点击标记时显示的信息面板。<apex:map><apex:mapMarker><apex:mapInfoWindow>

注意

Visualforce 映射组件不是 在 Developer Edition 组织中可用。

您在 Visualforce 标记中定义的地图会生成 JavaScript 代码 以呈现到页面上。此 JavaScript 连接到地图服务,并通过以下方式构建地图 获取地图图块并放置标记。如果要映射的项目没有纬度和 经度,Visualforce 地图可以进行地理编码 他们的地址。地图渲染后,用户可以通过平移和 缩放,就像他们习惯于其他地图站点一样。效果就好像你自己写的一样 自定义 JavaScript 与第三方映射服务进行交互,但实际上不需要 写出来。您可以在 Visualforce 中定义地图并免费获取地图 JavaScript。

重要

Visualforce 映射 组件将 JavaScript 添加到页面,并使用第三方 JavaScript 代码绘制地图。

  • Visualforce 添加的 JavaScript 使用 行业标准最佳实践,避免与在同一 JavaScript 上执行的其他 JavaScript 发生冲突 页。如果您自己的 JavaScript 没有使用最佳实践,它可能会与 映射代码。
  • 需要地理编码的地址 – 即不包含纬度和 经度 – 发送到第三方服务进行地理编码。这些地址未关联 ,除了您在 Visualforce 标记中提供的数据外,不会发送其他数据。但是,如果您的 组织要求严格控制在 Salesforce 外部共享的数据,不要使用地理编码 Visualforce地图的功能。
  • 创建基本地图 没有标记的基本地图
    只需要一个组件。此组件定义地图的基本画布,包括其尺寸、位置和初始缩放级别。<apex:map>
  • 向地图添加位置标记 您可以使用该组件向地图
    添加标记以表示特定位置。您可以包括指针悬停在标记上时显示的文本。<apex:mapMarker>
  • 使用自定义标记图标
    Visualforce 地图标记图标功能强大,但很普通。要区分标记并向地图添加细节或样式,请使用自定义地图标记图标。
  • 将信息窗口添加到标记
    信息窗口允许您在地图上显示额外的详细信息。当用户单击或点击标记时,将显示信息窗口。
  • 在 Apex 中构建地图数据的示例 在 Apex
    中构建位置数据以执行自定义查询、搜索附近位置、筛选或转换结果,或者在无法使用 Visualforce 标准控制器返回的结果时执行。

创建基本地图

没有标记的基本地图只需要一个组件。此组件定义地图的基本画布,包括其 尺寸、位置和初始缩放级别。

<apex:map>该属性定义围绕该点的点 地图居中。您可以在以下几个方面提供值 格式。

centercenter

  • 一个表示地址的字符串。例如,“1 Market Street, San Francisco, CA”。这 对地址进行地理编码以确定其纬度和经度。
  • 一个字符串,表示具有指定位置的 JSON对象和属性 坐标。例如,“{latitude: 37.794, longitude: -122.395}”。latitudelongitude
  • 类型为 的 Apex 映射对象 with 和 键指定位置坐标。Map<String, Double>latitudelongitude

如果没有子标记,则该属性是必需的。

<apex:map><apex:mapMarker>center这张简单的街道地图显示了 Salesforce 旧金山周围的社区 总部。

<apex:page >

    <h1>Salesforce in San Francisco</h1>
  
    <!-- Display the address on a map -->
    <apex:map width="600px" height="400px" mapType="roadmap" zoomLevel="16"
        center="One Market Street, San Francisco, CA">
    </apex:map>
    
</apex:page>

这 代码生成以下映射。

请注意此示例中的以下内容。

  • 映射的地址没有标记。该组件本身不显示地图标记,即使对于中心点也是如此。要显示最多 100 个标记,添加子组件。<apex:map><apex:mapMarker>
  • 地图的位置值以 街道地址,而不是地理位置。地图服务查找纬度和经度 地址。此过程称为地理编码。您最多可以将 10 个地理编码地址作为属性或作为随组件添加的标记包含在地图中。centercenter<apex:mapMarker>
  • 值是“路线图”,一条标准街道 地图。其他选项是“卫星”和“混合”。mapType

向地图添加位置标记

您可以使用该组件向地图添加标记以表示特定位置。您可以包含以下文本 当指针悬停在标记上时显示。

<apex:mapMarker>

若要在地图上放置标记,请添加一个组件作为关联 .您可以使用属性指定标记的位置。(可选)使用该属性在指针悬停时显示文本 标记。<apex:mapMarker><apex:map>positiontitle您最多可以加到

100地图上的标记。 使用迭代组件添加 集合或列表中的多个标记。

<apex:repeat>

注意

Visualforce 地图可以是 资源密集型,可能导致移动浏览器和 Salesforce 内存问题 应用程序。具有许多标记的地图或用作自定义标记的大图像可以进一步增加 内存消耗。如果您计划在使用的页面中部署 Visualforce 地图 在移动设备环境中,请务必彻底测试这些网页。该属性定义地图上的点 放置标记。您可以提供值 有几种格式。

positionposition

  • 一个表示地址的字符串。例如,“1 Market Street, San Francisco, CA”。这 对地址进行地理编码以确定其纬度和经度。
  • 一个字符串,表示具有指定位置的 JSON对象和属性 坐标。例如,“{latitude: 37.794, longitude: -122.395}”。latitudelongitude
  • 类型为 的 Apex 映射对象 with 和 键指定位置坐标。Map<String, Double>latitudelongitude

注意

您最多可以拥有10每个地图的地理编码地址查找。对组件属性和组件属性的查找都计入此限制。自 显示更多标记,提供 不需要地理编码。超过地理编码限制的位置将被跳过。center<apex:map>position<apex:mapMarker>position下面是一个页面,其中显示了 帐户,以帐户的 地址。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/NearbyContacts?id=001D000000JRBet -->
  
  <apex:pageBlock >
    <apex:pageBlockSection title="Contacts For {! Account.Name }">
    
     <apex:dataList value="{! Account.Contacts }" var="contact">
       <apex:outputText value="{! contact.Name }" />
     </apex:dataList> 
    
  <apex:map width="600px" height="400px" mapType="roadmap"
    center="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}">

    <apex:repeat value="{! Account.Contacts }" var="contact">
    <apex:mapMarker title="{! contact.Name }"
       position="{!contact.MailingStreet},{!contact.MailingCity},{!contact.MailingState}"
    />
    </apex:repeat>

  </apex:map>

    </apex:pageBlockSection>
  </apex:pageBlock>

</apex:page>

这 代码生成以下映射。

请注意此示例中的以下内容。

  • 和属性作为 Visualforce 表达式传递,该表达式 连接地址元素以提供可进行地理编码的地址字符串。centerposition
  • 由于此页面对地址使用地理编码,因此仅显示前 9 个地址 接触。属性使用一个地理编码查找作为 允许 10 个。(在图中,该帐户只有三个联系人。center<apex:map>

使用自定义标记图标

Visualforce 地图标记图标 功能齐全,但朴素无比。要区分标记并向地图添加细节或样式,请使用 自定义地图标记图标。要自定义标记的图标,请将属性设置为绝对或完全限定的 URL 要使用的图形。您可以引用 Web 上的任何图像,例如,如果您的图形是 分布在 CDN 中。您还可以使用存储在静态资源中的图形。如果您使用图像 从静态资源中,使用 URLFOR() 函数获取图像 URL。为 例:

icon

<apex:mapMarker title="{! Account.Name }" 
    position="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}"
    icon="{! URLFOR($Resource.MapMarkers, 'moderntower.png') }" />

使用常见的图形格式,例如 PNG、GIF 或 JPEG格式。首选标记大小为 32 × 32 像素。其他尺寸是缩放的,但不会 始终产生理想的结果。

注意

Visualforce 地图可以是 资源密集型,可能导致移动浏览器和 Salesforce 内存问题 应用程序。具有许多标记的地图或用作自定义标记的大图像可以进一步增加 内存消耗。如果您计划在使用的页面中部署 Visualforce 地图 在移动设备环境中,请务必彻底测试这些网页。此完整页面演示了如何使用自定义标记来指示帐户的位置,以及 帐户的标准标记 接触。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/AccountContacts?id=001D000000JRBet -->
  
  <apex:pageBlock >
    <apex:pageBlockSection title="Contacts For {! Account.Name }">
    
      <apex:dataList value="{! Account.Contacts }" var="contact">
        <apex:outputText value="{! contact.Name }" />
      </apex:dataList> 
      
      <apex:map width="600px" height="400px" mapType="roadmap"
  center="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}">

      <!-- Add a CUSTOM map marker for the account itself -->
      <apex:mapMarker title="{! Account.Name }" 
  position="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}"
       icon="{! URLFOR($Resource.MapMarkers, 'moderntower.png') }"/>
      
      <!-- Add STANDARD markers for the account's contacts -->
      <apex:repeat value="{! Account.Contacts }" var="ct">
        <apex:mapMarker title="{! ct.Name }" 
          position="{! ct.MailingStreet },{! ct.MailingCity },{! ct.MailingState }">
        </apex:mapMarker>
      </apex:repeat>

      </apex:map>
    
    </apex:pageBlockSection>
  </apex:pageBlock>
   
</apex:page>

这 代码生成以下映射。

要对迭代中添加的标记使用不同的图标,例如 ,请使用与迭代相关的表达式 变量来定义 URL。一种简单的方法是在 记录。另一种方法是在自定义公式字段中提供图标名称。<apex:repeat>这是上一个块,带有 假定联系人对象具有名为“ContactType__c”的自定义字段的变体,并且 每种触点类型都有一个相应的名称 图标。

<apex:repeat>

<!-- Add CUSTOM markers for the account's contacts -->
    <apex:repeat value="{! Account.Contacts }" var="ct">
        <apex:mapMarker title="{! ct.Name }" 
          position="{! ct.MailingStreet },{! ct.MailingCity },{! ct.MailingState }"
          icon="{! URLFOR($Resource.MapMarkers, ct.ContactType__c + '.png') }">
        </apex:mapMarker>
    </apex:repeat>

如果 您使用字段来提供图标 URL 的关键部分,确保它始终提供 可用值。例如,通过将其设为必填字段,或确保公式字段 提供合理的默认值。

将信息窗口添加到标记

信息窗口允许您在地图上显示额外的详细信息。当用户 单击或点击标记。

地图标记属性允许您显示 当用户将鼠标悬停在标记上时,少量信息。要显示更多信息或 可以更好地控制其格式,使用信息窗口代替属性或添加属性。titletitle

例如,您可以显示联系人地址的完整详细信息,格式为最佳 显示。您可以添加可点击的电话链接,甚至可以显示对象的个人资料照片 有一个。

要将信息窗口添加到地图标记,请将组件添加为 关联的 .组件的主体显示在 用户单击或点击标记时的信息窗口,可以是 Visualforce 标记、HTML 和 CSS,或者 纯文本。<apex:mapInfoWindow><apex:mapMarker><apex:mapInfoWindow>此完整页面使用 Visualforce 标记 信息内容 窗。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/AccountContactsCustomMarker?id=001D000000JRBet -->
  
  <apex:pageBlock >
    <apex:pageBlockSection title="Contacts For {! Account.Name }">
    
      <apex:dataList value="{! Account.Contacts }" var="contact">
        <apex:outputText value="{! contact.Name }" />
      </apex:dataList> 
      
      <apex:map width="600px" height="400px" mapType="roadmap"
  center="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}">

      <!-- Add markers for account contacts -->
      <apex:repeat value="{! Account.Contacts }" var="ct">
        <apex:mapMarker title="{! ct.Name }" 
          position="{! ct.MailingStreet },{! ct.MailingCity },{! ct.MailingState }">

          <!-- Add info window with contact details -->
          <apex:mapInfoWindow >
            <apex:outputPanel layout="block" style="font-weight: bold;">
              <apex:outputText>{! ct.Name }</apex:outputText>
            </apex:outputPanel>

            <apex:outputPanel layout="block">
              <apex:outputText>{! ct.MailingStreet }</apex:outputText>
            </apex:outputPanel>               

            <apex:outputPanel layout="block">
              <apex:outputText>{! ct.MailingCity }, {! ct.MailingState }</apex:outputText>
            </apex:outputPanel>               

            <apex:outputPanel layout="block">
              <apex:outputLink value="{! 'tel://' + ct.Phone }">
                  <apex:outputText>{! ct.Phone }</apex:outputText>
              </apex:outputLink>
            </apex:outputPanel>               
          </apex:mapInfoWindow>

        </apex:mapMarker>
      </apex:repeat>

      </apex:map>
    
    </apex:pageBlockSection>
  </apex:pageBlock>
   
</apex:page>

这 代码生成以下映射。

默认情况下,一次只显示一个信息窗口。当您单击另一个标记时,第一个标记 “信息”窗口将关闭,而“新建信息”窗口将打开。要同时显示多个信息窗口, 设置为 在包含组件上。

showOnlyActiveInfoWindowfalse<apex:map>

注意

仔细考虑显示多个的效果 信息窗口,因为它可以创建杂乱的地图。

在 Apex 中构建地图数据的示例

在 Apex 中构建您的位置数据以执行自定义查询,搜索附近 位置、过滤或转换结果,或者当您无法使用 Visualforce 标准返回的结果时 控制器。

Apex 代码使您可以完全控制返回并用于 地图和标记。您还可以使用 Apex 返回来自 Salesforce 外部的结果。此页面最多显示 10 个离用户最近的仓库 位置。

<apex:page controller="FindNearbyController" docType="html-5.0" >

    <!-- JavaScript to get the user's current location, and pre-fill
         the currentPosition form field. -->
    <script type="text/javascript">
        // Get location, fill in search field
    	function setUserLocation() {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(function(loc){
                    var latlon = loc.coords.latitude + "," + loc.coords.longitude;
                    var el = document.querySelector("input.currentPosition");
                    el.value = latlon;
                });
            }
        }
        // Only set the user location once the page is ready
    	var readyStateCheckInterval = setInterval(function() {
    		if (document.readyState === "interactive") {
        		clearInterval(readyStateCheckInterval);
        		setUserLocation();
    		}
		}, 10);
    </script>
    
    <apex:pageBlock >
        <!-- Form field to send currentPosition in request. You can make it
             an <apex:inputHidden> field to hide it. -->
        <apex:pageBlockSection >
            <apex:form >
                <apex:outputLabel for="currentPosition">Find Nearby</apex:outputLabel> 
                <apex:input size="30" 
                     html-placeholder="Attempting to obtain your position..."
                     id="currentPosition" styleClass="currentPosition" 
                     value="{!currentPosition}" />
                <apex:commandButton action="{!findNearby}" value="Go!"/>
            </apex:form>
        </apex:pageBlockSection>
        
        <!-- Map of the results -->
        <apex:pageBlockSection rendered="{!resultsAvailable}" title="Locations">
            <apex:map width="600px" height="400px">
                <apex:repeat value="{!locations}" var="pos">
                    <apex:mapMarker position="{!pos}"/>
                </apex:repeat>
            </apex:map>
        </apex:pageBlockSection>

    </apex:pageBlock>

</apex:page>

这 代码生成以下映射。

此页面有三个重要部分。

  • 开头的 JavaScript 块说明了如何访问 浏览器的内置功能,可以询问用户的当前位置。此代码 更新可见的表单字段。但是,您可以轻松使用隐藏的表单字段 而是避免显示原始纬度和经度及其不太可能的级别 的精度。
  • 第一个包含一个简短的表单,用于在 POSTBACK 中提交用户的位置 请求。出于说明目的,它是可见的,需要单击,但这是 不需要。<apex:pageBlockSection>
  • 在第二种情况下,地图本身很简单,只需要 五行代码。所有的复杂性都在表达式中,它访问 Apex 中的属性 控制器。<apex:pageBlockSection>{!locations}请注意属性的用法,该属性采用表达式的值。这 expression 是另一个 Apex 属性,将它与该属性一起使用会隐藏 map 部分 当位置无法放置在地图上时。rendered{!resultsAvailable}rendered

这是支持以前的 Apex 控制器 页。

public with sharing class FindNearbyController {

    public List<Map<String,Double>> locations { get; private set; }

    public String currentPosition { 
        get {
            if (String.isBlank(currentPosition)) {
                currentPosition = '37.77493,-122.419416'; // San Francisco
            }
            return currentPosition;
        }
        set; 
    }
    
    public Boolean resultsAvailable {
        get {
            if(locations == Null) {
                return false;
            }
            return true;
        }
    }
    
    public PageReference findNearby() {
        String lat, lon;

        // FRAGILE: You'll want a better lat/long parsing routine
        // Format: "<latitude>,<longitude>" (must have comma, but only one comma)
        List<String> latlon = currentPosition.split(',');
        lat = latlon[0].trim();
        lon = latlon[1].trim();

        // SOQL query to get the nearest warehouses
        String queryString =
           'SELECT Id, Name, Location__longitude__s, Location__latitude__s ' +
           'FROM Warehouse__c ' +
           'WHERE DISTANCE(Location__c, GEOLOCATION('+lat+','+lon+'), \'mi\') < 20 ' +
           'ORDER BY DISTANCE(Location__c, GEOLOCATION('+lat+','+lon+'), \'mi\') ' +
           'LIMIT 10';

        // Run the query
        List <Warehouse__c> warehouses = database.Query(queryString);
        
        if(0 < warehouses.size()) {
            // Convert to locations that can be mapped
            locations = new List<Map<String,Double>>();
            for (Warehouse__c wh : warehouses) {
                locations.add(
                    new Map<String,Double>{
                        'latitude' => wh.Location__latitude__s, 
                        'longitude' => wh.Location__longitude__s
                    }
                );
            }
        }
        else {
            System.debug('No results. Query: ' + queryString);
        }
                
        return null;
    }
}

花几分钟时间了解有关此控制器的更多信息,以及它如何与 Visualforce 页面配合使用。

  • 该属性是元素的列表。此列表以可直接使用的格式保存位置数据 按组件。locationsMap<String,Double><apex:mapMarker>
  • 该属性捕获 从页面表单提交的位置信息。此属性 此外,如果表单提交为空,则有效的默认值为 提供。(更健壮的实现将对表单进行更多错误检查 输入。currentPosition
  • 该属性,在 前面对 Visualforce 标记的描述。resultsAvailable
  • 操作方法被调用 当 Go! 被按下。这 方法完成所有工作,执行自定义 SOQL 查询并处理 结果到属性中 格式。findNearby<apex:commandButton>locations

如果要使用 的属性来提供额外的 信息(例如,仓库名称),您有多种选择。如果你的 方法返回 sObjects,您可以在 Visualforce 标记中引用相应的字段。如果你是 直接创建新对象,就像我们在这里一样,你可以创建一个内部类 将位置地图对象与标题字符串组合在一起。然后,返回 内部类对象到页面。title<apex:mapMarker>