VisualForce-开发指南-全-
VisualForce 开发指南(全)
原文:
zh.annas-archive.org/md5/eb1996a16a38ea3308833562ee978790译者:飞龙
前言
Visualforce 开发者指南 是一本实用的、动手操作的口袋指南,它提供了清晰简单的指导,用于在 Force.com 平台和移动应用程序上开发 Visualforce 页面。它还包含一个单一、连续的真实世界示例,并附有代码示例。本书以简单的方式解释了 Visualforce 的概念和技术方面。
Visualforce 开发者指南 涵盖了主要主题,从为 Force.com 平台开发 Visualforce 开始,继续到快速且无痛苦地为移动应用程序开发 Visualforce 页面。
Force.com 平台可以自动生成用户界面(标准页面),但在某些情况下,你可能需要构建一个更定制的 UI。Visualforce 允许开发者构建复杂和定制的用户界面,这些界面可以原生地托管在 Force.com 平台上。Visualforce 是一个包含类似于 HTML 的基于标签的标记语言的框架。
本书涵盖的内容
第一章, Visualforce 入门,解释了 MVC 模型和 Visualforce 架构。我们将介绍 Visualforce 页面。我们将了解 MVC 架构并讨论 Visualforce 页面。进一步地,我们将了解 Visualforce 页面的架构。我们将定义 Visualforce 页面的优势,并了解 Visualforce 开发工具。
第二章,控制器和扩展,介绍了可用于 Visualforce 页面的控制器和扩展类型。我们将了解控制器类型,并查看一些示例。
第三章, Visualforce 和标准 Web 开发技术,解释了如何使用 CSS、JavaScript、jQuery 和 HTML5 等标准 Web 开发技术来开发 Visualforce 页面。本章还包括了 CSS、JavaScript 和 jQuery 静态资源的用法。
第四章, Visualforce 自定义组件,概述了 Visualforce 自定义组件,并进一步解释了如何创建自定义组件以及自定义组件的用法。
第五章, 动态 Visualforce 绑定,解释了与标准对象和自定义对象使用动态引用的用法,如何引用 Apex Maps 和 Lists,以及如何处理字段集。
第六章, Visualforce 图表,讨论了 Visualforce 图表,这是一个组件集合,它提供了一种简单直观的方式来在 Visualforce 页面和自定义组件中创建图表。
第七章,移动端 Visualforce,介绍了如何通过使用 Visualforce Mobile 将建立在 Force.com 平台上的应用程序扩展到移动设备。
第八章,Visualforce 开发最佳实践,解释了 Visualforce 和 Apex 开发的最佳实践。我们将探讨如何提高用户体验,并检查一些 Visualforce 开发的编码标准。
附录,Apex 和 Visualforce 开发安全提示,提供了一些 Visualforce 和 Apex 开发的安全提示。我们将探讨一些用于扫描代码以检查安全和质量的工具,并学习一些安全漏洞。
您需要这本书的物品
Force.com 平台的要求如下:
-
互联网/网站的基本知识
-
如果您有一个免费的 Developer Force 账户(如果没有,请访问:
events.developerforce.com/signup) -
Visualforce 的基本知识
这本书的适用对象
Visualforce 开发者指南不是 Visualforce 开发的完整参考或圣经。这是一本节省时间的口袋指南,包括 Visualforce 开发中最需要和常用的技术方面。因此,这本书适合对 Visualforce 有基本知识的 Force.com 开发者。
习惯用法
在这本书中,您会发现许多不同风格的文本,用于区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。
文本中的代码词如下所示:"我们可以通过使用$Resource全局变量而不是硬编码文档 ID,在页面标记中通过名称引用静态资源。"
代码块设置如下:
<apex:page controller="TransientExampleController">
Non Transient Date: {!t1} <br/>
Transient Date : {!t2} <br/>
<apex:form >
<apex:commandLink value="Refresh"/>
</apex:form>
</apex:page>
新术语和重要词汇以粗体显示。您在屏幕上看到的词,例如在菜单或对话框中,在文本中如下所示:
"组件参考"是探索不同组件及其用途的最佳场所。
注意
警告或重要注意事项如下所示。
小贴士
小技巧和窍门如下所示。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中获得最大收益的标题非常重要。
要向我们发送一般反馈,只需发送一封电子邮件到<feedback@packtpub.com>,并在邮件主题中提及书名。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从 www.packtpub.com 下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
错误清单
尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击错误清单提交表单链接,并输入您的错误清单详情。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的“错误清单”部分。任何现有的错误清单都可以通过从 www.packtpub.com/support 选择您的标题来查看。
盗版
在互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。
第一章. Visualforce 入门
云计算对 IT/软件开发行业产生了重大变革。云平台是云计算的重要方向之一。云平台允许开发者开发应用程序并在云上运行它们,包括用于构建按需应用程序和平台即服务(PaaS)的平台。Salesforce.com 推出了第一个按需平台,称为 Force.com。
本章将向您介绍 Visualforce。我们将探讨 MVC 架构和 Visualforce,并进一步了解 Visualforce 页面的架构。我们将定义 Visualforce 页面的优势,并对 Visualforce 开发工具有一个大致的了解。
本章涵盖以下主题:
-
MVC 模型
-
理解 Visualforce
-
Visualforce 架构
-
Visualforce 的优势
-
Visualforce 开发工具
让我们开始吧,进入 Visualforce 的世界。
MVC 模型
Force.com 平台使用模型-视图-控制器(MVC)架构模式来开发应用程序。MVC 将应用程序开发工具分为以下几部分:
-
模型:这定义了数据结构。在 Force.com 中,对象定义了数据模型。Salesforce 通过将每个实体映射到某个对象来设计平台。
-
视图:这定义了数据是如何被表示的。在 Force.com 中,页面布局和 Visualforce 页面属于这一类别。
-
控制器:这定义了业务逻辑。操纵数据的规则和动作控制视图。在 Force.com 中,Apex类、触发器、工作流、审批和验证规则属于这一类别。
![MVC 模型]()
MVC 架构
理解 Visualforce
在 Force.com 平台中,我们可以使用自定义对象和标准对象来开发 Force.com 应用程序。每个对象都有一个标准用户界面,包含一个或多个页面布局。但是,我们不能使用标准页面布局来满足复杂需求。在这里,Visualforce 就派上用场了。
Visualforce 是一个基于 Web 的 UI 框架,可用于构建复杂、吸引人和动态的自定义用户界面。Visualforce 允许开发者使用标准 Web 开发技术,如 jQuery、JavaScript、CSS 和 HTML5。因此,我们可以为任何应用程序(包括移动应用程序)构建丰富的 UI。我们将在后面更深入地讨论使用标准 Web 开发技术进行 Visualforce 开发以及 Visualforce 移动开发。类似于 HTML,Visualforce 框架包括基于标签的标记语言。
Visualforce 页面有两个主要元素,称为 Visualforce 标记和 Visualforce 控制器。Visualforce 标记由前缀为 apex: 的 Visualforce 标签组成,其中还可以包含 HTML 标签、JavaScript 或任何其他标准 Web 开发代码。Visualforce 控制器包含一组指令,用于通过用户交互操作数据和模式。它还控制界面。与对象一起创建的标准控制器可以用作 Visualforce 控制器。标准控制器具有与标准页面中使用的相同逻辑和功能。但是,当我们需要使用不同的逻辑或功能时,我们可以编写自己的 Apex 控制器类,也可以使用 Apex 语言编写标准控制器或自定义控制器的扩展。Visualforce 中还有 AJAX 组件、动作的表达式语言公式和组件绑定交互。
Visualforce 架构
Visualforce 是一种类似于 HTML 的标记语言。Visualforce 页面在 Force.com 平台上运行,它可以与标准 Web 开发技术如 JavaScript、jQuery 以及通过 CSS 进行样式化集成。它允许我们构建更丰富和动态的用户界面。每个页面都可以通过一个唯一的页面名称来识别。因此,Visualforce 页面有一个唯一的 URL 用于访问页面。开发者可以通过在地址栏中输入页面名称来创建一个 Visualforce 页面,如下面的屏幕截图所示:

从 URL 创建 Visualforce 页面
创建页面后,我们可以使用相同的 URL 访问页面。Visualforce 标记具有一组特殊的组件,类似于其他标记的标签库系统。这些组件允许我们使用单个标签创建复杂的组件。这些组件在服务器上被处理和渲染,最终交付给客户端。与仅客户端方法相比,这种方法具有更高的性能和更丰富的功能。Visualforce 页面运行在以下图示所示的平台上:

在服务器上保存 Visualforce 时的执行流程
上述图示说明,每次开发者在平台上保存一个 Visualforce 页面时,平台都会编译标记和相关的控制器。在编译成功后,标记会被转换成 Visualforce 渲染器能够理解的一组抽象指令。如果有任何编译错误,则停止保存页面并返回错误给开发者。如果保存尝试成功完成,则指令会被保存到元数据仓库并发送到 Visualforce 渲染器。渲染器将指令转换为浏览器能够理解的 HTML,然后刷新页面,如下面的图示所示:

Visualforce 页面的执行流程
上述图示说明了当非开发者用户请求 Visualforce 页面时,应用程序服务器从元数据存储库检索页面,然后将其发送到 Visualforce 渲染器进行 HTML 转换。由于页面在开发期间已经被编译成指令,因此不需要编译。
Visualforce 的优势
以下是 Visualforce 对开发者的优势:
-
模型-视图-控制器开发风格:Visualforce 通过在 Force.com 平台上提供应用程序的视图来遵循 MVC 模式。视图由用户界面和 Visualforce 标记定义。与 Visualforce 标记相关联的 Visualforce 控制器负责业务逻辑。因此,设计师和开发者可以分别工作,设计师专注于用户界面,开发者专注于业务逻辑。
-
用户友好的开发:具有管理员配置文件的开发者用户可以在每个 Visualforce 页面的底部拥有一个 Visualforce 编辑器面板。此编辑器面板由用户记录的开发模式选项控制。此功能允许我们同时在一个窗口中编辑和查看结果页面。此 Visualforce 编辑器具有代码保存功能,包括自动编译和语法高亮。
-
一系列现成的 Visualforce 组件:Visualforce 在几个类别中提供了一套标准组件。例如,有输出组件,如
<apex:outputPanel>、<apex:outputField>、<apex:outputText>、<apex:pageBlock>等。还有输入组件,例如<apex:inputFile>、<apex:inputField>、<apex:inputText>、<apex:selectList>等。这些输入和输出组件具有一个称为数据驱动默认值的功能。例如,当我们在一个特定的 Visualforce 页面中指定<apex:inputField>组件时,<apex:inputField>标签为该字段提供与数据类型相关的编辑界面的小部件(例如,日期字段有日历,而电子邮件/电话字段有它们特定的验证)。还有 AJAX 组件,例如<apex:actionStatus>。AJAX 组件允许用户提高特定界面的交互性水平。 -
与 Salesforce 紧密集成/扩展自定义组件:一个 Visualforce 页面可以拥有自定义控制器以及标准控制器。标准控制器是在创建对象时创建的,可以用于 Visualforce 控制器。标准控制器具有与标准页面中使用的相同逻辑和功能。Visualforce 页面遵循这些标准化方法和功能。我们还可以使用自定义组件扩展标准组件。例如,我们可以使用扩展类来扩展特定 Visualforce 页面的标准控制器。我们可以创建自己的 Visualforce 自定义组件,而不是使用 Visualforce 内置组件,例如
<apex:inputFile>、<apex:inputField>、<apex:outputField>等。在接下来的两章中,我们将讨论更多关于 Visualforce 控制器和 Visualforce 自定义组件的内容。 -
使用 Web 技术灵活且可定制:通过使用 Web 技术,例如 JavaScript、CSS、jQuery、Flash 等,Visualforce 标记更加灵活和可定制,因为最终它会被渲染成 HTML。设计师可以使用 Visualforce 标签与这些 Web 技术一起使用。
Visualforce 开发工具
我们可以通过导航到您的姓名 | 设置 | 开发 | 页面来编辑和查看 Visualforce 页面。
但是,这并不是开发 Visualforce 页面的最佳方式。还有其他几种构建 Visualforce 页面的方法,具体如下:
-
Visualforce 编辑器面板:这将在 Visualforce 的优势部分进行讨论。
-
Force.com IDE:这个 IDE 用于创建和编辑 Visualforce 页面、自定义 Visualforce 组件、静态资源和控制器。
-
Force.com 的 Eclipse 插件:这与 Force.com IDE 相同,我们可以用它来创建和编辑 Visualforce 页面、自定义 Visualforce 组件、静态资源和控制器,这些都是 Force.com IDE 的主要功能之一。
概述
在本章中,我们熟悉了 MVC 模型和 Visualforce。我们了解了 Visualforce 架构和 Visualforce 的优势,以及各种 Visualforce 开发工具。
第二章 控制器和扩展
一组可以响应用户与 Visualforce 标记(例如,按钮点击或链接点击)交互的指令称为控制器。控制器可以控制页面的行为,并可以用来访问应在页面上显示的数据。
本章将向您介绍几种可用于 Visualforce 页面的控制器和扩展类型。我们将通过示例学习控制器的类型。
本章涵盖以下主题:
-
标准控制器
-
标准列表控制器
-
自定义控制器和控制器扩展
-
在 Visualforce 页面上处理大量数据
-
Visualforce 页面的执行顺序
-
验证规则和标准控制器或自定义控制器
-
使用 transient 关键字
-
创建自定义控制器和控制器扩展的注意事项
让我们更详细地看看控制器和扩展...
本章包含一系列示例,以解释 Visualforce 的重要元素和功能。从本章开始,我们将构建一个订单处理应用程序。该应用程序中有四个自定义对象(API 名称:Customer__c、Item__c、Order__c、Order_Line__c)。以下是我们将在 Force.com 平台上创建的订单处理应用程序的 E-R 图:

订单处理应用程序的 E-R 图
标准控制器
Force.com 平台提供了几种类型的控制器。第一种是标准控制器,每个sObject都有一个标准控制器。它们具有与它们最初在标准页面中使用的相同逻辑和功能。因此,我们可以使用标准控制器与 Visualforce 页面一起使用。例如,如果我们为 Visualforce 页面使用联系人标准控制器,我们可以实现联系人的标准Save方法,而无需编写任何额外的 Apex 代码。这种行为与在标准联系人编辑页面上实现Save方法的行为相同。
如何在 Visualforce 页面上使用标准控制器
<apex:page>标签有一个名为standardController的属性,用于将标准控制器与 Visualforce 页面关联。standardController属性的值将是 sObject 的 API 名称:
<apex:page standardController="Customer__c">
</apex:page>
上一段代码显示了standardController属性的用法。
提示
您不能同时使用standardController和controller属性。
标准控制器操作
在 Visualforce 页面上,我们可以为以下标准 Visualforce 组件定义action属性:
-
<apex:commandButton>:此组件创建一个按钮,调用一个操作 -
<apex:commandLink>:此组件创建一个链接,调用一个操作 -
<apex:actionPoller>:此组件定期调用一个操作 -
<apex:actionSupport>:此组件在另一个命名的组件上触发事件(例如onclick、onmouseover等)并调用一个操作 -
<apex:actionFunction>: 此组件定义了一个新的 JavaScript 函数,该函数调用一个动作 -
<apex:page>: 此组件在页面加载时调用一个动作
可以使用{!}符号从页面调用动作方法。例如,如果您的动作方法名称是MyFirstMethod,则可以从页面标记中使用{!MyFirstMethod}符号来调用动作方法。
提示
此动作方法可以来自标准控制器、自定义控制器或控制器扩展。
标准控制器有几个标准动作方法,如下所示:
-
save: 此方法插入/更新一条记录。成功完成后,将重定向到标准详情页面或自定义 Visualforce 页面。 -
quicksave: 此方法插入/更新一条记录。没有重定向到详情页面或自定义 Visualforce 页面。 -
edit: 此方法将用户导航到当前记录的编辑页面。成功完成后,将返回调用动作的页面。 -
delete: 此方法删除当前记录。通过选择最近查看的列表过滤器将用户重定向到列表视图页面。 -
cancel: 此方法取消编辑操作。成功完成后,将返回调用编辑动作的页面。 -
list: 此方法通过选择最近查看的列表过滤器重定向到列表视图页面。
例如,以下页面允许我们插入新客户或更新现有客户记录。如果我们打算使用此页面来更新客户记录,则必须使用 ID 查询字符串参数指定 URL。每个标准控制器都有一个 getter 方法,该方法返回页面 URL 中指定的 ID 查询字符串参数指定的记录。当我们点击保存时,标准控制器上的save动作被触发,并更新客户详情。如果我们打算使用此页面来插入客户记录,则不得指定 URL 作为参数。在这种情况下,当我们点击保存时,标准控制器上的save动作被触发,并插入新的客户记录。
<apex:page standardController="Customer__c">
<apex:form >
<apex:pageBlock title="New Customer" mode="edit">
<apex:pageBlockButtons >
<apex:commandButton action="{!save}" value="Save"/>
</apex:pageBlockButtons>
<apex:pageBlockSection title="My Content Section" columns="2">
<apex:inputField value="{!Customer__c.Name}"/>
<apex:inputField value="{!Customer__c.Email__c}"/>
<apex:inputField value="{!Customer__c.Address__c}"/>
<apex:inputField value="{!Customer__c.Telephone__c}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
提示
页面标记允许您通过使用{!sObjectAPIName.FieldAPIName}来访问特定 sObject 的字段。例如,如果您想访问Customer对象的Email字段,使用Customer__c标准控制器的页面可以使用{!Customer__c.Email__c}来返回当前上下文中客户的Email字段的值。
以下页面允许我们查看客户记录。在此页面中,也必须在 ID 查询字符串参数中指定 URL。Customer__c标准控制器的 getter 方法返回页面 URL 中指定的 ID 查询字符串参数指定的记录:
<apex:page standardController="Customer__c">
<apex:form >
<apex:pageBlock title="Customer" mode="edit">
<apex:pageBlockButtons >
<apex:commandButton action="{!save}" value="Save"/>
</apex:pageBlockButtons>
<apex:pageBlockSection title="Customer Details" columns="2">
<apex:outputField value="{!Customer__c.Name}"/>
<apex:outputField value="{!Customer__c.Email__c}"/>
<apex:outputField value="{!Customer__c.Address__c}"/>
<apex:outputField value="{!Customer__c.Telephone__c}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
提示
要检查特定对象对登录用户的可访问性,您可以使用 {!$ObjectType.objectname.accessible} 语法。此表达式返回一个布尔值。例如,如果您想检查 Customer 对象的可访问性,您可以使用 {!$ObjectType.Customer__c.accessible}。
<apex:page standardController="Customer__c">
<apex:form >
<apex:pageBlock title="New Customer" mode="edit">
<apex:pageBlockButtons >
<apex:commandButton rendered="{!$ObjectType.Customer__c.accessible}" action="{!save}" value="Save"/>
</apex:pageBlockButtons>
<apex:pageBlockSection title="Customer Details" columns="2">
<apex:inputField value="{!Customer__c.Name}"/>
<apex:inputField value="{!Customer__c.Email__c}"/>
<apex:inputField value="{!Customer__c.Address__c}"/>
<apex:inputField value="{!Customer__c.Telephone__c}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
以下代码解释了对象可访问性的用法。根据示例,您可以看到保存按钮,只有当特定用户具有访问客户记录的安全权限时才会显示。
标准列表控制器
第二种控制器类型是标准列表控制器,可用于显示或对一组记录(包括相关列表、列表页面和批量操作页面)执行操作。它允许我们在特定页面上过滤记录。我们可以使用标准列表控制器来处理账户、资产、活动、案例、联系人、合同、想法、潜在客户、机会、订单、产品 2、解决方案、用户以及所有自定义对象。
如何在 Visualforce 中使用标准列表控制器
与标准控制器类似,我们可以指定 <apex:page> 组件的 standardController 属性。此外,我们还需要指定 <apex:page> 组件的 recordSetVar 属性。
小贴士
standardController 属性指定了我们想要访问的记录类型。recordSetVar 属性表示页面使用列表控制器,以及记录集合的变量名(用于访问记录集合中的数据)。
以下标记解释了当页面与列表控制器相关联时,页面如何访问记录列表。在以下示例中,您可以引用客户记录列表。
<apex:page standardController="Customer__c" recordSetVar="customers" sidebar="false">
<apex:pageBlock >
<apex:pageBlockTable value="{!customers}" var="a">
<apex:column value="{!a.name}"/>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>
以下截图展示了前述代码的结果:

客户列表示例的结果页面
标准列表控制器操作
所有具有 action 属性的标准 Visualforce 组件都可以与具有标准列表控制器的 Visualforce 页面一起使用。这些组件的使用方式与标准控制器相同。以下操作方法由所有标准列表控制器支持:
-
save:此操作方法插入/更新记录。在成功完成后,它将重定向到标准详情页面或自定义 Visualforce 页面。 -
quicksave:此方法插入/更新记录。没有重定向到详情页面或自定义 Visualforce 页面。 -
List:此方法在用户未指定筛选器 ID 时,通过选择最近查看的列表筛选器将重定向到列表视图页面。 -
cancel:此方法取消编辑操作。在成功完成后,它将返回到调用编辑操作的页面。 -
first:此方法显示集合中的第一页记录。 -
last:此方法显示集合中的最后一页记录。 -
next:此方法显示集合中的下一页记录。 -
previous:此方法显示集合中的上一页记录。
Salesforce 标准页面中的列表视图可用于过滤页面上的显示记录。例如,在客户主页上,您可以从列表视图下拉菜单中选择以 c 开头的视图,查看以字母 c 开头的客户。您可以在与列表控制器关联的页面上实现此功能。
可以向与列表控制器关联的页面添加分页功能。分页功能允许您实现下一页和上一页的操作。
例如,要创建一个包含列表视图和分页的简单客户列表页面,创建一个具有以下标记的页面:
<apex:page standardController="Customer__c" recordSetvar="customers">
<apex:form id="theForm">
<apex:pageBlock title="Viewing Customers">
<apex:pageBlockSection >
<apex:selectList value="{!filterid}" size="1">
<apex:selectOptions value="{!listviewoptions}"/>
<apex:actionSupport event="onchange" rerender="list"/>
</apex:selectList>
</apex:pageBlockSection>
<apex:pageBlockSection id="list">
<apex:dataList var="a" value="{!customers}" type="1">
{!a.name}
</apex:dataList>
</apex:pageBlockSection>
<apex:panelGrid columns="2">
<apex:commandLink action="{!previous}" rerender="list">Previous</apex:commandlink>
<apex:commandLink action="{!next}" rerender="list">Next</apex:commandlink>
</apex:panelGrid>
</apex:pageBlock>
</apex:form>
</apex:page>
以下截图显示了前面代码的结果:

使用分页查看客户列表
小贴士
默认情况下,列表控制器每页返回 20 条记录。要控制每页显示的记录数,请使用控制器扩展来设置pageSize属性。
自定义控制器和控制器扩展
自定义控制器用于实现逻辑和功能,而不使用标准控制器;控制器扩展用于扩展标准控制器或自定义控制器的逻辑和功能。自定义控制器和控制器扩展都是使用 Apex 编写的。
理解自定义控制器
自定义控制器用于实现逻辑和功能,而不使用标准控制器。自定义控制器使用 Apex 编写。以下是一些您可能想要使用自定义控制器的情况:
-
实现完全不同的功能,而不依赖于标准控制器的行为
-
覆盖现有功能
-
为页面创建新操作
-
自定义导航
-
使用 HTTP 调用或 Web 服务
-
使用向导
-
对页面上的信息访问有更大的控制权
-
在不应用权限的情况下运行页面
小贴士
每个页面只能使用一个控制器。
构建自定义控制器
您可以通过设置页面和 Visualforce 编辑器来构建自定义控制器。所有管理员和开发人员功能都包含在设置页面中,您可以从点击页面顶部的您的名字后出现的菜单中找到设置页面。
Visualforce 编辑器允许我们在同一窗口中编辑 Visualforce 页面的标记,并且我们可以看到页面的结果也将显示在同一页上。此编辑器具有自动完成、语法高亮、快速修复功能(开发者可以即时创建组件)以及使用以下方法在保存时编译:
-
通过设置页面:可以通过导航到你的名字 | 设置 | 开发 | Apex 类 | 新建来完成此操作。
-
通过 Visualforce 编辑器:在创建页面后,您可以在
<apex:page>标签的控制器属性中指定自定义控制器的名称,然后点击 保存 按钮。然后,如果您是开发者,页面将要求您创建一个与您输入的名称相同的类。然后,新创建的控制器将在 Visualforce 编辑器中显示,如图下截图所示。小贴士
您可以选择使用
sharing或without sharing关键字编写控制器类,这会影响页面在系统模式或用户模式下运行。![构建自定义控制器]()
通过 Visualforce 编辑器创建自定义控制器
以下类是自定义控制器的一个示例。这个自定义控制器具有从 Item__c 自定义对象检索现有项目列表并添加新项目记录的功能。insertNewItem 是 ItemController 的动作方法。ExistingITems 是一个项目属性列表,用于检索现有项目记录。ExistingITems 属性有一个重写的 get 方法:
public with sharing class ItemController {
//public item property for new insertion
public Item__c NewItem{get;set;}
public ItemController(){
NewItem = new Item__c();
}
//get existing items to show in a table
public List<Item__c> ExistingITems{
get{
ExistingITems = new List<Item__c>();
ExistingITems = [SELECT Id, Name, Item_Name__c, Unit_Price__c FROM Item__c LIMIT 100];
return ExistingITems;
}
set;
}
public PageReference insertNewItem() {
try{
insert NewItem;
//reset public property for new insert
NewItem = new Item__c();
}catch(DmlException ex){
ApexPages.addMessages(ex);
}
return null;
}
}
小贴士
自定义控制器使用非参数化构造函数。您不能为自定义控制器创建包含参数的构造函数。
上述控制器与以下 Visualforce 页面相关联。该页面有两个 <apex:pageBlock> 组件:一个用于显示现有项目记录表,另一个用于插入新项目:
<apex:page controller="ItemController">
<apex:form >
<apex:pageBlock title="Existing Items">
<apex:pageBlockTable value="{!ExistingITems}" var="oneItem" rendered="{!ExistingITems.size > 0}">
<apex:column value="{!oneItem.Item_Name__c}"/>
<apex:column value="{!oneItem.Unit_Price__c}"/>
</apex:pageBlockTable>
<apex:outputText value="No records to display" rendered="{!ExistingITems.size == 0}"></apex:outputText>
</apex:pageBlock>
<apex:pageBlock title="New Item">
<apex:pageMessages ></apex:pageMessages>
<apex:pageBlockSection >
<apex:inputField value="{!NewItem.Item_Name__c}"/>
<apex:inputField value="{!NewItem.Unit_Price__c}"/>
</apex:pageBlockSection>
<apex:pageBlockButtons >
<apex:commandButton action="{!insertNewItem}" value="save"/>
</apex:pageBlockButtons>
</apex:pageBlock>
</apex:form>
</apex:page>
理解控制器扩展
控制器扩展用于扩展标准控制器或自定义控制器的逻辑和功能。控制器扩展不能在没有标准控制器或自定义控制器的情况下存在于页面上。控制器扩展是用 Apex 编写的。当您想要:
-
保持标准或自定义控制器的大部分功能不变,并添加更多功能
-
构建一个根据用户权限运行的 Visualforce 页面
构建控制器扩展
我们可以像构建自定义控制器一样构建控制器扩展。
小贴士
扩展不能独立存在于页面上。它们可以用于带有自定义控制器或标准控制器的 Visualforce 页面。
以下类是控制器扩展的一个简单示例。这个控制器扩展用于扩展 Order__c 自定义对象的标准控制器的逻辑和功能。在这个扩展中,我们有一个参数化构造函数来从标准控制器获取订单记录。getRecord() 是从标准控制器获取记录的方法。prepareFullOrder() 是一个自定义方法,用于查询特定订单的订单行:
public with sharing class OrderViewExtension{
public Order__c CurrentOrder{get;set;}
public List<Order_Line__c> OrderLines{get;set;}
public OrderViewExtension(ApexPages.StandardController controller){
CurrentOrder = new Order__c();
this.CurrentOrder = (Order__c)controller.getRecord();
prepareFullOrder();
}
public void prepareFullOrder(){
OrderLines = new List<Order_Line__c>();
OrderLines = [SELECT Id, Name, Price__c, Item__c, Item__r.Unit_Price__c,Item__r.Item_Name__c, Order__c, Quantity__c FROM Order_Line__c WHERE Order__c =: this.CurrentOrder.Id];
}
}
小贴士
控制器扩展使用一个参数化构造函数,带有 ApexPages.StandardController 类型的参数或自定义控制器类型。
以下 Visualforce 页面使用了前面的控制器扩展。在页面上,我们有一个包含两个部分的页面块。第一部分显示了订单头部的详细信息。第二部分用于显示特定订单的订单行:
<apex:page standardController="Order__c" extensions="OrderViewExtension">
<apex:form >
<apex:pageBlock >
<apex:pageBlockSection title="Order Header">
<apex:outputField value="{!Order__c.Name}"/>
<apex:outputField value="{!Order__c.Customer__c}"/>
<apex:outputField value="{!Order__c.Planned_Delivery_Date__c}"/>
</apex:pageBlockSection>
<apex:pageBlockSection title="Order Lines" columns="1">
<apex:pageBlockTable value="{!OrderLines}" var="line">
<apex:column value="{!line.Name}"/>
<apex:column headerValue="Item">
<apex:outputLink value="/{!line.Item__c}" target="_blank">{!line.Item__r.Item_Name__c}</apex:outputLink>
</apex:column>
<apex:column value="{!line.Item__r.Unit_Price__c}"/>
<apex:column value="{!line.Quantity__c}"/>
<apex:column value="{!line.Price__c}"/>
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
控制器方法
在自定义控制器或控制器扩展中可以使用三种类型的方法:
-
获取方法
-
设置方法
-
动作方法
获取方法
开发者可以使用获取方法在 Visualforce 标记中显示数据库或其他计算值。这意味着获取方法用于将数据从 Apex 控制器传递到 Visualforce 页面。定义获取方法有两种方式。
通常,获取方法命名为 getVariable,其中变量是获取方法返回的属性的名称:
public class GetterSetterExample{
String GetterVariable;
public String getGetterVariable() {
return GetterVariable;
}
}
获取方法可以使用默认的获取和设置方法定义一个属性:
public class GetterSetterExample{
public String GetterVariableDefault{get;set;}
}
变量可以通过 {!} 表达式在 Visualforce 页面上访问。
设置方法
设置方法用于将用户定义的值传递到 Apex 控制器。设置方法与获取方法的定义方式相同。以下示例使用默认的获取和设置方法搜索数据库中已存在的项目:
public with sharing class SearchItemController {
public List<Item__c> ExistingItems{get;set;}
public String Keyword{get;set;}
public SearchItemController(){
ExistingItems = new List<Item__c>();
}
public void SearchItems(){
ExistingItems = [SELECT Id, Name, Item_Name__c, Unit_Price__c FROM Item__c WHERE Item_Name__c LIKE: ('%'+Keyword+'%')];
}
}
以下是一个使用前面控制器的 Visualforce 页面。Keyword 属性具有 <apex:inputText> 组件的默认获取和设置方法,用于获取用户的输入。ExistingItems 列属性也有默认的获取和设置方法,用于搜索和显示搜索结果。当用户输入搜索关键字并点击 搜索 按钮时,SearchItems() 动作方法将被执行,这将获取关键字搜索文本并运行查询以搜索项目。在动作方法执行之前,将执行关键字设置方法。然后查询结果将被收集到 ExistingItems 列属性中,然后执行 ExistingItems 获取方法,页面将显示搜索结果:
<apex:page controller="SearchItemController">
<apex:form >
<apex:pageBlock >
<apex:pageBlockSection >
<apex:pageBlockSectionItem >
<apex:outputLabel value="Item Name Or keyword"></apex:outputLabel>
<apex:inputText value="{!Keyword}"/>
</apex:pageBlockSectionItem>
<apex:commandButton value="Search" action="{!SearchItems}"/>
</apex:pageBlockSection>
</apex:pageBlock>
<apex:pageBlock title="Search Result" id="searchResult">
<apex:pageBlockTable value="{!ExistingItems}" var="oneItem" rendered="{!ExistingItems.size > 0}">
<apex:column value="{!oneItem.Item_Name__c}"/>
<apex:column value="{!oneItem.Unit_Price__c}"/>
</apex:pageBlockTable>
<apex:outputText value="No records to display" rendered="{!ExistingItems.size == 0}"></apex:outputText>
</apex:pageBlock>
</apex:form>
</apex:page>
动作方法
动作方法用于在自定义控制器或控制器扩展中实现我们的自定义或扩展逻辑和功能。动作方法可以在页面事件(如按钮点击或 JavaScript 事件)上触发。在 Visualforce 页面上,我们可以在许多标准 Visualforce 组件中定义 action 属性。这些组件是 <apex:commandButton>、<apex:commandLink>、<apex:actionPoller>、<apex:actionSupport>、<apex:actionFunction> 和 <apex:page>。前面的项目搜索示例有一个名为 SearchItems 的动作方法。SearchItems 用于根据用户输入的项目搜索条件查询项目。
在 Visualforce 页面上处理大量数据
在 Visualforce 页面上,我们不得不处理单个记录以及大量数据集。当我们处理大量数据集时,我们可以使用迭代组件,如 <apex:pageBlockTable>、<apex:repeat> 和 <apex:dataTable>。这些迭代组件在集合中的最大项目数限制为 1000 项。请参考迭代组件的使用示例。我们已经在之前的搜索项示例中使用了 <apex:pageBlockTable>。
提示
自定义控制器和控制器扩展遵循 Apex 管理员限制。
Visualforce 提供了“只读模式”功能,以克服单个请求中可查询的行数限制和页面中可迭代的集合项目数限制。设置 Visualforce 的只读模式功能有两种方法,如下所示:
-
为控制器方法设置只读模式:为此设置,我们可以使用
@ReadOnly注解定义 Visualforce 控制器方法。这种只读模式将单个查询中查询的记录数从 50,000 增加到 1,000,000 行。只读模式的@ReadOnly注解在 JavaScript 远程调用中用作目标,用于加载<apex:chart>组件的数据集并在组件中显示一些值。 -
为整个页面设置只读模式:通过为
<apex:page>上的readOnly属性添加true值,可以启用此只读模式。这种只读模式将单个查询中查询的记录数从 50,000 增加到 1,000,000。它还增加了迭代组件中集合项目的最大数量。由于这是一个只读模式,请注意,页面不能执行任何 DML 操作。
Visualforce 页面的执行顺序
Visualforce 页面有一个生命周期或生存期。这个时间定义为页面创建与用户会话期间销毁之间的时间段。生命周期由 Visualforce 页面请求的类型和页面内容定义。有两种类型的 Visualforce 页面请求,如下所示:
-
Get 请求
-
Postback 请求
Visualforce 页面的 get 请求执行顺序
当我们通过输入 URL 或点击按钮或链接来请求新页面时,会创建一个 get 请求。以下图表说明了 Visualforce 页面在 get 请求期间如何与自定义控制器或控制器扩展交互:

Visualforce 页面的 get 请求执行顺序
执行顺序如下:
-
构造方法是通过初始化控制器对象来调用的。
-
如果有任何自定义组件,它们将被创建,并且在其关联类上调用构造方法。如果组件中指定了任何使用表达式的属性,这些表达式也将被评估。
-
任何
assignTo属性和表达式都会被评估。之后,<apex:page>组件上的action属性会被评估,并调用所有获取器或设置器方法。 -
如果页面包含
<apex:form>标签,那么表示数据库状态的全部信息都会被加密并保存在页面请求之间的视图状态中。每当页面更新时,该视图状态也会相应更新。 -
最后,生成的 HTML 会被发送到浏览器。如果存在任何客户端技术(例如 JavaScript 和 CSS),浏览器将执行它们。
Visualforce 页面回发请求的执行顺序
一些用户交互(例如,由用户点击按钮触发的 save 操作)需要页面更新,通常这些页面更新是通过回发请求来执行的。以下图表展示了在回发请求期间,Visualforce 页面如何与自定义控制器或控制器扩展进行交互:

Visualforce 页面回发请求的执行顺序
执行顺序如下:
-
视图状态会被解码,并用作在回发请求期间更新页面值的依据。
-
表达式会被评估,并执行设置器。
-
执行操作。在成功完成后,数据会被更新。如果回发请求将用户重定向到同一页面,视图状态也会更新。
-
结果会被发送到浏览器。
提示
如果我们想在页面不执行输入验证或数据更改的情况下执行操作,可以为特定组件使用具有 true 值的立即属性。
回发请求可以以页面重定向结束,有时自定义控制器或控制器扩展可能会在原始页面和重定向页面上共享。如果回发请求包含 <apex:form> 组件,则只返回 ID 查询参数。
提示
<apex:page> 组件的 action 属性仅在 get 请求期间被评估。一旦用户被重定向到另一个页面,视图状态和控制器对象就会被删除。
验证规则和标准控制器/自定义控制器
可以将验证规则应用于自定义或标准对象,以在插入和更新操作中验证数据。当我们对 Visualforce 页面执行此类操作时,它使用标准控制器或自定义控制器,该记录可能引起验证规则错误,我们可以在 Visualforce 页面上显示这些错误,就像在标准页面上做的那样。验证规则有两个选项来选择显示特定字段错误的位置。如果我们选择页面顶部,错误可以通过在<apex:page>组件内的<apex:pageMessages>或<apex:messages>组件中显示。如果我们选择字段选项,错误将显示在位于<apex:inputField>组件旁边的相关字段中。例如,你可以在构建自定义控制器部分中查看提供的示例页面。
你可以通过为单价字段输入非数字字符来尝试这个示例。将显示一个错误消息,位于Unit_Price__c字段附近,与<apex:inputField>组件相关。
使用 transient 关键字
transient关键字用于声明变量,并在 Apex 类中使用。将变量声明为 transient 可以减少视图状态的大小。带有transient关键字的变量不能被保存,不应作为特定 Visualforce 页面的视图状态的一部分传输。transient 变量仅在页面请求期间需要。
transient关键字用于可序列化的 Apex 类,这意味着实现Batchable或Schedulable接口的类。以下 Apex 对象被原生认为是transient:
-
PageReference -
XmlStreamClasses -
Collections(只有当它们持有的对象类型自动标记为transient时) -
由系统方法如
Schema.getGlobalDescribe生成的多数对象 -
静态变量
-
JSONParser类的实例
以下示例包含一个 transient datatime变量和一个非 transient datatime变量。这个示例展示了 transient 变量的主要特性,即它们不能被保存,不应成为视图状态的一部分。当我们点击刷新按钮时,transient 日期将被重新创建,但非 transient 日期将保持其原始值:
<apex:page controller="TransientExampleController">
Non Transient Date: {!t1} <br/>
Transient Date : {!t2} <br/>
<apex:form >
<apex:commandLink value="Refresh"/>
</apex:form>
</apex:page>
public with sharing class TransientExampleController {
DateTime t1;
transient DateTime t2;
public String getT1() {
if (t1 == null) t1 = System.now();
return '' + t1;
}
public String getT2() {
if (t2 == null) t2 = System.now();
return '' + t2;
}
创建自定义控制器和控制器扩展时的注意事项
当你创建自定义控制器和控制器扩展时,请记住以下注意事项:
-
你需要牢记的是 Apex 限制器。
-
Apex 类可以通过使用
without sharing和with sharing分别以系统模式和用户模式运行。在不共享控制器的情况下可以暴露敏感数据。 -
必须将
webservice方法定义为全局的。所有其他方法都是公开的。 -
通过使用集合、映射或列表来尝试减少访问数据库的时间。这将提高你代码的效率。
-
Apex 方法和变量不是按照保证的顺序实例化的。
-
您不能在控制器构造方法中实现数据操作语言(DML)。
-
您不能为控制器中的任何 getter 方法、setter 方法或构造方法定义
@future注解。 -
原始数据类型(如 String、Integer 等)是通过值传递的,而非原始 Apex 数据类型(如 list、maps、set、sObject 等)是通过引用传递到组件控制器的。
摘要
在本章中,我们熟悉了控制器的类型和扩展。我们学习了标准控制器、标准列表控制器、自定义控制器和控制器扩展之间的差异及其用法。我们学习了如何处理代码以便与大量数据一起工作。此外,我们还看到了 Visualforce 页面的执行顺序、transient 关键字的使用以及验证规则和控制器的相互连接。
第三章. Visualforce 和标准 Web 开发技术
通过结合本地的 Visualforce 标记和标准 Web 开发技术,可以用于构建 Force.com 应用程序的丰富 UI。然而,Visualforce 的 HTML 渲染比较复杂,并且有多种方式可以通过使用额外的资源如 CSS 和 JavaScript 来改变 Visualforce 生成的默认 HTML。
本章讲解如何使用 CSS、JavaScript、jQuery 和 HTML5 等标准 Web 开发技术来开发 Visualforce 页面。本章还包括了 CSS、JavaScript 和 jQuery 静态资源的用法。以下主题将在本章中介绍:
-
样式化 Visualforce 页面
-
在 Visualforce 页面中使用 JavaScript
-
在 Visualforce 页面中使用 jQuery
-
HTML5 和 Visualforce 页面
让我们构建丰富的用户界面。
样式化 Visualforce 页面
我们使用 Visualforce 页面来完成简单的 UI 需求和复杂的 UI 需求。在实现复杂场景的情况下,仅使用 Salesforce 的标准样式无法满足这些需求。我们可以通过使用自己的样式表或样式来自定义 Visualforce 页面的外观和感觉。
许多标准 Visualforce 组件都有style和/或styleClass属性。我们可以使用这两个属性之一通过 CSS 来定制页面,这允许我们更改组件的默认样式(宽度、高度、颜色和字体)。Visualforce 中有两种样式类型,如下所示:
-
Salesforce 样式
-
自定义样式
Salesforce 样式
Salesforce 标准页面有可以在 Visualforce 页面中使用的标准样式。当我们使用标准 Visualforce 组件,如<apex:inputField>、<apex:pageBlock>、<apex:pageBlockTable>和<apex:detail>时,它们会获得 Salesforce 提供的默认样式。<apex:page>或<apex:apgeBlock>组件中的tabStyle属性可以指定特定对象标签的样式。这将改变前面组件的颜色方案。当我们使用标准控制器时,Visualforce 页面组件会继承相关对象的样式。当我们使用自定义控制器时,我们可以通过在<apex:page>标签上使用tabStyle属性来使用 Salesforce 任何标准标签的样式。
自定义样式
我们可以通过使用自定义样式来扩展 Salesforce 样式。可以通过使用style和/或styleClass属性将自定义样式添加到 Visualforce 页面中。style属性和styleClass属性在大多数 Visualforce 组件中都是可用的。style属性允许你添加内联自定义 CSS 语句。styleClass属性允许你通过在 CSS 文件中指定的类名来添加自定义样式。
在以下示例中,示例 Visualforce 页面通过style和styleClass属性添加了自定义样式:
<apex:page >
<style>
.sample {font-weight: bold;}
</style>
<apex:outputText value="This text is styled via style attribute" style="font-weight: bold;"/> <br/>
<apex:outputText value="This text is styled via styleClass attribute" styleClass="sample"/>
</apex:page>
在前面的示例中,我们在 Visualforce 页面上实现了 CSS。但是,如果我们要在多个位置使用相同的样式,我们必须将特定的 CSS 文件添加到每个 Visualforce 页面中。我们可以使用静态资源来解决这个问题。这是将自定义样式绑定到 Visualforce 标记的另一种方式。
静态资源通过 设置 屏幕上传到 Force.com 平台。Force.com 平台允许我们上传图像、样式表、JavaScript 和存档(.zip 和 .jar 文件)。以下是我们上面 Visualforce 页面使用的样式表(CSS 文件名:main.css):
.sample {font-weight: bold;}
让我们看看如何将这个样式表上传到静态资源中。资源名称是 CusomMainStyle,它必须是唯一的。可以通过以下路径创建资源:
您的姓名 | 设置 | 开发 | 静态资源 | 新建

使用静态资源存储 CSS 文件
我们可以在 Visualforce 页面中如下引用 CustomMainStyle 静态资源。可以使用 <apex:stylesheet> 标签包含样式表。资源名称用于在 Visualforce 页面中引用静态资源,如下面的代码所示:
<apex:page>
<apex:stylesheet value="{!$Resource.CustomMainStyle}"/>
<apex:outputText value="This text is styled via styleClass attribute via static resources" styleClass="sample"/>
</apex:page>
小贴士
如果您想完全删除 Salesforce 标准样式,您必须将 <apex:page> 标签上的 standardStylesheets、sidebar 和 showHeader 属性设置为 false 值。如果您停止加载标准 Salesforce 样式表,您可以减小 Visualforce 页面的尺寸:
<apex:page sidebar="false" showHeader="false" standardStylesheets="false">
</apex:page>
我们之前提到,Force.com 平台允许我们将存档文件(如 ZIP 和 JAR 文件)作为静态资源上传。在这种情况下,ZIP 文件可以包含图像、CSS 文件和 JavaScript 文件等资源。在这种情况下,我们可以通过使用 URLFOR 函数引用 ZIP 文件内的单个资源。URLFOR 函数有两个参数。第一个参数是静态资源的名称,我们在上传静态资源时提供。第二个参数是 ZIP 文件中特定文件的路径。以下示例中使用的静态资源是一个包含 main.css 样式表的目录 CustomStyleZipFolder 的 ZIP 文件。

将 zip 文件作为静态资源上传
<apex:page >
<apex:stylesheet value="{!URLFOR($Resource.CustomStyleZip,'/CustomStyleZipFolder/main.css')}"/>
<apex:outputText value="This text is styled via styleClass attribute via static resources" styleClass="sample"/> <br/>
<apex:outputText value="Following image is loaded via css class"/> <br/>
<apex:outputPanel styleClass="imageCls">
</apex:outputPanel>
<apex:outputText value="Following image is loaded directly from static resource"/> <br/>
<apex:image value="{!URLFOR($Resource.CustomStyleZip,'/CustomStyleZipFolder/images/sfLogo.png')}" width="100" height="50"/>
</apex:page>
对于静态资源,存在一个特殊场景,您可以使用文件的相对路径。这允许我们以相对方式引用存档内的其他内容。例如,Main.css 文件具有以下样式:
.sample {font-weight: bold;}
.imageCls {background:url(images/sfLogo.jpg) no-repeat top left;
width: 100px;
height: 100px;
display: block;
}
在前面的样式代码中,图像的路径需要相对于 Main.css 文件指定。在这种情况下,我们准备了 CustomStyleZipFolder 目录,其中包含 Main.css 文件和 images 文件夹。在这里,Images 中存在的 sfLogo.jpg 图像在 Main.css 中被引用。
然后,我们只需将 Main.css 文件包含到 Visualforce 页面中。我们不需要担心样式表中的相对路径,因为静态资源包含样式表和图像。
小贴士
单个静态资源的最大大小为 5 MB。组织中静态资源的最大大小为 250 MB。
在 Visualforce 页面中使用 JavaScript
JavaScript 是 Visualforce 页面的关键浏览器技术之一。JavaScript 为在 JavaScript 对象、HTML 元素和 Visualforce 控制器之间进行通信提供了框架。我们可以在 Visualforce 页面中使用 JavaScript 库以及一些 Visualforce 组件(如 <apex:actionFunction>、<apex:actionSupport>、<apex:commandButton>、<apex:commandLink>)等。JavaScript 代码可以在 Visualforce 页面中编写,并且可以通过使用静态资源将其包含在 Visualforce 页面中。这是在 Visualforce 页面中包含 JavaScript 库的最佳方法。我们可以使用 <apex:includeScript> 组件从静态资源中包含 JavaScript 库。例如:
<apex:includeScript value="{!$Resource.MyJSFile}"/>
在 JavaScript 中访问 Visualforce 组件
当我们在 JavaScript 代码中引用 Visualforce 组件时,ID 属性就派上用场。每个 Visualforce 组件都有一个 ID 属性。要使用 JavaScript 引用特定组件,必须指定该组件的 ID 属性,并且它用于将两个组件绑定在一起。当页面渲染时,此 ID 属性是特定组件 DOM ID 的一部分。ID 属性也必须是唯一的。以下代码片段展示了使用 id 属性绑定两个组件的方法:
<apex:outputLable value="Label Name" for="item"/>
<apex:inputField id="item" value="{!item__c.Name}">
以下示例提供了一个在 Visualforce 页面中处理 JavaScript 的方法思路。此页面已实现通过 JavaScript 修改复选框来更改选择列表的值。JavaScript 代码包含在 <script> 标签内。JavaScript 函数有两个参数。第一个参数是触发事件的元素(input),第二个参数是目标选择列表字段的 DOM ID (id)。{!$Component.inputStatus} 表达式获取由 <apex:inputField id="inputStatus" value="{!order.Status__c}"/> 组件生成的 HTML 元素的 DOM ID:
<apex:page controller="OrderStatusUpdate" id="pageId">
<script type="text/javascript">
function updateStatus(input,id) {
if(input.checked){
document.getElementById(id).value="Processing";
//alert(document.getElementById(id).value);
}else{
document.getElementById(id).value="New";
//alert(document.getElementById(id).value);
}
}
</script>
<apex:form id="formId">
<apex:pageBlock id="pageBId">
<apex:pageBlockTable id="tableId" value="{!Orders}" var="order">
<apex:column value="{!order.Name}"/>
<apex:column value="{!order.Customer__c}"/>
<apex:column id="checkId" headerValue="Status">
<apex:inputField id="inputStatus" value="{!order.Status__c}" />
</apex:column>
<apex:column headerValue="Started Processing" >
<apex:selectCheckboxes onclick="updateStatus(this,'{!$Component.inputStatus}');" >
</apex:selectCheckboxes>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
以下代码是前面 Visualforce 页面相关控制器类的代码。它按照以下方式检索现有订单:
public with sharing class OrderStatusUpdate {
public List<Order__c> Orders{get;set;}
public OrderStatusUpdate(){
Orders = new List<Order__c>();
Orders = [SELECT id, Name, Customer__c, Status__c, Planned_Delivery_Date__c, Delivered__c FROM Order__c LIMIT 1000];
}
}
小贴士
下载示例代码
您可以从您在 www.packtpub.com 的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了此书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
Apex 控制器的 JavaScript 远程调用
JavaScript 远程调用是提供对 APEX 控制器中某些方法支持的过程,这些方法将通过 JavaScript 调用。此功能使我们能够实现使用标准 Visualforce Ajax 组件无法完成的复杂和动态行为。JavaScript 远程调用在 Spring '11 作为开发者预览版发布。自 Summer '11 版本发布以来,JavaScript 远程调用提供了对更多返回数据类型的支持。此外,响应中不再重复引用相同的对象。JavaScript 远程调用有三个主要部分:
-
用于调用远程方法的 JavaScript 代码
-
Apex 控制器中的远程方法
-
Visualforce 页面中的回调函数(用 JavaScript 编写)
要使用 JavaScript 远程调用,您的请求必须采用以下形式:
[<namespace>.]<controller>.<method>([params...,] <callbackFunction>(result, event)
{
// callback function logic
}, {escape:true});
以下代码的描述如下:
-
namespace:这是您的组织命名空间。如果类来自已安装的包,则此为必需项。 -
controller:这是您的 Apex 控制器的名称。 -
method:这是您正在调用的 Apex 方法的名称。 -
params:这是您方法接受的参数的逗号分隔列表。 -
callbackFunction:这是处理控制器响应的函数的名称。它返回调用的状态和方法结果。 -
escape:这指定了您的响应是否应该被转义(默认为true)或不应转义(false)。
远程方法必须以 @RemoteAction 注解开始,如下所示:
@RemoteAction
global static String getItemId(String objectName) { ... }
远程方法可以作为参数具有以下数据类型:
-
Apex 原始数据类型(String、Integer 等)
-
集合(Set、List、Map)
-
sObject(标准对象和自定义对象)
-
用户定义的 Apex 类和接口
远程方法可以返回以下数据类型:
-
Apex 原始数据类型(String、Integer 等)
-
sObjects(标准对象和自定义对象)
-
集合(Set、List、Map)
-
用户定义的 Apex 类和枚举
-
SelectOption -
PageReference -
SaveResult -
UpsertResult -
DeleteResult
提示
远程方法必须通过名称和参数数量唯一标识。例如,我们不能编写具有相同方法名称、相等参数数量但不同参数类型的远程方法。
以下示例展示了如何在 Visualforce 页面和 Apex 控制器中使用 JavaScript 远程调用:
<apex:page controller="JavaScriptRemotingController" id="pageId">
<script type="text/javascript">
function updateStatus(input,id) {
var inputStatus=id;
JavaScriptRemotingController.doStartShipping(inputStatus,function(result,event){
},{escape:true});
}</script>
<apex:form id="formId">
<apex:pageBlock id="pageBId">
<apex:pageBlockTable id="tableId" value="{!Orders}" var="order">
<apex:column value="{!order.Name}"/>
<apex:column value="{!order.Customer__c}"/>
<apex:column id="checkId" headerValue="Status">
<apex:inputField id="inputStatus" value="{!order.Status__c}" />
</apex:column>
<apex:column headerValue="Started Processing" >
<apex:selectCheckboxes onclick="updateStatus(this,'{!order.Id}');">
</apex:selectCheckboxes>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
这是相关的 Apex 控制器:
global with sharing class JavaScriptRemotingController {
public List<Order__c> Orders{
get{
Orders = new List<Order__c>();
Orders = [SELECT id, Name, Customer__c, Status__c, Planned_Delivery_Date__c, Delivered__c FROM Order__c LIMIT 1000];
return Orders;
}
set;
}
public JavaScriptRemotingController(){
}
@RemoteAction
global static Item__c doStartShipping(String para){
Order__c updateOrder;
try{
updateOrder=[SELECT id, Name, Customer__c, Status__c, Planned_Delivery_Date__c, Delivered__c FROM Order__c Where Id =: para];
updateOrder.Status__c = 'Shipping';
update updateOrder;
}catch(DMLException e){
ApexPages.addMessages(e);
return null;
}
return null;
}
}
在 Visualforce 页面中使用 jQuery
jQuery 是一个开源的 JavaScript 库,它允许我们实现 HTML 的客户端脚本。jQuery 被设计成能够通过引入新的插件来扩展主要库,从而引入各种新功能。它还允许我们导航文档、选择 DOM 元素、创建动画、事件处理以及开发 Ajax 应用程序。
当我们开发 Visualforce 页面时,jQuery 可以用来简化 UI 开发。例如,jQuery 用于简化 DOM 操作并访问 UI 元素库,以及简化移动设备的 Ajax 技术和技术。
以下示例展示了我们之前示例的 jQuery 版本。这用于解释 Visualforce 中的 JavaScript 代码。本例使用 Order__c 标准控制器。此页面需要带有订单 ID 的 ID 参数:
c.ap1.visual.force.com/apex/JQueryExample?id=a02900000086Hlr
此页面渲染订单详情页面。我们使用 jQuery 来满足我们的需求。因此,Visualforce 页面需要包含 jQuery 库以实现 jQuery 实现。在以下示例中,我们使用了主 jQuery 库的在线参考:
<apex:includeScript value="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" />
您还可以使用静态资源来包含 jQuery 库。用法与 JavaScript 和 CSS 示例相同。
提示
还有其他具有相同默认全局变量名($)的 JavaScript 库。如果我们也使用相同的全局变量名,客户端将会有冲突。我们的 jQuery 函数将无法工作。为了消除这种冲突,我们可以使用 jQuery.noConflict() 并将其分配给另一个全局变量,然后在我们的 jQuery 代码中使用这个新全局变量。
<apex:page StandardController="Order__c" id="pageId">
<apex:includeScript value="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" />
<script type="text/javascript">
j$ = jQuery.noConflict();
j$(document).ready(function() {
j$('.checkBox').click(function () {
j$('.inputStatus').val('Processing');
});
});
</script>
<apex:form id="formId">
<apex:pageBlock id="pageBId">
<apex:pageBlockSection id="pBlockSection">
<apex:outputField value="{!Order__c.Name}"/>
<apex:outputField value="{!Order__c.Customer__c}"/>
<apex:inputField styleClass="inputStatus" value="{!Order__c.Status__c}" />
<apex:pageBlockSectionItem id="pbSectionItem">
<apex:outputLabel value="Mark as Started Processing"></apex:outputLabel>
<apex:selectCheckboxes styleClass="checkBox" >
</apex:selectCheckboxes>
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
提示
如果我们没有为特定组件使用 id 属性,Visualforce 将使用动态生成的 id,例如,j_id0, j_id0:j_id1。考虑一个例子,我们已指定 <apex:inputField id="inputOne"/> 的 id 属性。但我们没有为 inputOne 的父组件指定任何 id 属性。我们可以使用 jQuery 选择这样的组件。这被称为部分选择器。例如:j$('[id*= inputOne]')
HTML5 和 Visualforce 页面
HTML5 是 HTML 的新标准。HTML 的前一个版本是 HTML 4.01。HTML5 具有新功能,如新元素、新属性、视频和音频支持、2D/3D 图形支持、完整的 CSS3 支持、本地存储、本地 SQL 数据库支持,以及具有网页应用功能。有了这些功能,我们可以减少对外部插件的依赖。还有更多标记来替代脚本。HTML5 具有更好的错误处理机制。
当涉及到 Force.com 开发时,我们可以使用 HTML5 进行 Visualforce 页面开发,并开发移动网页应用。在 Force.com 平台上,HTML5 在开发基于网页的移动应用中起着重要作用。例如,最近 Salesforce 发布了 touch.salesforce.com,它使用了 HTML5。
默认情况下,Visualforce 页面使用 HTML 4.01 转换的 docType。自 Winter '12 版本以来,Visualforce 页面支持在 <apex:page> 标签中更改 docType 属性。在一个纯 HTML5 页面中,必须在页面顶部指定 <!DOCTYPE html> 标签。<apex:page> 的 docType 属性实现了这一要求。
以下是在<apex:page>组件上docType Visualforce 属性的示例用法:
<apex:page docType="html-5.0"><!-- HTML5 --></apex:page>
<apex:page docType="html-4.0.1-transitional"><!-- HTML 4.0.1 Transitional --></apex:page>
<apex:page docType="xhtml-5.0.1-strict"><!-- XHTML 5.0.1 Strict--></apex:page>
以下示例是一个使用 HTML5 实现拖放功能的 Visualforce 页面。这里有一个矩形和一个引用自静态资源的图片。我们可以将图片拖入矩形中:
<apex:page docType="html-5.0" sidebar="false" showHeader="false" standardStylesheets="false" cache="true" >
<html>
<head>
<style type="text/css">
#div1 {width:400px;height:400px;padding:10px;border:1px solid #aaaaaa;}
</style>
<script>
function allowDrop(ev)
{
ev.preventDefault();
}
function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}
function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
</script>
</head>
<body>
<p>Drag the Salesforce logo into the rectangle:</p>
<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<br/>
<img id="drag1" src="img/sfLogo.png')}" draggable="true" ondragstart="drag(event)" width="400" height="400"/>
</body>
</html>
</apex:page>
摘要
在本章中,我们熟悉了本地 Visualforce 标记和标准 Web 开发技术的结合使用。我们看到了如何通过使用 CSS、JavaScript、jQuery 和 HTML5 在 Force.com 平台上构建丰富的 UI。我们还学习了 CSS、JavaScript 和 jQuery 静态资源的用法。
第四章:Visualforce 自定义组件
Salesforce 拥有一系列标准 Visualforce 组件,如 <apex:detail>、<apex:pageBlock>、<apex:pageBlockTable> 和 <apex:relatedList>。它们已准备好在 Visualforce 页面中使用,并且 Force.com 平台允许我们构建自己的 Visualforce 组件,这些组件可以在 Visualforce 页面中使用。
本章作为 Visualforce 自定义组件的概述,并进一步解释了如何创建自定义组件。本章涵盖了以下主题:
-
理解 Visualforce 自定义组件
-
如何在 Visualforce 页面中创建和使用自定义组件
-
自定义属性和自定义控制器
让我们构建自己的 Visualforce 自定义组件。
理解 Visualforce 自定义组件
有许多标准 Visualforce 组件(如 <apex:detail>、<apex:pageBlock>、<apex:pageBlockTable> 和 <apex:relatedList>),这些组件可以在 Visualforce 页面中重用。标准 Visualforce 组件是一个预构建的、封装的代码段。这些标准 Visualforce 组件是根据常见用法构建的。
Force.com 平台允许我们开发可在特定应用程序中重用的自定义 Visualforce 组件。自定义组件可以使用 Apex 和 Visualforce 进行开发。例如,假设我们想要创建一个包含最近订单的客户摘要,并且需要在我们的订单处理应用程序的不同位置使用此功能。我们还需要指定最近订单的数量。根据指定的数量,客户摘要中显示的最近订单数量将会改变。使用 Visualforce 自定义组件是实现此类特定要求的最优选择。
Visualforce 自定义组件可以具有零个或多个属性作为参数传递到组件中。具有属性的自定义组件类似于参数化的 Apex 方法。我们可以在最终使用级别(在 Visualforce 页面中)更改属性的值。
创建和使用自定义组件
我们可以创建一个 Visualforce 组件用于在 Visualforce 页面中使用。导航到以下路径以创建一个新的 Visualforce 组件:
您的姓名 | 设置 | 开发 | 组件 | 新建

创建新的 Visualforce 组件
在创建自定义组件时,我们需要指定以下属性:
-
标签:此自定义组件将通过标签在设置工具中识别。
-
名称:此自定义组件将通过
name在 Visualforce 标记中识别。必须在组织内是唯一的。 -
描述:这给出了自定义组件的描述。
-
主体:Visualforce 标记必须放置在主体部分。
![创建和使用自定义组件]()
注意
自定义组件的名称应该以字母开头,并且不应该以下划线结尾。此外,名称中不应包含空格或连续的两个下划线。
自定义组件可以包含的最大数据量是 1 MB,或大约 1,000,000 个字符。
我们可以通过使用版本设置来指定与特定组件一起使用的 Visualforce 版本和 API。
自定义组件的主体可以定义如下:
<apex:component
<!—Desire markup here-->
</apex:component>
提示
组件标记与其他 Visualforce 页面相同。它可以是由 Visualforce 和 HTML 标签组合而成的。我们还可以添加自定义 CSS 和 JavaScript。
所有标记都应该定义在<apex:component>标签内。我们的自定义组件示例是一个包含最近订单的客户摘要。假设我们的自定义组件名称是customerSummary,我们可以在多个 Visualforce 页面上使用此组件。使用方法如下:
<apex:page>
<c: customerSummary />
</apex:page>
自定义属性和自定义控制器
当我们创建复杂的自定义组件时,我们需要使用一些其他功能来构建自定义组件。主要的是,我们必须为自定义组件使用自定义属性和自定义控制器。属性可以在<apex:component>中定义,用于从 Visualforce 页面(使用组件的页面)传递值到自定义组件或组件的控制器。
我们已经实现了从本章开头解释的示例。以下是对组件标记的描述,它包含了属性和组件的定义。我们有两个要传递的属性,即客户 ID 和我们要在页面上显示的最近订单数量。这两个参数用于将值传递到组件的控制器:
<apex:component controller="CustomerSummaryComponenetController">
<!-- Attribute Definitions -->
<apex:attribute name="customerId" Type="String" required="true" description="customer id" assignTo="{!CusID}"/>
<apex:attribute name="noOfRecentOrders" Type="Integer" required="true" description="Number of recent orders" assignTo="{!RecentNo}"/>
<!-- Attribute Definitions : End -->
<!-- Component Definition -->
<apex:componentBody >
<apex:pageBlock >
<apex:pageBlockSection title="Customer Details">
<apex:outputField value="{!CurrentCustomer.Name}"/>
<apex:outputField value="{!CurrentCustomer.Address__c}"/>
<apex:outputField value="{!CurrentCustomer.Email__c}"/>
</apex:pageBlockSection>
<apex:pageBlockSection title="Recent Order Details">
<apex:pageBlockTable value="{!RecentOrderList}" var="order">
<apex:column value="{!order.Name}"/>
<apex:column value="{!order.Planned_Delivery_Date__c}"/>
<apex:column value="{!order.Status__c}"/>
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:componentBody>
<!-- Component Definition : End -->
</apex:component>
以下代码片段显示了与cutomerSummary自定义组件关联的自定义控制器。此控制器用于操作属性值。在这个例子中,我们查询了特定客户的客户记录和最近订单详情。CurrentCustomer和RecentOrderList的查询结果取决于CusID和RecentNo的值:
public class CustomerSummaryComponenetController{
public String CusID{get;set;}
public Integer RecentNo{get;set;}
public Customer__c CurrentCustomer{
get{
CurrentCustomer = new Customer__c();
CurrentCustomer = [SELECT Id, Name, Address__c, Email__c, Telephone__c FROM Customer__c WHERE Id =: CusID];
return CurrentCustomer;
}
set;
}
public List<Order__c> RecentOrderList{
get{
RecentOrderList = new List<Order__c>();
RecentOrderList = [SELECT Id, Name, Customer__c, Delivered__c, Planned_Delivery_Date__c, Status__c FROM Order__c WHERE Customer__c =: CusID ORDER BY CreatedDate DESC LIMIT :RecentNo];
return RecentOrderList;
}
set;
}
}
这就是使用我们的自定义组件的方法。在这里,我们传递了我们要看到的客户 ID 和最近订单数量的值:
<apex:page StandardController="Customer__c">
<c:CustomerSummary customerId="{!Customer__c.Id}" noOfRecentOrders="3"></c:CustomerSummary>
</apex:page>
以下截图显示了customerSummary自定义组件的结果:

摘要
在本章中,我们概述了 Visualforce 自定义组件。现在我们有了创建和使用 Visualforce 自定义组件的知识。通过使用 Visualforce 自定义组件,我们学习了在 Visualforce 中重用代码的机制。我们看到了如何通过使用自定义属性和自定义控制器来构建更可定制的自定义组件。
第五章. 动态 Visualforce 绑定
动态 Visualforce 绑定是 Spring '11 版本中最大的特性之一。我们可以使用这个特性来构建通用的 Visualforce 页面,而无需考虑页面上必须显示哪些记录字段。记录字段是在运行时而不是编译时确定的。这是一个强大的特性,它允许我们最小化代码(Visualforce 和 Apex 代码)。否则,我们必须编写更多的查询,填充记录列表,并渲染更多的字段。使用动态 Visualforce 绑定,我们可以开发一个页面,根据不同用户的授权或偏好以不同的方式渲染。
本章涵盖以下主题:
-
使用标准对象和自定义对象的动态引用
-
引用 Apex Maps 和 Lists
-
与字段集一起工作
-
动态引用全局变量
让我们了解动态 Visualforce 绑定...
使用标准对象和自定义对象的动态引用
Salesforce 支持标准对象和自定义对象的动态 Visualforce 绑定。我们可以使用以下形式的动态绑定:
reference[expression]
让我们详细讨论前面的形式:
-
reference:这可以是一个 sObject、Apex 类或全局变量。 -
expression:这可以是字段名称或相关对象。如果是相关对象,则可以使用递归选择的字段或进一步相关对象。
小贴士
动态绑定可以在公式表达式有效的页面上使用。它使用{!}符号。如果从 Apex 类中引用,则特定的属性(sObject 或变量)必须是公共的或全局的。
注意
定义关系:如果在表达式中需要评估对象关系,它们会变得复杂。考虑我们的例子,其中Order__c自定义对象与Customer__c自定义对象有关联。这两个对象之间的关系称为Orders__r。Customer__c对象具有Email__c字段。以下动态类型转换的查找将返回相同的Email__c字段:
-
Order__c.Customer__c['Email__c'] -
Order__c['Customer__c.Email__c'] -
Order__c['Customer__c']['Email__c'] -
Order__c.Orders__r[Email__c] -
Order__c[Orders__r.Email__c] -
Order__c[Orders__r][Email__c]
动态 Visualforce 页面必须有一个标准控制器,并且进一步的实现可以在关联的控制器扩展中完成。原因是 Visualforce 自动处理页面StandardController或StandardSetController对象执行的 SOQL 查询的优化,只加载使用的字段。
当我们创建具有静态引用的页面时,页面可以在编译期间识别字段和对象。然后StandardController对象将特定的字段和对象转换为 SOQL 查询。但是,动态引用是在运行时而不是在编译时评估的。这意味着动态引用是在执行StandardController对象的 SOQL 查询之后评估的。因此,当我们使用动态引用并需要向控制器扩展提供一些额外信息时,我们可以使用addFields()方法添加任意数量的额外字段。此方法将传递一个额外字段列表给StandardController,这些字段将加载而不会产生运行时错误。addField()方法的用法如下:
public DynamicOrderExtension(ApexPages.StandardController controller) {
controller.addFields(editableFields);
}
以下示例展示了动态 Visualforce 绑定的用法。此页面显示了一个带有一些可编辑字段的订单记录。一些字段与object(Customer__c)相关。我们可以通过对象关系遍历来理解动态引用的用法。
<apex:page standardController="Order__c" extensions="DynamicOrderExtension">
<apex:pageMessages /><br/>
<apex:form >
<apex:pageBlock title="Edit Order" mode="edit">
<apex:pageBlockSection columns="1">
<apex:inputField value="{!Order__c.Name}"/>
<apex:repeat value="{!editableFields}" var="f">
<apex:inputField value="{!Order__c[f]}"/>
</apex:repeat>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
以下代码是前面 Visualforce 页面的控制器扩展。DynamicOrderExtension控制器扩展有一个名为editableFields的字符串列表,这个字符串列表包含Order__c对象的一些字段名以及Order__c的相关对象(Customer__c)的一些字段。在这个例子中,可编辑字段是硬编码的。但我们可以通过使用 Apex 的Schema.sObjectType方法来获取动态引用的信息。这将使引用更加动态和强大。例如,Schema.SobjectType.Order__c.fields.getMap()返回一个包含Order__c字段名的映射。前面的标记中包含<apex:repeat>标签,用于循环editableFields字符串列表,以及<apex:inputField>标签,用于显示返回的特定字符串。它表示订单字段和相关对象字段的名字。下面的标记行显示了动态引用:
<apex:inputField value="{!Order__c[f]}"/>
public with sharing class DynamicOrderExtension {
public final Order__c orderDetails { get; private set; }
public DynamicOrderExtension(ApexPages.StandardController controller) {
String qid = ApexPages.currentPage().getParameters().get('id');
String theQuery = 'SELECT Id, ' + joinList(editableFields, ', ') +
' FROM Order__c WHERE Id = :qid';
this.orderDetails = Database.query(theQuery);
controller.addFields(editableFields);
}
public List<String> editableFields {
get {
if (editableFields == null) {
editableFields = new List<String>();
editableFields.add('Delivered__c');
editableFields.add('Customer__c');
editableFields.add('Planned_Delivery_Date__c');
editableFields.add('Status__c');
editableFields.add('Customer__r.Email__c');
}
return editableFields ;
}
private set;
}
private static String joinList(List<String> theList, String separator) {
if (theList == null) {
return null;
}
if (separator == null) {
separator = '';
}
String joined = '';
Boolean firstItem = true;
for (String item : theList) {
if(null != item) {
if(firstItem){
firstItem = false;
}
else {
joined += separator;
}
joined += item;
}
}
return joined;
}
}
此页面需要通过指定为 id 查询参数的有效案例记录 ID 来访问。例如,c.ap1.visual.force.com/apex/DynamicBindingExample?id=a02900000086Hlr。
引用 Apex Maps 和 Lists
Apex Maps 和 Lists 可以在 Visualforce 页面中动态引用。Apex Lists 与<apex:pageBlockTable>和<apex:repeat>标签广泛使用。在我们的前面例子(在使用标准对象和自定义对象的动态引用部分)中,我们已经看到了 Apex List 的动态引用。以下示例展示了 Apex Map 的动态引用。这是DynamicExampleListMap页面的标记:
<apex:page controller="DynamicBindingsMapListExample">
<apex:form >
<apex:actionFunction name="reDisplayCustomers" rerender="cust" />
<apex:pageBlock title="Criteria">
<apex:outputLabel value="Starting Letter"/>
<apex:selectList value="{!selectedKey}" size="1" onchange="reDisplayCustomers()">
<apex:selectOptions value="{!keys}" />
</apex:selectList>
</apex:pageBlock>
<apex:pageBlock title="Customers">
<apex:outputPanel id="cust">
<apex:pageBlockTable value="{!customerMap[selectedKey]}" var="cus">
<apex:column value="{!cus.name}"/>
<apex:column value="{!cus.Address__c}"/>
<apex:column value="{!cus.Email__c}"/>
</apex:pageBlockTable>
</apex:outputPanel>
</apex:pageBlock>
</apex:form>
</apex:page>
以下是与之相关的自定义控制器。customerMap对象包含客户记录,下拉列表会根据 Map 中的适当值动态填充。我们可以从下拉列表和客户列表中选择一个字母,然后根据所选字母重新排列结果。customerMap对象通过使用{!customerMap[selectedKey]}动态引用在运行时返回相应的客户列表:
public class DynamicBindingsMapListExample
{
public Map<string, List<Customer__c>> customerMap{get; set;}
public List<selectoption> keys {get; set;}
public String selectedKey {get;set;}
public Map<string, Customer__c> custByName {get;set;}
public Set<string> getMapKeys()
{
return customerMap.keySet();
}
public DynamicBindingsMapListExample()
{
custByName = new Map<string, Customer__c>();
List<string> sortedKeys=new List<string>();
customerMap = new Map<string, list<Customer__c>>();
customerMap.put('All', new List<Customer__c>());
List<Customer__c> customers = [SELECT Id, Name, Email__c, Address__c FROM Customer__c ORDER BY Name asc];
for (Customer__c tempCustomer : customers)
{
customerMap.get('All').add(tempCustomer);
String start = tempCustomer.Name.substring(0,1);
List<Customer__c> custFromMap = customerMap.get(start);
if (custFromMap == null)
{
custFromMap =new List<Customer__c>();
customerMap.put(start, custFromMap);
}
custFromMap.add(tempCustomer);
custByName.put(tempCustomer.name, tempCustomer);
}
keys=new List<selectoption>();
for (String key : customerMap.keySet())
{
if (key != 'All')
{
sortedKeys.add(key);
}
}
sortedKeys.sort();
sortedKeys.add(0, 'All');
for (String key : sortedKeys) {
keys.add(new SelectOption(key, key));
}
selectedKey='All';
}
}
与字段集一起工作
字段集是一组可以以声明方式定义的字段。字段集在 API 版本 21.0 的 Visualforce 页面上可用。这些字段集可以通过动态绑定在 Visualforce 页面上显示。例如,假设我们创建了一个字段集(字段集名称:CustomerDetails),其中包含客户对象的Email__c、Name和Address__c字段。我们可以在 Visualforce 中如下引用CustomerDetails字段集:
<apex:page standardController="Customer__c">
<apex:repeat value="{!$ObjectType.Customer__c.FieldSets.CustomerDetails}" var="f">
<apex:outputText value="{! Customer__c [f]}" /><br/>
</apex:repeat>
</apex:page>
当我们想要创建一个管理包或在字段集中添加/删除/重新排序字段时,我们可以不修改任何代码就完成这些操作。
小贴士
一个 Visualforce 页面最多可以有 50 个字段集。
摘要
在本章中,我们了解了 Spring'11 发布的一项强大功能——动态绑定。我们熟悉了标准对象和自定义对象动态引用的用法。我们还对引用 Apex Maps/List 以及使用字段集的方式有了很好的了解。我们还看到了全局变量动态引用的用法。通过所有这些,我们学习了最小化 Visualforce 和 Apex 代码的机制。
第六章. Visualforce 图表
Visualforce 图表是 Winter '13 版本中最好的特性之一。它是一组组件,提供了一种简单的方法来在我们的 Visualforce 页面上创建图表和 Visualforce 自定义组件。这个特性使我们能够根据我们的数据集(来自 SOQL 查询)自定义图表,并在我们的 Visualforce 页面上创建自定义图表(如饼图、柱状图和折线图)。我们可以使用 Visualforce 和 Apex 创建图表,图表组件会为我们处理所有的 JavaScript 代码。Visualforce 图表通过客户端的 JavaScript 渲染,这使我们能够在 Visualforce 页面上构建动画和视觉上令人兴奋的图表。
有时,标准的 Salesforce 图表和仪表板可能不足以满足我们的需求。这就是 Visualforce 图表发挥作用的地方。当我们无法使用 Visualforce 图表满足我们的需求时,我们可以在 Visualforce 中使用 Google 图表。
本章解释了 Visualforce 图表,它是一组组件,提供了一种简单直观的方法来在您的 Visualforce 页面上和自定义组件中创建图表。本章将涵盖以下主题:
-
Visualforce 图表的限制和注意事项
-
Visualforce 图表是如何工作的
-
使用 Visualforce 图表的复杂图表
让我们构建一些令人兴奋的 Visualforce 图表…
Visualforce 图表的限制和注意事项
当 Force.com 发布 Visualforce 图表功能时,他们宣布了一些已知的使用 Visualforce 图表的限制和注意事项,如下所示:
-
Visualforce 图表只能在支持可伸缩矢量图形(SVG)的浏览器中渲染。
-
Visualforce 图表无法在渲染为 PDF 的页面上显示,因为 Visualforce 图表使用 JavaScript 来绘制图表。
-
Visualforce 图表不用于电子邮件消息或电子邮件模板,因为电子邮件客户端不支持在电子邮件消息中执行 JavaScript。
-
当我们使用 Visualforce 图表开发 Visualforce 页面时,我们需要使用 JavaScript 调试工具,如 Firebug,来跟踪从 Visualforce 图表返回到 JavaScript 控制台的错误和消息。
-
动态 Visualforce 图表(由 Apex 生成)截至 2013 年春季仍未被 Force.com 平台支持。然而,这个特性预计很快就会发布。
Visualforce 图表是如何工作的
Visualforce 图表依赖于 Apex、Visualforce 和 JavaScript。当我们创建 Visualforce 图表时,我们需要一个 Apex 方法来准备或查询作为图表数据源的数据。然后我们需要使用 Visualforce 图表组件来定义我们的图表。在 Apex 方法中准备好的图表数据绑定到图表组件上,JavaScript 在浏览器中绘制图表。
Visualforce 图表需要一个至少包含一个数据系列组件的图表容器。我们有能力添加额外的系列、图表轴和标签组件(如图例、图表标签和数据点的工具提示)。
以下示例创建了一个简单的饼图,其中包含发送给客户的物品数量。在这个例子中,我们为图表数据源硬编码了值。以下是一个饼图示例的标记:
<apex:page controller="VFChartController" title="Pie Chart">
<apex:chart height="350" width="450" data="{!chartData}">
<apex:pieSeries dataField="data" labelField="name"/>
<apex:legend position="right"/>
</apex:chart>
</apex:page>
这里是相关的自定义控制器,它为图表准备数据源。图表容器由<apex:chart>组件定义,数据绑定通过getChartData()控制器方法完成。<apex:pieSeries>组件根据返回的数据定义标签和数据字段,如下所示:
public class VFChartController {
public List<PieChartData> getChartData () {
List<PieChartData> data = new List<PieChartData>();
data.add(new PieChartData('RAM', 30));
data.add(new PieChartData('Hard Disk', 15));
data.add(new PieChartData('VGA Card', 10));
data.add(new PieChartData('Mouse', 20));
data.add(new PieChartData('USB Drive 16BG', 20));
data.add(new PieChartData('USB Drive 32BG', 5));
return data;
}
// Wrapper class
public class PieChartData {
public String name { get; set; }
public Integer data { get; set; }
public PieChartData(String name, Integer data) {
this.name = name;
this.data = data;
}
}
}
前一个示例的结果图表如下截图所示:

结果饼图
前一个示例说明了以下要点:
-
PieChartData: 这是一个内部类,它包含一组属性来定义图表的标签(name属性)和数据(data属性)。 -
getChartData(): 此方法返回一个PieChartData包装对象的列表。这些列表元素创建了图表的数据点。 -
<apex:pieSeries>: 此组件定义了从返回的数据(PieChartData对象)中获取的标签和数据字段。
提供图表数据
以下提供图表数据源的三种不同方式:
-
使用控制器方法
-
使用 JavaScript 函数
-
使用 JavaScript 数组
使用控制器方法
这种技术已在我们的简单饼图示例中说明。这是一个服务器端技术,在这里我们使用控制器方法返回一个对象列表。这个对象列表可以是我们的 Apex 包装对象(如前一个示例所示)、AggregateResult对象或sObjects。方法的结果在服务器端序列化为 JSON,并在客户端直接由<apex:chart>组件使用。有关此技术的更多信息,请参阅简单饼图示例。
使用 JavaScript 函数
提供图表组件数据还有另一种方式,即通过 JavaScript 函数。我们可以在<apex:chart>组件中使用 JavaScript 函数的名称。这个 JavaScript 函数是数据提供者,它可以在 Visualforce 页面中定义或链接到 Visualforce 页面。我们可以使用这个 JavaScript 函数在发送到<apex:chart>组件之前操纵数据。有关在 Visualforce 中使用 JavaScript Remoting 的更多信息,请参阅 Apex 控制器中的 JavaScript Remoting 部分。以下是一个带有<apex:chart>组件的简单 JavaScript 函数示例:
<apex:page controller="VFRemoteChartController" title="Pie Chart">
<script>
function getRemoteChartData(callback) {
VFRemoteChartController.getRemotePieChartData(function(result, event) {
if(event.status && result && result.constructor === Array) {
callback(result);
}
});
}
</script>
<apex:chart height="350" width="450" data="getRemoteChartData">
<apex:pieSeries dataField="data" labelField="name"/>
<apex:legend position="right"/>
</apex:chart>
</apex:page>
以下类是前一个页面的相关自定义控制器。我们使用@RemoteAction注解定义了远程方法。该远程方法将数据转换为图表组件:
public class VFRemoteChartController {
@RemoteAction
public static List<PieChartData> getRemotePieChartData() {
List<PieChartData> data = new List<PieChartData>();
data.add(new PieChartData('RAM', 30));
data.add(new PieChartData('Hard Disk', 15));
data.add(new PieChartData('VGA Card', 10));
data.add(new PieChartData('Mouse', 20));
data.add(new PieChartData('USB Drive 16BG', 20));
data.add(new PieChartData('USB Drive 32BG', 5));
return data;
}
// Wrapper class
public class PieChartData {
public String name { get; set; }
public Integer data { get; set; }
public PieChartData(String name, Integer data) {
this.name = name;
this.data = data;
}
}
}
使用 JavaScript 数组
提供数据的另一种方式是使用 JavaScript 数组。我们可以通过使用 JavaScript 数组(可以是 Salesforce 数据或非 Salesforce 数据)来使用 Visualforce 图表,而无需使用任何自定义控制器。我们可以在 JavaScript 代码中使用 Ajax Toolkit API 查询 Salesforce 数据,该 API 是 API 的 JavaScript 包装器,我们可以在自己的 JavaScript 代码中使用 JavaScript 数组来构建非 Salesforce 数据源。然后我们可以通过将数组的名称提供给<apex:chart>组件来在图表组件中使用该数组。当数据源仅依赖于客户端而不依赖于服务器端时,这种方法很有用。以下示例说明了如何定义使用 JavaScript 数组的 Visualforce 图表:
<apex:page >
<script>
// Build the chart data array in JavaScript
var dataArray = new Array();
dataArray.push({'data':15,'name':'Hard Disk'});
dataArray.push({'data':10,'name':'VGA Card'});
dataArray.push({'data':20,'name':'Mouse'});
dataArray.push({'data':20,'name':'USB Drive 16BG'});
dataArray.push({'data':5,'name':'USB Drive 32BG'});
</script>
<apex:chart height="350" width="450" data="dataArray">
<apex:pieSeries dataField="data" labelField="name"/>
<apex:legend position="right"/>
</apex:chart>
</apex:page>
小贴士
Visualforce 图表更可定制。我们可以自定义元素的外观和感觉、标记、填充颜色/线条的不透明度,并且可以结合各种数据源。
使用 Visualforce 图表的复杂图表
我们可以使用 Visualforce 图表构建复杂的图表,这些图表在一个图表中代表多个数据系列。例如,我们可以构建一个包含多个数据系列的图表。以下示例显示了三年内销售的商品数量。代码显示了自定义控制器,自定义控制器中的getComplexChartData()方法为图表组件准备数据。此控制器还有一个@RemoteAction方法,用于将数据传递到图表组件。但此示例没有使用 JavaScript 远程调用。它说明了在服务器端和 JavaScript 远程调用方法中重用数据生成方法的方式。
public class ComplexChartController{
// Return a list of data points for a chart
public List<ChartData> getVFChartData() {
return ComplexChartController.getComplexChartData();
}
// Make the chart data available via JavaScript remoting
@RemoteAction
public static List<ChartData> getRemoteVFChartData() {
return ComplexChartController.getComplexChartData();
}
//prepare data sources
public static List<ChartData> getComplexChartData() {
List<ChartData> data = new List<ChartData>();
data.add(new ChartData('RAM', 1300, 1275, 2534));
data.add(new ChartData('Hard Disk', 1234, 2431, 1534));
data.add(new ChartData('VGA Card', 2634, 2500, 2376));
data.add(new ChartData('Mouse', 1765, 2000, 1432));
data.add(new ChartData('USB D 16BG', 967, 932, 1450));
data.add(new ChartData('USB D 32BG', 500, 765, 1768));
return data;
}
// Wrapper class
public class ChartData{
public String name { get; set; }
public Integer data1 { get; set; }
public Integer data2 { get; set; }
public Integer data3 { get; set; }
public ChartData(String name, Integer data1, Integer data2, Integer data3) {
this.name = name;
this.data1 = data1;
this.data2 = data2;
this.data3 = data3;
}
}
}
以下截图表示一个包含三个线系列的图表。此图表说明了三年内销售的商品数量:

复杂图表示例的输出
小贴士
在折线图和柱状图中,需要定义 x 轴和 y 轴。
复杂图表示例的标记如下:
<apex:page controller="ComplexChartController">
<apex:chart height="400" width="700" data="{!vFChartData}">
<apex:axis type="Numeric" position="left" fields="data1" title="Items Sold Quantity" grid="true"/>
<apex:axis type="Category" position="bottom" fields="name" title="Item"> </apex:axis>
<apex:lineSeries axis="left" fill="true" xField="name" yField="data1" markerType="cross" markerSize="4" markerFill="#FF0000"/>
<apex:lineSeries axis="left" xField="name" yField="data2" markerType="circle" markerSize="4" markerFill="#8E35EF"/>
<apex:lineSeries axis="left" xField="name" yField="data3" markerType="circle" markerSize="8" markerFill="#FFFFFF"/>
</apex:chart>
</apex:page>
摘要
在本章中,我们熟悉了 Visualforce 图表,它允许我们根据我们的数据构建自定义图表。我们了解到 Visualforce 图表是一个基于 JavaScript 的功能。因此,我们学习了 Visualforce 图表的限制和注意事项。我们还看到了如何使用 Visualforce 图表组件创建简单和复杂的图表。
第七章. Visualforce for Mobile
通过使用 Visualforce 和 Apex,我们可以在 Force.com 平台上构建复杂、动态和强大的原生应用程序。如今,由于行业竞争,用户不仅满足于仅有一个 Web 应用程序。当涉及到 Force.com 平台上的应用程序时,我们可以通过使用 Visualforce Mobile 来扩展它们到移动设备。Visualforce Mobile 是客户端和按需编程的混合体。这使得我们能够通过离线数据访问的灵活性扩展移动设备的应用程序。我们可以通过使用 Visualforce 和 Apex 快速构建移动设备应用程序。
本章介绍了如何扩展在 Force.com 平台上为移动设备构建的应用程序,以及开发者如何使用 Visualforce Mobile。本章将涵盖以下主题:
-
理解 Salesforce Mobile
-
开发和移动化 Visualforce 页面
让我们构建适用于移动设备的 Visualforce。
理解 Salesforce Mobile
Salesforce Mobile 是由 Salesforce 提供的一款客户端应用程序。Salesforce Mobile 用于扩展在 Force.com 平台上构建的应用程序。这使得我们能够从 BlackBerry、iPhone 或 Windows Mobile 设备访问 Salesforce 数据。Salesforce Mobile 客户端应用程序具有以下功能:
-
通过无线运营商网络与 Salesforce 进行交互
-
在移动设备上存储用户数据的本地副本。它使用自己的数据库。
一组称为移动配置的参数由发送到移动设备的数据确定。它定义了用户 Salesforce 记录的相关子集。
小贴士
如果我们通过移动设备访问 Salesforce 数据,我们需要为特定用户购买单独的 Salesforce Mobile 用户许可证。开发者版和终极版只有一个移动许可证。其他人必须单独购买。
Salesforce Mobile 和 Visualforce Mobile 支持的设备
Salesforce Mobile 支持 BlackBerry、iPhone 和 Windows Mobile。目前,Windows Mobile 不支持 Visualforce Mobile。BlackBerry 和 iPhone 设备必须满足以下要求:
-
BlackBerry 配置如下:
-
支持的操作系统版本为 4.3 至 7.0,以及为了更好的性能,版本为 4.6 至 4.7
-
设备上应至少有 5 MB 的空闲内存
-
移动客户端应用程序支持 BlackBerry 8100 系列(Pearl)、BlackBerry 8300 系列(Curve)、BlackBerry 8800 系列、BlackBerry 8900 系列(Javelin)、BlackBerry 9000 系列(Bold)和 BlackBerry 9500 系列(Storm)
小贴士
您不需要拥有 iPhone 或 BlackBerry 设备来开发和测试应用程序。您可以使用模拟器。
-
-
iPhone 配置如下:
-
支持最新的 iPhone 操作系统
-
设备上应至少有 5 MB 的空闲内存
-
移动客户端应用程序支持 iPhone、iPhone 3G、iPhone 3GS 和 iPod Touch
-
移动应用程序的功能和限制
Salesforce Mobile 的原生客户端应用程序包含一个嵌入式浏览器,用于在客户端应用程序和 Visualforce 页面之间通信。在使用 Salesforce Mobile 时,我们需要考虑以下一些问题:
-
账户、资产、联系人、机会、任务、潜在客户、事件、价格表、产品、案例解决方案和自定义对象可以被移动化。
-
自定义链接、s-controls、混合应用、合并字段和图像字段无法移动化。
-
工作流规则、验证规则、公式字段和 Apex 触发器不适合在移动设备上运行。然而,在记录保存并提交到 Salesforce 后,它们可以在服务器端运行。
-
用户权限、记录类型和页面布局从 Salesforce 继承。然而,管理员可以更改它们以限制移动用户的权限。
-
当我们将子数据集添加到父数据集时,该对象在移动设备上成为相关列表。
-
报告仅在 BlackBerry 客户端应用程序中可用,但仪表板在 iPhone 和 BlackBerry 中均可用。
-
排序、摘要、小计或分组在移动应用程序的报告查看器中不受支持。
-
Salesforce 的自定义视图可以被 iPhone 和 BlackBerry 用户访问,但只能由 BlackBerry 用户创建自定义视图。
-
在移动应用程序中,自定义视图仅限于两列。
-
移动化的 Visualforce 标签和 Web 标签可以被 iPhone 和 BlackBerry 用户在客户端应用程序中访问。
小贴士
嵌入式浏览器通过设备的互联网连接与 Salesforce 通信;原生客户端应用程序通过 SOAP API 异步与 Salesforce 通信。嵌入式浏览器可以执行 JavaScript,但原生客户端应用程序不能。
使用 Visualforce Mobile
当我们使用移动应用程序时,它们是客户端应用程序,并且需要安装。移动应用程序需要定期连接以发送和接收数据。与移动按需应用程序相比,移动客户端应用程序依赖于网络连接和速度。移动客户端应用程序也可以用于离线工作。当我们谈到速度时,无线数据网络仍然非常慢。但是客户端应用程序响应速度很快。
有一些情况,原生客户端应用程序无法满足客户的需求。因此,我们可以在以下情况下使用 Visualforce Mobile:
-
与其他 Web API 集成,例如 Google Maps
-
重建客户端应用程序中不可用的功能,例如响应审批请求
-
将不支持在客户端应用程序中的标准 Salesforce 对象移动化
-
与外围设备集成,例如蓝牙或嵌入式 GPS
-
覆盖详细页面上的标准按钮操作
开发和移动化 Visualforce 页面
为 Salesforce Mobile 开发 Visualforce 页面与为 Salesforce 开发页面不同,尤其是在桌面浏览器和移动浏览器的用户体验不同的情况下。
为 iPhone 和 BlackBerry 构建 Visualforce Mobile 页面的最佳实践
以下是一些最佳实践:
-
控制器:Salesforce Mobile 支持自定义对象和许多标准对象。标准页面的标准布局和样式对于移动浏览器来说过于复杂。在为移动应用程序开发页面时,最佳实践是使用自定义控制器。如果您的控制器具有非常复杂的业务逻辑,可能会减慢页面的加载速度。
-
页眉和侧边栏:从 Visualforce Mobile 页面中删除页眉和侧边栏,因为它们可能会导致加载时间过长,并且在移动屏幕上没有足够的空间显示。可以通过以下代码删除:
<apex:page showHeader="false" sidebar="false"> -
页面样式:Salesforce 页面的标准样式表对于移动浏览器来说过于复杂。我们必须通过使用
<apex:page>标签的standardStylesheets属性停止加载标准样式表,如下所示:<apex:page showHeader="false" sidebar="false" standardStylesheets="false"> <style type="text/css"> <!—your custom styles here--> </style> </apex:page>提示
向页面添加样式表的最佳方法是在
<apex:page>标签下方包含一个<style>部分。 -
重用:在移动客户端应用程序中,重用是一个关键组件。我们可以创建一个带有自定义样式的 Visualforce 页面,并在其他 Visualforce 页面中使用
<apex:include>组件重用该页面。例如,如果前一页的名称是myStylePage并且我们在该页面上实现了自定义样式,那么我们可以按照以下方式包含带有样式的前一页:<apex:page standardStylesheets="false"/> <apex:include pageName="myStylePage"/> </apex:page>提示
我们可以将移动优化的样式表作为静态资源创建,并在非移动页面上引用相同的样式表。使用样式表作为静态资源可能会增加页面的加载时间。
-
查找:在 BlackBerry 和 iPhone 上,查找功能无法正常工作。因此,最佳实践是在保存记录时验证查找条目。我们可以使用 Apex 触发器进行验证。我们还可以偶尔更改文件类型。
iPhone 注意事项
在为 iPhone 开发页面时,我们必须考虑以下事项:
-
页面缩放:为了最大限度地提高与广泛网站的兼容性,iPhone 浏览器将页面宽度设置为 980 像素。使用
<meta>标签,iPhone 浏览器可以识别显示页面的宽度。以下标签定义仅适用于 iPhone 浏览器,其他浏览器忽略此标签:<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> -
URL 目标:嵌入式浏览器不支持
target="_blank"属性。带有此属性的页面将无法加载。 -
屏幕旋转:旋转屏幕不会导致页面翻转和调整大小。
-
静态资源缓存:嵌入式浏览器不支持缓存。
-
文件访问:嵌入式浏览器本身不提供对文件系统、相机、位置或其他设备数据的访问。
提示
移动开发的通用规则
我们不应该使用带有 JavaScript 的组件在依赖于 Salesforce 样式的组件上执行操作。如果我们能在我们页面的 HTML 源代码中看到指向 JavaScript(.js)文件的<script>标签或指向样式表(.css)的<link>标签,那么页面就可能属于前面的类别。
BlackBerry 注意事项
在为 BlackBerry 智能手机开发页面时,以下注意事项会被应用:
-
JavaScript 支持:行内 DOM 事件在 BlackBerry 浏览器中不起作用。BlackBerry 浏览器对 JavaScript 的支持有限。在为 BlackBerry 开发 Visualforce 页面时,避免使用 JavaScript 是最佳选择。
-
表单和视图状态:如果你想在你的 Visualforce 移动页面上使用
<apex:form>标签,请使用标准的 HTML 表单而不是<apex:form>。如果我们使用<apex:form>标签,页面的视图状态对于 BlackBerry 浏览器来说会太大。当我们使用标准的 HTML 标签时,我们必须手动完成以下操作,并且不能使用<apex:commandLink>标签和<apex:commandButton>组件:-
在页面之间保持状态
-
重定向到另一个页面
小贴士
可以使用控制器中的
ApexPages.currentPage().getParameters()映射检索从表单发送的参数。
-
-
页面样式:我们必须遵循为 iPhone 和 BlackBerry 构建 Visualforce 移动页面的最佳实践。此外,我们必须知道 BlackBerry 浏览器会忽略一些 CSS 属性,例如
margin-left。 -
换行符:除非行上有内容,否则
<br/>标签会被忽略。 -
导航:在 BlackBerry 客户端应用程序的嵌入式浏览器中没有任何内置的导航。我们必须提供导航链接。
开发跨平台兼容的页面
当我们构建 Visualforce 移动页面以在 iPhone 和 BlackBerry 浏览器上表现良好时,我们需要遵循 Salesforce 提供的以下方法:
-
分离和重定向:我们可以通过使用 JavaScript 构建 Visualforce 移动页面,将其重定向到适合优化的页面(iPhone 优化的或 BlackBerry 优化的)来实现。为此,我们必须分别为 iPhone 和 BlackBerry 构建页面。当连接的设备不是 BlackBerry 设备时,页面将重定向到 iPhone 优化的页面,如下面的代码所示:
<apex:page> <language="javascript" type="text/javascript"> if(!window.blackberry){ window.location.href='{!$Page.iPhoneOptimizedVersion}'; } </script> </apex:page> -
条件代码: 服务器通过使用头部的用户代理字符串来识别连接的设备(iPhone 或 BlackBerry),这是浏览器传递给服务器的用户代理字符串。因此,我们可以为 iPhone 和 BlackBerry 设备上的性能良好的页面构建设备条件代码和样式。优点是标记在服务器端解释,用户只得到由条件代码选择的合适标记。缺点是由于条件代码,代码可能更复杂。以下示例显示了处理条件代码和标记的方式。标记有两个
<apex:outputPanel>组件,每个组件为特定设备渲染标记:<apex:page controller="ConditionalCodeController"> <apex:outputPanel rendered="{!deviceType = 'BlackBerry'}"> <apex:outputText value="This is BlackBerry"></apex:outputText> </apex:outputPanel> <apex:outputPanel rendered="{!deviceType = 'iPhone'}"> <apex:outputText value="This is iPhone"></apex:outputText> </apex:outputPanel> </apex:page>前一个标记的控制器如下,它评估用户代理并准备
deviceType属性,以便渲染正确的输出面板:public class ConditionalCodeController { public String deviceType { get; set; } public ConditionalCodeController() { String userAgent = ApexPages.currentPage().getHeaders().get('USER-AGENT'); if(userAgent.contains('iPhone')) { deviceType = 'iPhone'; } else if(userAgent.contains('BlackBerry')) { deviceType = 'BlackBerry'; } } } -
最低共同点: 建设到最低共同点,仅包含最小、不干扰的 JavaScript,避免在标签中使用内联事件的脚本。根据客户组织中的设备,您可能需要完全避免 JavaScript。在较旧的 BlackBerry 智能手机上,使用任何 JavaScript 代码可能会使页面出现故障。
使用 JavaScript 库
JavaScript 库包含在 Salesforce 移动中触发操作命令。此 JavaScript 库可用于在本地客户端应用程序和 Visualforce 移动页面上构建无缝的用户体验。
小贴士
JavaScript 命令仅在启用 JavaScript 的设备上工作。
以下是在 JavaScript 库中的函数:
-
mobileforce.device.sync(): 此函数强制移动客户端应用程序与 Salesforce 同步,更新设备上的数据记录。 -
mobileforce.device.close(): 此函数关闭包含 Visualforce 页面的嵌入式浏览器,并将用户返回到原始/上一个标签或记录。 -
mobileforce.device.syncClose(): 此函数强制移动客户端应用程序与 Salesforce 同步,并关闭包含 Visualforce 页面的嵌入式浏览器。 -
mobileforce.device.getLocation(): 此函数获取设备当前位置的 GPS 坐标。
以下示例展示了 JavaScript 库中所有可用命令的使用:
<apex:page showheader="false">
<html >
<head>
<title>Visualforce Mobile Trigger Test</title>
<!-- <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />-->
<!-- Using static resource -->
<script type="application/x-javascript" src="img/mobileforce.js">
</script>
<script>
function sync() {
mobileforce.device.sync();
return false;
}
function doClose() {
mobileforce.device.close();
return false;
}
function syncClose() {
mobileforce.device.syncClose();
return false;
}
updateLocation = function(lat,lon) {
document.getElementById('lat').value = lat;
document.getElementById('lon').value = lon;
}
function getLocation(){
mobileforce.device.getLocation(updateLocation);
return false;
}
</script>
</head>
<body>
<h2>Triggers:</h2>
<p>
<a href="#" onclick="return sync();">JS sync</a><br/>
<a href="#" onclick="return doClose();">JS close</a><br/>
<a href="#" onclick="return syncClose();">JS sync and close</a><br/>
<a href="mobileforce:///sync">HTML sync</a><br/>
<a href="mobileforce:///close">HTML close</a><br/>
<a href="mobileforce:///sync/close">HTML sync and close</a><br/>
</p>
<h2>Location:</h2>
<p>Latitude: <input type="text" disabled="disabled" id="lat" name="lat" value=""/></p>
<p>Logitude: <input type="text" disabled="disabled" id="lon" name="lon" value=""/></p>
<a href="#" onclick="return getLocation();">Get location</a><br/>
</body>
</html>
</apex:page>
构建一个移动就绪的 Visualforce 标签
在构建 Visualforce 移动页面后,我们必须进行一些配置才能通过 Salesforce 移动访问这些页面。为了使 Visualforce 页面移动化,我们可以使用Mobile Ready属性创建 Web 和 Visualforce 标签。在导航到以下路径并点击 Visualforce 标签中的新建后,我们将被引导到一个页面,在那里我们将创建一个移动就绪的 Visualforce 标签:
YourName | Setup | Create | Tab

构建一个移动就绪的 Visualforce 标签
“移动就绪”复选框用于指定 Visualforce 页面是否在移动浏览器中正确显示和运行。通过选择“移动就绪”复选框,我们可以将选项卡添加到我们的移动配置可用选项卡列表中。
创建移动配置
当我们使用 Salesforce 移动时,我们必须确定移动用户的数据和授予移动用户的权限。移动配置是一组参数。一个组织可以为不同类型的移动用户拥有多个移动配置。当我们创建移动配置时,必须将特定用户分配给移动许可证。
小贴士
如果用户账户的“移动用户”复选框被勾选,那么这意味着该用户已被识别为移动用户,除非我们向用户的配置文件或权限集授予“管理移动配置”权限。
要创建移动配置,请导航到:
您的姓名 | 设置 | 移动管理 | Salesforce 移动 | 配置 | 新建移动配置
只有活动的移动配置将可供使用。可选地,我们可以选择“移动最近项目”选项来标记 Salesforce 中最近使用的记录以进行设备同步,以及选择“移动最近项目”选项从“最大最近项目数”下拉列表中选择一个值。在移动配置中,我们可以添加单个用户或使用配置文件来授予权限。如果数据大小超过属性,我们将不会同步,以避免过度加载移动设备。为此,我们必须指定在此移动配置中所有数据集合并允许的最大数据大小。
小贴士
如果合并后的数据大小超过此限制,Salesforce.com 不会同步任何数据集。
创建移动配置后,我们可以通过添加对象和记录来定义数据集,并自动同步移动设备。
在开发完 Visualforce 移动页面后,我们必须测试它们以检查其功能和外观。为此,我们可以在 BlackBerry 或 iPhone 设备上安装移动应用程序,或者使用 iPhone 或 BlackBerry 模拟器。
摘要
在本章中,我们学习了 Salesforce 移动及其使用方法。我们熟悉了将基于 Force.com 平台构建的应用程序扩展到移动设备的方式,以及如何使用 Visualforce 移动。我们了解了如何开发和移动 Visualforce 页面。我们看到了 Visualforce 移动所需的移动设备支持配置。
第八章:Visualforce 开发的最佳实践
Visualforce 页面是 Salesforce 标准页面的替代品。当我们使用 Visualforce 页面时,不应存在延迟体验和意外行为。因此,我们必须遵循最佳实践,在 Visualforce 开发过程中提高用户体验和编码标准,以改善用户体验。在某些情况和组件中,我们可以应用一些最佳实践。本章将涵盖以下主题:
-
访问组件 ID
-
页面块组件
-
控制器和控制器扩展
-
提高 Visualforce 的性能
-
静态资源
-
渲染 PDF
-
使用组件面
让我们构建具有超级性能的 Visualforce 页面…
访问组件 ID
当我们在 JavaScript 中引用 Visualforce 组件时,ID 属性起着重要作用。每个 Visualforce 组件都有一个 ID 属性。为了在 JavaScript 中引用它,必须指定特定组件的 ID 属性,并且它用于将两个组件绑定在一起。当页面渲染时,这个 ID 属性是特定组件 DOM ID 的一部分。ID 属性也必须是唯一的。以下是一些用于访问组件 ID 的最佳实践:
-
使用
$Component全局变量来简化访问。例如,当我们有一个id="inputOne"的输入字段在id="blockOne"的页面块内时,我们可以使用$Component.blockOne.inputOne表达式来访问输入字段。 -
如果要访问的组件是 Visualforce 组件层次结构中
$Component变量的祖先或兄弟,则无需为该组件指定 ID。
页面块组件
<apex:pageBlockSectionItem>组件只能有两个子组件。根据客户需求和开发需求,<apex:pageBlockSectionItem>内部可以有超过两个子元素。使用<apex:outputPanel>,我们可以在<apex:pageBlockSectionItem>中添加超过两个元素,如下所示:
<apex:pageBlock>
<apex:pageBlockSection>
<apex:pageBlockSectionItem>
<apex:outputLabel value="LabelName"/>
<apex:outputPanel>
<!—We can add our extra child components here within the apex:outputPanel -->
</apex:outputPanel>
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
</apex:pageBlock>
控制器和控制器扩展
当我们开发与 Visualforce 页面关联的控制器和控制器扩展时,我们需要遵守以下最佳实践:
-
通过使用
with sharing关键字,我们可以在控制器中强制执行共享规则。然后代码将在用户模式下执行,而不是系统模式下。 -
我们不应依赖于在构造函数之前执行 setter 方法。
-
在自定义控制器或控制器扩展中创建自定义方法时,我们不应依赖于执行顺序或副作用。
-
不要在循环中使用 DML 操作。
-
在执行记录过滤时,按以下顺序添加过滤器:
-
在 SOQL 中
-
在 Apex 中
-
在 Visualforce 中
-
-
如果可能,必须在 SOQL 中而不是 Apex 中进行计算。
提高 Visualforce 的性能
Visualforce 页面的性能是开发中需要考虑的关键因素,因为性能是影响最终用户对应用程序满意度的原因。以下是一些提高 Visualforce 性能的最佳实践:
-
每个 Visualforce 页面只使用一个
<apex:form>标签,因为每个<apex:form>标签都会给页面添加一个视图状态。Visualforce 页面的视图状态大小限制为 135 KB。我们可以通过减少视图状态的大小来缩短 Visualforce 页面的加载时间。 -
尽可能地在自定义控制器中使用 transient 关键字。transient 实例变量不维护状态。如果特定实例仅在页面请求中使用,则它不应是视图状态的一部分。这将有助于减少视图状态的大小。
-
当使用 SOQL 查询引用特定对象的属性时,只使用 SOQL 查询中的相关数据。
-
在设计 Visualforce 页面时,不要让页面承载过多的功能和数据。过载的页面会增加视图状态、页面大小、堆大小,并可能导致视图状态达到限制器限制。
-
为了减少 Visualforce 页面的加载时间:
-
不要在 getter 方法中使用 SOQL 查询。
-
经常使用或全局数据必须进行缓存。
-
通过使用
standardSetControllers的内置分页或限制 SOQL 查询中的数据来减少页面中显示的记录数。
-
-
我们可以通过使用
<apex:actionPoller>组件来减少多个并发请求的延迟,从而增加调用 Apex 控制器的间隔时间。 -
使用 SOQL 的
OFFSET实现特定子集结果的分页。 -
如果一个组织中有大量只读数据,那么我们必须使用自定义对象或自定义设置来存储这些数据。
-
使用
<apex:repeat>在大型集合上迭代。 -
不要在 Visualforce 页面中硬编码选择列表值,而是使用控制器将它们添加到
selectOption列表中。 -
使用延迟加载方法,根据必要性减少或延迟数据的加载。在延迟加载中,基本功能将首先加载,其他功能将延迟到用户操作时再加载。为了实现延迟加载,我们可以:
-
使用
rerender属性执行部分页面加载。 -
使用 JavaScript remoting 调用控制器中的操作。
-
-
在 Visualforce 页面中使用 CSS 时,我们必须小心。Visualforce 的性能直接受到 Visualforce 优化的影响。以下是一些提高 CSS 性能的技巧:
-
使用单独的 CSS 文件,并在 Visualforce 页面中引用它们(而不是在页面本身中编写 CSS 代码)。这将减小文件大小。
-
使用单个 CSS 文件而不是多个 CSS 文件。这将减少 HTTP 请求的数量。
-
通过静态资源引用 CSS 文件,因为它具有内置的缓存机制。
-
当创建完全自定义 CSS 的页面(不使用 Salesforce CSS)时,不要忘记将
<apex:page>标签的showHeaders和standardStylesheets属性设置为false。
-
-
当在 Visualforce 页面中使用 JavaScript 时,我们必须优化它们以提高 Visualforce 的性能。以下是一些提高 JavaScript 性能的提示:
-
使用单独的 JavaScript 文件并在 Visualforce 页面中引用它们(而不是在页面本身中编写 JavaScript)。这将减少利用浏览器缓存的单个页面的大小。
-
使用单个 JavaScript 文件而不是使用多个 JavaScript 文件。这将减少 HTTP 请求的数量并移除重复的函数。
-
使用仅包含所需功能的自定义版本的 JavaScript 库。这将减少文件大小。
-
如果可能,使用
<script>标签在 Visualforce 页面中包含 JavaScript,并将其放置在</apex:page>关闭标签之前。这将避免在 Visualforce 页面的任何其他内容之前加载 JavaScript。 -
删除不必要的注释和空白,以减少文件大小并加快下载速度。
-
-
使用
escapeSingleQuotes方法来避免 SOQL 和 SOSL 注入攻击。 -
图片在 Visualforce 页面中经常扮演重要角色。因此,我们必须使用以下提示来优化 Visualforce 页面中图片的使用:
-
使用 CSS Sprites 而不是单个图片。使用 Sprites,我们可以将(相似大小的)图片合并成一个文件。这将减少页面中使用的图片数量并减少 HTTP 请求的数量。
-
如果可能,尽量减少图片的使用并鼓励使用 CSS。
-
使用静态资源来引用 Visualforce 页面中的图片。
-
提示
由于视图状态数据被加密,无法使用 Firebug 等工具查看视图状态。
静态资源
静态资源具有内置的缓存功能,并使用 Salesforce 内置的内容分发网络。使用静态资源引用 CSS 文件、图片和 JavaScript 的以下是一些优点:
-
使用静态资源通过
<apex:page>标签的动作属性显示另一个静态资源的内容。通过这样做,我们可以从 Visualforce 页面重定向到静态资源。假设我们有一个名为helpPdf的 PDF 作为静态资源,我们将其用作<apex:page>标签的动作属性,如下所示:<apex:page sidebar="false" showHeader="false" standardStylesheets="false" action="{!URLFOR($Resource.helpPdf)}"> </apex:page> -
URLFOR函数在这里起着重要作用。没有URLFOR函数,重定向将无法正常工作。这不仅仅限于 PDF;我们可以使用任何静态资源进行重定向。<apex:page sidebar="false" showHeader="false" standardStylesheets="false" action="{!URLFOR($Resource.helpStaticResource, 'index.htm')}"> </apex:page>
渲染 PDF
当我们在 Visualforce 页面中使用组件并且页面被渲染为 PDF 时,这些组件并不总是工作。我们不得使用依赖于 JavaScript 动作和 Salesforce 标准样式的组件。
-
以下组件在 PDF 渲染中是安全的:
-
<apex:composition>(只要页面包含安全的 PDF 组件) -
<apex:facet> -
<apex:dataList> -
<apex:define> -
<apex:include>(只要页面包含安全的 PDF 组件) -
<apex:insert> -
<apex:image> -
<apex:repeat> -
<apex:outputLabel> -
<apex:outputLink> -
<apex:outputPanel> -
<apex:outputText> -
<apex:page> -
<apex:panelGrid> -
<apex:panelGroup> -
<apex:param> -
<apex:stylesheet>(只要 URL 没有直接引用 Salesforce 样式表) -
<apex:variable>
-
-
以下组件在渲染 PDF 时可以谨慎使用(其他组件在 PDF 渲染中不安全):
-
<apex:attribute> -
<apex:column> -
<apex:component> -
<apex:componentBody> -
<apex:dataTable>
-
使用组件面
<apex:facet>组件用于指定 Visualforce 页面区域的内容,并提供有关父组件中数据的信息。例如,我们可以在<apex:dataTable>的页眉或页脚中使用面组件。我们可以通过使用<apex:facet>组件来覆盖 Visualforce 组件的默认面。以下是一个关于面组件的优缺点示例:
-
<apex:facet>组件不能直接在 Apex 中使用;它必须是另一个 Visualforce 组件的子组件。我们可以在动态组件中使用它。 -
面仅允许在开始和结束标签内有一个子组件。
-
以下是一个使用
<apex:facet>组件与<apex:dataTable>组件一起使用的示例:<apex:page standardController="Item__c"> <apex:pageBlock> <apex:dataTable value="{!item}" var="i"> <apex:facet name="caption"><h1>This is {!item.Item_Name__c}</h1></apex:facet> <apex:column> <apex:facet name="header">Name</apex:facet> <apex:outputText value="{!i.Item_Name__c}"/> </apex:column> <apex:column> <apex:facet name="header">Unit Price</apex:facet> <apex:outputText value="{!i.Unit_Price__c }"/> </apex:column> </apex:dataTable> </apex:pageBlock> </apex:page> -
我们可以使用面组件与
<apex:actionSatus>一起使用。它用于扩展显示状态指示器。以下是一个示例:<apex:page> <apex:form > <apex:commandButton value="Facet with action Status" status="Status" rerender="rerenderBlock"/> <apex:pageBlock id="rerenderBlock"> </apex:pageBlock> <apex:actionStatus id="Status"> <apex:facet name="start"> <img src="img/{!$Resource.LoadingAnimation}"/> </apex:facet> </apex:actionStatus> </apex:form> </apex:page>
摘要
本章旨在解释 Visualforce 开发的最佳实践。在本章中,我们熟悉了遵循的最佳实践,以避免意外行为,减少访问组件 ID、页面块组件、控制器和控制器扩展、提高 Visualforce 性能、静态资源、渲染 PDF 和使用组件面的延迟体验。我们看到了提高用户体验和编码标准的方法。
附录 A. Apex 和 Visualforce 开发的安全提示
安全性是网络应用程序的重要组成部分。这一重要部分同样适用于 Force.com 应用程序。我们使用 Visualforce 标记和 Apex 创建自定义页面,这使得我们能够向客户提供完全定制的功能。当我们使用这些编程语言时,我们必须注意安全方面。
Force.com 平台提供一些内置的安全辅助功能,例如用户管理、配置文件管理、角色层次结构、组织默认值(OWD)、权限集、公共组、共享设置、字段可访问性、密码策略、会话设置、网络访问、登录访问策略、证书和密钥管理、单点登录设置、身份提供者、身份提供者、查看设置审计跟踪、过期所有密码、委托管理、远程站点设置和 HTML 文档和附件设置。但是,当我们使用 Apex 和 Visualforce 创建自定义页面时,我们必须小心,因为有许多方法可以绕过 Force.com 平台上的内置安全防御。可能存在一般安全漏洞以及 Apex 和 Visualforce 特定的漏洞。
安全扫描工具
在我们将应用程序添加到 AppExchange 之前,我们必须获得安全方面的认证。开发者必须意识到这些安全关注点。有一些工具可用于扫描我们的代码以检查安全和质量,例如,Force.com 安全源扫描器。
Force.com 安全源扫描器
Force.com 安全源扫描器是 Force.com 平台上的基于云的代码分析工具。这是一个免费的工具,供 Force.com 开发者使用,代码扫描遵循先到先得的原则。文件大小、队列大小和代码的复杂性直接影响扫描结果的时间。为了扫描特定的 Salesforce.com 用户账户,我们必须拥有“编写 Apex”权限,并且特定的代码不能包含在包中。我们可以在security.force.com/security/tools/forcecom/scanner提交代码进行扫描。此工具扫描所有可能的代码流程,并检查 Apex 代码的安全漏洞和质量。Force.com 安全源扫描器可以检测以下安全漏洞。
-
跨站脚本
-
SOQL 注入
-
SOSL 注入
-
框架欺骗
-
访问控制问题
Force.com 安全源扫描器可以检测以下代码和设计问题:
-
循环中的 DML 语句
-
循环中的 SOQL/SOSL
-
未将 Apex 方法批量化
-
循环中的异步(
@future)方法 -
硬编码 ID
-
硬编码
Trigger.new[0] -
硬编码
Trigger.old[0] -
引用静态资源
-
没有 WHERE 子句或没有 LIMIT 子句的查询
-
同一对象上的多个触发器
跨站脚本(XSS)
跨站脚本攻击会攻击存在恶意客户端脚本或 HTML 的 Web 应用程序。如果 Web 应用程序包含恶意脚本,攻击者就可以利用 Web 应用程序作为中间层,使受信任的用户成为攻击的受害者。当动态生成的 Web 页面显示无效、未经过滤和未编码的用户输入时,就会发生跨站脚本弱点,这允许攻击者将恶意脚本嵌入到生成的页面中。这可以被利用来执行脚本代码,就像它来自网站的服务器一样,发送到任何使用该网站的用户的计算机上。
Force.com 平台有几种方法来防止 XSS(跨站脚本)攻击,具体如下:
-
Visualforce 页面中的未转义输出和公式:可能存在依赖于用户输入的 Visualforce 页面,并且后续功能将使用该用户输入继续。有一些编码函数可以阻止 XSS 漏洞,具体如下:
-
HTMLENCODE:此函数将文本和合并字段值编码为保留 HTML 字符。例如,大于号(>)编码为>。 -
JSENCODE:此函数通过插入转义字符来编码文本和合并字段值。 -
JSINHTMLENCODE:此函数执行HTMLENCODE和JSENCODE函数的任务。 -
URLENCODE:此函数通过替换 URL 中的非法字符来编码文本和合并字段值。例如,空格被替换为%20。
-
提示
所有标准 Visualforce 组件(以<apex>开头)都是防 XSS 的。它们有一个过滤器来阻止 XSS 攻击。可选地,我们可以通过将 escape 属性的值设置为 false 来禁用 Visualforce 组件上的转义。
跨站请求伪造(CSRF)
互联网无法,也无法充分验证一个格式良好、有效、一致请求是否是提交请求的用户有意提供的。实际上,当服务器收到请求时,它无法确定该请求是由有效用户还是攻击者发起的,这可能导致权限提升或数据盗窃攻击的潜在升级。
Force.com 平台在标准控制器中实现了反 CSRF(跨站请求伪造)机制。每个页面都有一个作为隐藏字段的随机字符。当我们加载下一页时,会检查其有效性,并在值与预期值匹配后执行命令。
以下代码在名为AutoRun的自定义方法中绕过了反 CSRF 控制。在 Force.com 平台中,此类场景没有内置的反 CSRF 控制。有一些解决方案可以在执行操作之前添加一个中间确认页面,并缩短组织的空闲会话超时时间:
<apex:page controller="SecurityIssuesController" sidebar="false" action="{!AutoRun}">
public class SecurityIssuesController{
public Pagereference AutoRun(){
Id id = ApexPages.currentPage().getParameters().get('id');
Item__c singleItem = [select id, Name FROM Item__c WHERE id = :id];
delete singleItem;
return null;
}
}
SOQL 注入
最常见的注入攻击发生在用户的输入直接涉及查询或命令时。因此,攻击者可以传递不受信任的数据来执行特定的功能或命令。然后攻击者将获得访问未经授权数据的权限。
Apex 使用 SOQL 作为查询语言,其功能比 SQL 有限。但 SOQL 注入攻击与 SQL 注入攻击类似。Salesforce.com 用户愿意将敏感数据放入 Salesforce,因为 Salesforce.com 是一个安全平台。因此,当我们构建自定义页面和自定义控制器时,我们必须更加注意防止此类攻击。在 Force.com 中,SOQL 注入发生在动态 SOQL 查询中。
动态 SOQL 用于在 Apex 代码的运行时创建 SOQL 查询字符串,并允许我们构建更灵活的功能(例如,依赖于用户输入的搜索功能)。使用 Database.query(queryString) 方法,我们可以创建动态查询,返回单个 sObject 或 sObjects 列表。如果应用程序在 Apex 中使用用户的输入来构建动态 SOQL 而我们没有正确处理输入,则可以在 Apex 中实现 SOQL 注入。
Force.com 平台提供了一个名为 escapeSingleQuotes 的方法来防止 SOQL 注入。使用此方法,我们可以通过在用户输入字符串中的所有单引号添加转义字符(\)来处理用户的输入。基本上,此方法将所有单引号视为字符串的定界符,而不是数据库命令。
以下示例说明了 Apex 中 SOQL 注入的漏洞。此查询返回未交付的订单记录以及特定订单的客户的名称(cusName),该名称是根据用户输入找到的。
String queryString = 'SELECT Id FROM Order__c WHERE (Delivered__c = false and Customer__r.Name like \'%' + cusName + '%')';
如果用户输入是 chamil,执行查询字符串将如下所示:
queryString = SELECT Id FROM Order__c WHERE (Delivered__c = false and Customer__r.Name like '% chamil %')
这是一个干净的输入。但问题是用户可以输入恶意输入,例如,chamil%' 或 Customer__r.Name like ''。然后查询字符串将如下所示:
queryString = SELECT Id FROM Order__c WHERE (Delivered__c = false and Customer__r.Name like '%chamil%') or (Name like '%')
在这种情况下,查询的结果将不会返回选择性的订单,而是将数据库中的所有订单都发送出来。这是 SOQL 注入的影响。有一种方法可以保护免受此类 SOQL 注入攻击。我们可以使用一个字符串变量来分配用户输入,并将该特定变量添加到动态查询中。以下是为前述漏洞提供的固定代码片段:
String userInput = '%' + cusName + '%';
String queryString = 'SELECT Id FROM Order__c WHERE (Delivered__c = false and Customer__r.Name like: userInput)';
数据访问控制
Force.com 平台允许我们配置对象权限(读取、创建、编辑和删除)并创建数据共享规则。我们可以使用这些功能来实现安全控制。标准控制器遵循这些安全设置。但是,自定义控制器和控制器扩展可以在执行过程中访问所有数据。这是默认行为,但我们可以使用 with sharing 关键字从 Apex 类中控制数据访问。该关键字的使用方法如下:
public with sharing class ExampleController {
public void methodOne()
{
List<Item__c> = [Select Id, Name FROM Item__c WHERE Id IN: itemIds];
}
}
with sharing关键字强制 Apex 类考虑已登录用户的权限共享安全设置。
摘要
我们学习了保护应用程序的重要性。我们熟悉了可能存在的漏洞、解决这些漏洞的方法以及安全扫描工具。
每个开始都有一个结束,因此我们已到达本书的结尾。我们涵盖了帮助你提高 Visualforce 开发知识的最重要主题。此外,你可以通过在 Twitter 上使用#askforce和blogs.developerforce.com来使用 Force.com 资源,例如 Force.com 讨论板(你可以在技术问题上获得帮助)。
愿力量与你同在!





浙公网安备 33010602011771号