动态视觉力 绑定是编写显示信息的通用 Visualforce 页面的一种方式 关于记录,而不一定知道要显示哪些字段。换言之,字段 页面是在运行时确定的,而不是在编译时确定的。这允许开发人员设计一个 根据权限为不同受众呈现不同受众的单个页面,或者 偏好。动态绑定对于托管的 Visualforce 页面非常有用 软件包,因为它们允许以非常 很少编码。支持动态 Visualforce 绑定 适用于标准和自定义对象。动态绑定采用以下一般形式:
reference[expression]
哪里
- reference计算结果为 sObject、Apex 类或全局 变量
- expression计算结果为作为字段名称的字符串,或 相关对象。如果返回相关对象,则该对象可用于递归选择字段或 其他相关对象。
动态绑定可以在公式表达式有效的任何位置使用。在以下页面上使用它们 这:
{!reference[expression]}
或者,您可以在整个动态的末尾添加 表达。如果动态表达式解析为 sObject,则引用该对象上的特定字段。如果 u 是 Apex 类,则该字段必须为 or 。为 例:
fieldnamefieldnamereferencepublicglobal
{!myContact['Account'][fieldname]}
您的动态 Visualforce 页面应该是 旨在为页面上的对象使用标准控制器,并实现任何进一步的 通过控制器进行定制 扩展。
您可以使用 Apex 方法获取 动态引用的信息,特别是访问对象字段的引用。 例如,以 Apex 控制器和 扩展可以理解。Schema.SobjectTypeSchema.SobjectType.Account.fields.getMap()
重要
保存页面时会检查静态引用的有效性,并且 无效的引用将阻止您保存它。就其性质而言,动态引用只能 在运行时进行检查,如果您的页面包含无效的动态引用,则 页面被查看,页面失败。可以创建对自定义字段的引用,或者 全局变量,但如果该字段或全局值稍后被删除,则页面 下次查看时将失败。
定义关系
和 都可以是复杂的表达式,例如计算结果为对象的表达式 关系。例如,假设一个名为 Object1__c 的对象与 另一个名为 Object2__c 的对象。这两个对象之间的关系的名称是 称为Relationship__r。referenceexpression如果 Object2__c 有一个名为 的字段,则 以下动态转换查找都返回对同一字段的引用:
myField
- Object1__c.Object2__c[‘myField’]
- Object1__c[‘Object2__c.myField’]
- Object1__c[‘Object2__c’][‘我的字段’]
- Object1__c.Relationship__r[myField]
- Object1__c[Relationship__r.myField]
- Object1__c[Relationship__r][myField]
对标准对象使用动态参照
使用动态 Visualforce 绑定 使用要访问的一组已知字段构建简单、可重用的页面。这 该方法的优点是可以轻松自定义哪些字段与用户相关 来工作。
接下来的两个示例出于教学目的而特意简单。请参阅使用动态引用 用户可自定义页面,以获取更高级的示例,以充分利用动态 Visualforce。
简单的动态表单
以下示例演示了构建 Visualforce 页面的最简单方法,该页面使用 动态引用。首先,创建一个控制器扩展,该扩展提供 字段设置为 显示:
public class DynamicAccountFieldsLister {
public DynamicAccountFieldsLister(ApexPages.StandardController controller) {
controller.addFields(editableFields);
}
public List<String> editableFields {
get {
if (editableFields == null) {
editableFields = new List<String>();
editableFields.add('Industry');
editableFields.add('AnnualRevenue');
editableFields.add('BillingCity');
}
return editableFields ;
}
private set;
}
}
接下来,创建一个使用上述控制器的页面 外延:
DynamicAccountEditor
<apex:page standardController="Account"
extensions="DynamicAccountFieldsLister">
<apex:pageMessages /><br/>
<apex:form>
<apex:pageBlock title="Edit Account" mode="edit">
<apex:pageBlockSection columns="1">
<apex:inputField value="{!Account.Name}"/>
<apex:repeat value="{!editableFields}" var="f">
<apex:inputField value="{!Account[f]}"/>
</apex:repeat>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
请注意此示例中发生的情况:
- 控制器扩展创建一个名为 的字符串列表。每个字符串映射到 Account 对象中的字段名称。DynamicAccountFieldsListereditableFields
- 列表是 硬编码,但您可以从查询或计算中确定它们,请阅读 它们来自自定义设置,或以其他方式提供更动态的 经验。这就是动态引用的强大之处。editableFields
- DynamicAccountEditor标记使用标记进行循环 通过 返回的字符串。<apex:repeat>editableFields
- 标签 通过引用 iteration 元素来显示每个字段,该元素表示 帐户上的字段名称。动态引用实际上显示 值。<apex:inputField>editableFieldsf{!Account[f]}
确保动态引用中的字段由标准加载 控制器
Visualforce 自动 优化了页面(或)执行的 SOQL 查询,仅加载实际 在页面上使用。当您创建具有静态 对对象和字段的引用,可以事先知道字段和对象。 保存页面后,Visualforce 能够 确定并保存需要将哪些对象和字段添加到 SOQL 查询中 稍后将执行, 请求页面时。StandardControllerStandardSetControllerStandardController
动态引用在运行时计算,在 SOQL 查询由 这。如果字段仅 通过动态引用使用,它不会自动加载。当那 动态引用稍后被评估,它将解析为缺失的数据, 其结果是 SOQL 错误。您必须向 控制器,以便它知道要加载哪些字段和相关对象。StandardController可以使用页面控制器上的方法传入 要加载的其他字段。在前面的示例中,这是在控制器中完成的 扩展的 构造 函数:
StandardControlleraddFields()
public DynamicAccountFieldsLister(ApexPages.StandardController controller) {
controller.addFields(editableFields);
}
这 构造函数使用与页面标记相同的属性 ,将更多字段添加到 控制器要加载的字段列表。
editableFields
这适用于以下情况下可以知道要加载的字段的完整列表的页面 控制器扩展被实例化。如果字段列表不能 确定直到稍后在请求处理中,您可以调用控制器,然后添加 领域。这将导致控制器发送修改后的查询。对 User-Customizable Page 提供了此技术的一个示例。reset()
注意
仅当使用 对 或 的缺省查询。如果你的 控制器或控制器扩展执行自己的 SOQL 查询,使用 是不必要的,并且没有 影响。StandardControllerStandardSetControlleraddFields()有关这些方法的详细信息,请参阅 StandardController 文档。
对相关内容的动态引用 对象
此示例为案例创建一个 Visualforce 页面 记录,其中包含某些可编辑的字段。显示的一些字段来自 一个相关对象,显示如何使用动态引用进行遍历 关系。首先,创建一个名为 Apex 控制器的扩展:
DynamicCaseLoader
public class DynamicCaseLoader {
public final Case caseDetails { get; private set; }
// SOQL query loads the case, with Case fields and related Contact fields
public DynamicCaseLoader(ApexPages.StandardController controller) {
String qid = ApexPages.currentPage().getParameters().get('id');
String theQuery = 'SELECT Id, ' + joinList(caseFieldList, ', ') +
' FROM Case WHERE Id = :qid';
this.caseDetails = Database.query(theQuery);
}
// A list of fields to show on the Visualforce page
public List<String> caseFieldList {
get {
if (caseFieldList == null) {
caseFieldList = new List<String>();
caseFieldList.add('CaseNumber');
caseFieldList.add('Origin');
caseFieldList.add('Status');
caseFieldList.add('Contact.Name'); // related field
caseFieldList.add('Contact.Email'); // related field
caseFieldList.add('Contact.Phone'); // related field
}
return caseFieldList;
}
private set;
}
// Join an Apex list of fields into a SELECT fields list string
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;
}
}
对应的页面, , 使用此扩展检索有关特定案例及其 相关 联系:
DynamicCaseEditor
<apex:page standardController="Case" extensions="DynamicCaseLoader">
<br/>
<apex:form >
<apex:repeat value="{!caseFieldList}" var="cf">
<h2>{!cf}</h2>
<br/>
<!-- The only editable information should be contact information -->
<apex:inputText value="{!caseDetails[cf]}"
rendered="{!IF(contains(cf, "Contact"), true, false)}"/>
<apex:outputText value="{!caseDetails[cf]}"
rendered="{!IF(contains(cf, "Contact"), false, true)}"/>
<br/><br/>
</apex:repeat>
</apex:form>
</apex:page>
访问 此页面将有效案例记录的 ID 指定为查询参数。例如。 您的页面将显示类似于以下内容的表单:
idhttps://Salesforce_instance/apex/DynamicCaseEditor?id=500D0000003ZtPy关于此示例,有许多注意事项:
- 在控制器扩展中,构造函数对 要显示的对象。这是因为页面无法加载 相关字段,但有许多不同的用例 需要自定义的 SOQL 查询。查询结果可供 页面浏览属性。无需执行 构造函数中的查询 – 它可以很容易地位于 属性的方法。StandardControllercaseFieldListget
- SOQL 查询指定要加载的字段,因此没有必要 使用在简单动态表单中需要的。addFields()
- SOQL 查询是在运行时构造的。实用程序方法将 将字段名称列表转换为适合在 SOQL 语句中使用的字符串。SELECT
- 在标记中,通过循环访问字段来显示表单字段 使用 和 的名称 在 动态引用以获取字段值。每个字段都可能被写入 由两个组件组成,并且 .这些标记的 render 属性 控制两者中的哪一个实际显示:如果字段名称包含 string “Contact”,则信息将呈现在标签中,如果 它不是,它是在 .<apex:repeat>cf<apex:outputText><apex:inputText><apex:inputText><apex:outputText>
对 用户可自定义的页面
Visualforce 动态绑定的全部潜力 在构建页面时不知道对象上哪些字段可用。这 以下示例演示了此功能,其中包含可以 在不知道 Account 对象上的任何字段的情况下进行自定义,但 对于所有对象都需要的“名称”字段。这是制作的 可以通过使用 来检索 对象上存在的字段和 Visualforce 动态 引用。Schema.SobjectType.Account.fields.getMap()此示例提供的功能很简单。主列表视图最初 仅显示帐户名称,但显示“自定义列表”按钮 允许用户选择要添加到列表中的字段。什么时候 他们保存他们的首选项,他们返回列表视图,并将动态地看到一个 生成的 Visualforce 页面 在附加列中显示这些字段。
注意
您还可以构建页面 不知道使用动态的字段 带有字段集的引用。首先,创建一个名为 :
DynamicCustomizableListHandler
public class DynamicCustomizableListHandler {
// Resources we need to hold on to across requests
private ApexPages.StandardSetController controller;
private PageReference savePage;
// This is the state for the list "app"
private Set<String> unSelectedNames = new Set<String>();
private Set<String> selectedNames = new Set<String>();
private Set<String> inaccessibleNames = new Set<String>();
public DynamicCustomizableListHandler(ApexPages.StandardSetController controller) {
this.controller = controller;
loadFieldsWithVisibility();
}
// Initial load of the fields lists
private void loadFieldsWithVisibility() {
Map<String, Schema.SobjectField> fields =
Schema.SobjectType.Account.fields.getMap();
for (String s : fields.keySet()) {
if (s != 'Name') { // name is always displayed
unSelectedNames.add(s);
}
if (!fields.get(s).getDescribe().isAccessible()) {
inaccessibleNames.add(s);
}
}
}
// The fields to show in the list
// This is what we generate the dynamic references from
public List<String> getDisplayFields() {
List<String> displayFields = new List<String>(selectedNames);
displayFields.sort();
return displayFields;
}
// Nav: go to customize screen
public PageReference customize() {
savePage = ApexPages.currentPage();
return Page.CustomizeDynamicList;
}
// Nav: return to list view
public PageReference show() {
// This forces a re-query with the new fields list
controller.reset();
controller.addFields(getDisplayFields());
return savePage;
}
// Create the select options for the two select lists on the page
public List<SelectOption> getSelectedOptions() {
return selectOptionsFromSet(selectedNames);
}
public List<SelectOption> getUnSelectedOptions() {
return selectOptionsFromSet(unSelectedNames);
}
private List<SelectOption> selectOptionsFromSet(Set<String> opts) {
List<String> optionsList = new List<String>(opts);
optionsList.sort();
List<SelectOption> options = new List<SelectOption>();
for (String s : optionsList) {
options.add(new
SelectOption(s, decorateName(s), inaccessibleNames.contains(s)));
}
return options;
}
private String decorateName(String s) {
return inaccessibleNames.contains(s) ? '*' + s : s;
}
// These properties receive the customization form postback data
// Each time the [<<] or [>>] button is clicked, these get the contents
// of the respective selection lists from the form
public transient List<String> selected { get; set; }
public transient List<String> unselected { get; set; }
// Handle the actual button clicks. Page gets updated via a
// rerender on the form
public void doAdd() {
moveFields(selected, selectedNames, unSelectedNames);
}
public void doRemove() {
moveFields(unselected, unSelectedNames, selectedNames);
}
private void moveFields(List<String> items,
Set<String> moveTo, Set<String> removeFrom) {
for (String s: items) {
if( ! inaccessibleNames.contains(s)) {
moveTo.add(s);
removeFrom.remove(s);
}
}
}
}
注意
什么时候 保存课程时,系统可能会提示您缺少 Visualforce 页面。这是 因为方法中的页面引用。单击“快速修复”链接 创建页面 – 来自 稍后的代码块将被粘贴到其中。customize()关于这个班级的一些注意事项:
- 标准控制器方法 和 在方法中使用,该方法是返回到 列表视图。它们是必需的,因为要显示的字段列表可能具有 已更改,因此加载数据以供显示的查询需要 重新执行。addFields()reset()show()
- 两种操作方法,以及 ,从列表中导航 查看自定义表单,然后再查看回来。customize()show()
- 导航操作方法之后的所有内容都与自定义有关 形式。这些方法大致分为两组,其中指出 评论。第一组提供自定义表单使用的列表, 第二组处理从一个列表中移动项目的两个按钮 到另一个。List<SelectOption>
现在,创建一个 Visualforce 页面 使用 以后 标记:
DynamicCustomizableList
<apex:page standardController="Account" recordSetVar="accountList"
extensions="DynamicCustomizableListHandler">
<br/>
<apex:form >
<!-- View selection widget, uses StandardController methods -->
<apex:pageBlock>
<apex:outputLabel value="Select Accounts View: " for="viewsList"/>
<apex:selectList id="viewsList" size="1" value="{!filterId}">
<apex:actionSupport event="onchange" rerender="theTable"/>
<apex:selectOptions value="{!listViewOptions}"/>
</apex:selectList>
</apex:pageblock>
<!-- This list of accounts has customizable columns -->
<apex:pageBlock title="Accounts" mode="edit">
<apex:pageMessages />
<apex:panelGroup id="theTable">
<apex:pageBlockTable value="{!accountList}" var="acct">
<apex:column value="{!acct.Name}"/>
<!-- This is the dynamic reference part -->
<apex:repeat value="{!displayFields}" var="f">
<apex:column value="{!acct[f]}"/>
</apex:repeat>
</apex:pageBlockTable>
</apex:panelGroup>
</apex:pageBlock>
<br/>
<apex:commandButton value="Customize List" action="{!customize}"/>
</apex:form>
</apex:page>
这 页面显示组织中的帐户列表。顶部的 提供 为帐户定义的视图的标准下拉列表,与用户看到的视图相同 在标准 Salesforce 上 帐户页面。此视图小组件使用 .
<apex:pageBlock>StandardSetController
第二个持有一个具有 在 .都 重复组件中的列使用对帐户字段的动态引用,以显示用户的 自定义选择的字段。<apex:pageBlock><apex:pageBlockTable><apex:repeat>{!acct[f]}这个迷你应用程序的最后一部分是自定义表单。创建一个名为 的页面。您可能有 在创建控制器扩展时,已创建此页面。粘贴到 以后:
CustomizeDynamicList
<apex:page standardController="Account" recordSetVar="ignored"
extensions="DynamicCustomizableListHandler">
<br/>
<apex:form >
<apex:pageBlock title="Select Fields to Display" id="selectionBlock">
<apex:pageMessages />
<apex:panelGrid columns="3">
<apex:selectList id="unselected_list" required="false"
value="{!selected}" multiselect="true" size="20" style="width:250px">
<apex:selectOptions value="{!unSelectedOptions}"/>
</apex:selectList>
<apex:panelGroup >
<apex:commandButton value=">>"
action="{!doAdd}" rerender="selectionBlock"/>
<br/>
<apex:commandButton value="<<"
action="{!doRemove}" rerender="selectionBlock"/>
</apex:panelGroup>
<apex:selectList id="selected_list" required="false"
value="{!unselected}" multiselect="true" size="20" style="width:250px">
<apex:selectOptions value="{!selectedOptions}"/>
</apex:selectList>
</apex:panelGrid>
<em>Note: Fields marked <strong>*</strong> are inaccessible to your account</em>
</apex:pageBlock>
<br/>
<apex:commandButton value="Show These Fields" action="{!show}"/>
</apex:form>
</apex:page>
这 “简单首选项”页面显示两个列表,用户从列表中移动字段 左侧的可用字段到要显示在右侧的字段列表。 单击“显示这些字段”将返回到列表本身。以下是有关此标记的一些注意事项:
- 此页面使用与列表视图相同的标准控制器,即使没有 正在显示帐户。这是维护视图状态所必需的。 其中包含要显示的字段列表。如果此表单保存了 用户对永久内容(如自定义设置)的偏好, 这没有必要。
- 第一个列表由对方法的调用填充,并在提交表单时填充 (通过两个组件中的任何一个),在提交表单时选择的列表中的值将保存到属性中。相应 代码处理其他列表。getUnSelectedOptions()<apex:commandButton>selected
- 这些要移动的字段的“增量”列表由 or 方法处理,具体取决于哪个 按钮。doAdd()doRemove()
当您组装控制器扩展和这些页面,并导航到组织中的 /apex/DynamicCustomizableList 时, 您将看到类似于以下内容的序列:
- 在默认状态下查看可自定义列表,仅包含帐户名称 显示字段。
单击自定义列表。
- 显示显示首选项屏幕。
移动 某些字段添加到右侧的列表中,然后单击“显示这些字段” 字段。
- 将显示自定义列表视图。
将动态引用与自定义对象一起使用,以及 包
包开发人员可以使用动态 Visualforce 绑定仅列出 用户可以访问的字段。在开发托管 软件包中包含 Visualforce 页面,该页面具有 显示对象上的字段。由于包开发人员不知道哪些字段 订阅者可以访问,他们可以定义一个动态页面,该页面为每个页面呈现不同的内容 订户。
以下示例使用一个自定义对象,该对象与使用 Visualforce 页面的页面布局打包在一起,以演示 不同的订阅用户如何查看同一页面。
- 使用以下字段和数据类型创建自定义对象(API 名称):BookBook__c
- 标题: Text(255)
- 作者: 文本(255)
- ISBN: 文本(20)
- 价格: 货币(5, 2)
- 出版商: Text(255)
- 编辑“书籍”页面布局,使其首先显示自定义字段,并删除一些自定义字段 标准字段,例如“创建者”、“上次修改者”、“所有者”和“名称”。
- 创建新的自定义对象选项卡。将对象设置为“Book”,将选项卡样式设置为 书。
- 切换到 Book 选项卡并创建几个 Book 对象。值无关紧要,但 您确实需要一些记录才能实际存在。
- 创建使用以下命令调用的控制器扩展 法典:BookExtension
public with sharing class BookExtension { private ApexPages.StandardController stdController; public BookExtension (ApexPages.StandardController ct) { this.stdController = ct; if( ! Test.isRunningTest()) { // You can't call addFields() in a test context, it's a bug stdController.addFields(accessibleFields); } } public List<String> accessibleFields { get { if (accessibleFields == null) { // Get a list (map) of all fields on the object Map<String, Schema.SobjectField> fields = Schema.SobjectType.Book__c.fields.getMap(); // Save only the fields accessible by the current user Set<String> availableFieldsSet = new Set<String>(); for (String s : fields.keySet()) { if (fields.get(s).getDescribe().isAccessible() // Comment out next line to show standard/system fields && fields.get(s).getDescribe().isCustom() ){ availableFieldsSet.add(s.toLowerCase()); if(Test.isRunningTest()) System.debug('Field: ' + s); } } // Convert set to list, save to property accessibleFields = new List<String>(availableFieldsSet); } return accessibleFields; } private set; } }
- 创建 Visualforce 页面 使用控制器的调用 扩展以显示 Book 的值 对象:booksView
<apex:page standardController="Book__c" extensions="BookExtension" > <apex:pageBlock title="{!Book__c.Name}"> <apex:pageBlockSection > <apex:repeat value="{!accessibleFields}" var="f"> <apex:pageBlockSectionItem > <apex:outputLabel value="{!$ObjectType['Book__c'].Fields[f].Label}"/> <apex:outputText value="{!Book__c[f]}"/> </apex:pageBlockSectionItem> </apex:repeat> </apex:pageBlockSection> </apex:pageBlock> </apex:page>
- 由于控制器扩展将被打包,因此需要为 Apex 类创建测试。 创建一个使用此基本代码调用的 Apex 类,以获取 开始:BookExtensionTest
@isTest public class BookExtensionTest { public static testMethod void testBookExtension() { // Create a book to test with Book__c book = new Book__c(); book.Author__c = 'Harry Lime'; insert book; Test.startTest(); // Add the page to the test context PageReference testPage = Page.booksView; testPage.getParameters().put('id', String.valueOf(book.Id)); Test.setCurrentPage(testPage); // Create a controller for the book ApexPages.StandardController sc = new ApexPages.StandardController(book); // Real start of testing BookExtension // BookExtension has only two methods; to get 100% code coverage, we need // to call the constructor and get the accessibleFields property // Create an extension with the controller BookExtension bookExt = new BookExtension(sc); // Get the list of accessible fields from the extension Set<String> fields = new Set<String>(bookExt.accessibleFields); // Test that accessibleFields is not empty System.assert( ! fields.isEmpty()); // Test that accessibleFields includes Author__c // This is a bad test; you can't know that subscriber won't disable System.assert(fields.contains('Author__c'.toLowerCase()), 'Expected accessibleFields to include Author__c'); Test.stopTest(); } }
注意此 Apex 测试只是一个示例。什么时候 创建包含在包中的测试,验证所有行为,包括 阳性和阴性结果。 - 创建一个名为 的包,并添加自定义 对象,即 Visualforce 页面, 和 Apex 类。其他 引用的元素(例如页面的控制器扩展 Apex 类)是 自动包含。bookBundlebookExtensionTest
- 将软件包安装到 订阅者组织。bookBundle
- 安装软件包后,从书籍的对象管理设置中,添加 名为 Rating 的新字段。
- 创建新的 Book 对象。同样,记录的值实际上并不 事。
- 导航到带有 附加到 URL 的包命名空间和书籍 ID。例如,if 是命名空间,a00D0000008e7t4 是书籍 ID, 生成的 URL 应为 .booksViewGBOOKhttps://Salesforce_instance/apex/GBOOK__booksView?id=a00D0000008e7t4
从订阅组织查看页面时,它应包含所有 打包的“图书”字段,以及新创建的“评级”字段。不同的用户和 组织可以继续添加他们想要的任何字段,动态 Visualforce 页面将进行调整和 根据需要显示。
引用 Apex 地图和列表
使用动态的 Visualforce 页面 绑定可以在其标记中引用 Apex 和数据类型。MapList例如,如果 Apex 定义为 遵循:
List
public List<String> people {
get {
return new List<String>{'Winston', 'Julia', 'Brien'};
}
set;
}
public List<Integer> iter {
get {
return new List<Integer>{0, 1, 2};
}
set;
}
它 可以在 Visualforce 中访问 页面喜欢 这:
<apex:repeat value="{!iter}" var="pos">
<apex:outputText value="{!people[pos]}" /><br/>
</apex:repeat>
同样,如果您有以下 Apex :
Map
public Map<String,String> directors {
get {
return new Map<String, String> {
'Kieslowski' => 'Poland',
'del Toro' => 'Mexico',
'Gondry' => 'France'
};
}
set;
}
你 Visualforce 页面可以显示 像这样的值 这:
<apex:repeat value="{!directors}" var="dirKey">
<apex:outputText value="{!dirKey}" /> --
<apex:outputText value="{!directors[dirKey]}" /><br/>
</apex:repeat>
使用对标记中的列表和映射的动态引用来创建表单 使用不在组织的自定义对象中的数据。使用 单个映射可能比在 Apex 中创建一系列实例变量要简单得多 控制器或仅为表单数据创建自定义对象。<apex:inputText>下面是一个使用地图的 Visualforce 页面 保存表单数据以供自定义处理 控制器:
<apex:page controller="ListsMapsController">
<apex:outputPanel id="box" layout="block">
<apex:pageMessages/>
<apex:form >
<apex:repeat value="{!inputFields}" var="fieldKey">
<apex:outputText value="{!fieldKey}"/>:
<apex:inputText value="{!inputFields[fieldKey]}"/><br/>
</apex:repeat>
<apex:commandButton action="{!submitFieldData}"
value="Submit" id="button" rerender="box"/>
</apex:form>
</apex:outputPanel>
</apex:page>
这里有一个简单的控制器, 适用于以下形式:
public class ListsMapsController {
public Map<String, String> inputFields { get; set; }
public ListsMapsController() {
inputFields = new Map<String, String> {
'firstName' => 'Jonny', 'lastName' => 'Appleseed', 'age' => '42' };
}
public PageReference submitFieldData() {
doSomethingInterestingWithInput();
return null;
}
public void doSomethingInterestingWithInput() {
inputFields.put('age', (Integer.valueOf(inputFields.get('age')) + 10).format());
}
}
A 可以包含对 sObjects 或 sObject 字段。要更新这些项目,请在输入字段中引用字段名称:
Map
public with sharing class MapAccCont {
Map<Integer, Account> mapToAccount = new Map<Integer, Account>();
public MapAccCont() {
Integer i = 0;
for (Account a : [SELECT Id, Name FROM Account LIMIT 10]) {
mapToAccount.put(i, a);
i++;
}
}
public Map<Integer, Account> getMapToAccount() {
return mapToAccount;
}
}
<apex:page controller="MapAccCont">
<apex:form>
<apex:repeat value="{!mapToAccount}" var="accNum">
<apex:inputField value="{!mapToAccount[accNum].Name}" />
</apex:repeat>
</apex:form>
</apex:page>
未解析的动态引用
请记住,如果动态引用,则在运行时可能会出现以下问题 无法解决:
- 如果没有映射到特定键的值,则 Visualforce 页面 返回错误消息。例如,有了这个 控制器:
public class ToolController { public Map<String, String> toolMap { get; set; } public String myKey { get; set; } public ToolController() { Map<String, String> toolsMap = new Map<String, String>(); toolsMap.put('Stapler', 'Keeps things organized'); } }
这 页面在运行时导致错误 时间:<apex:page controller="ToolController"> <!-- This renders an error on the page --> <apex:outputText value="{!toolMap['Paperclip']}" /> </apex:page>
- 如果键为 ,则 Visualforce 页面 呈现一个空字符串。例如,使用与上述相同的控制器, 此页面显示一个空的 空间:null
<apex:page controller="ToolController"> <!-- This renders a blank space --> <apex:outputText value="{!toolMap[null]}" /> </apex:page>
使用字段集
您可以使用动态绑定在 Visualforce 上显示字段集 页面。字段集是一组字段。例如,您可以设置一个字段 包含描述用户名字、中间名、姓氏和业务的字段 标题。如果将页面添加到托管包中,管理员可以添加、删除或 对字段集中的字段重新排序,以修改 Visualforce 页面上显示的字段 无需修改任何代码。字段集可用于 API 上的 Visualforce 页面 版本 21.0 或更高版本。您最多可以拥有50田 在单个页面上引用的集。一个 sObject 最多可以有2,000字段集。
注意
每个字段集最多可以有25字段通过 查找关系。字段只能跨越距离实体一个级别。
使用 Visualforce 处理字段集
字段集可以通过组合在 Visualforce 中直接引用 全局变量,其中 关键词。例如,如果您的 Contact 对象具有一个名为 properNames 的字段集,该字段集显示 三个字段,您的 Visualforce 页面可以通过 以后 迭 代:
$ObjectTypeFieldSets
<apex:page standardController="Contact">
<apex:repeat value="{!$ObjectType.Contact.FieldSets.properNames}" var="f">
<apex:outputText value="{!Contact[f]}" /><br/>
</apex:repeat>
</apex:page>
您还可以选择呈现其他信息,例如字段标签和数据 类型,通过字段中字段的以下特殊属性 设置:
属性名称 | 描述 |
---|---|
DBRequired | 指示该字段是否为 对象 |
FieldPath | 列出字段的跨区信息 |
Label | 字段的 UI 标签 |
Required | 指示字段中是否为必填字段 设置 |
Type | 字段的数据类型 |
例如,您可以访问 properNames 中字段的标签和数据类型,例如 这:
<apex:page standardController="Contact">
<apex:pageBlock title="Fields in Proper Names">
<apex:pageBlockTable value="{!$ObjectType.Contact.FieldSets.properNames}" var="f">
<apex:column value="{!f}">
<apex:facet name="header">Name</apex:facet>
</apex:column>
<apex:column value="{!f.Label}">
<apex:facet name="header">Label</apex:facet>
</apex:column>
<apex:column value="{!f.Type}" >
<apex:facet name="header">Data Type</apex:facet>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>
如果将此 Visualforce 页面添加到托管软件包并分发,则订阅者 可以编辑 properNames 字段集。生成逻辑 Visualforce 页面保持不变,但演示文稿因每个页面而异 订阅者的实现。若要引用托管包中的字段集, 您必须在字段集前面加上组织的命名空间。使用 标记,如果 properNames 来自一个名为 Spectre,字段集的引用方式如下 这:
{!$ObjectType.Contact.FieldSets.Spectre__properNames}
使用 Apex 处理字段集
当您的 Visualforce 页面使用 标准控制器。使用自定义控制器时,需要添加所需的 字段添加到页面的 SOQL 查询中。Apex 提供了两个 Schema 对象,允许 您可以发现字段集及其包含的字段,以及 .有关的信息 这两个系统类,请参阅 Lightning 平台 Apex 代码中的“FieldSet 类” 开发人员指南。Schema.FieldSetSchema.FieldSetMember
示例:在 Visualforce 上显示字段集 页此示例使用和方法动态获取 为 Merchandise 自定义对象设置的 Dimensions 字段中的所有字段。这 然后,使用字段列表来构造 SOQL 查询,以确保这些字段 可供展示。Visualforce 页面使用 作为其的类 控制器。
Schema.FieldSetSchema.FieldSetMemberMerchandiseDetails
public class MerchandiseDetails {
public Merchandise__c merch { get; set; }
public MerchandiseDetails() {
this.merch = getMerchandise();
}
public List<Schema.FieldSetMember> getFields() {
return SObjectType.Merchandise__c.FieldSets.Dimensions.getFields();
}
private Merchandise__c getMerchandise() {
String query = 'SELECT ';
for(Schema.FieldSetMember f : this.getFields()) {
query += f.getFieldPath() + ', ';
}
query += 'Id, Name FROM Merchandise__c LIMIT 1';
return Database.query(query);
}
}
Visualforce 页面使用 上述控制器是 简单:
<apex:page controller="MerchandiseDetails">
<apex:form >
<apex:pageBlock title="Product Details">
<apex:pageBlockSection title="Product">
<apex:inputField value="{!merch.Name}"/>
</apex:pageBlockSection>
<apex:pageBlockSection title="Dimensions">
<apex:repeat value="{!fields}" var="f">
<apex:inputField value="{!merch[f.fieldPath]}"
required="{!OR(f.required, f.dbrequired)}"/>
</apex:repeat>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
一 关于上述标记需要注意的是用于确定字段是否的表达式 在表格上应注明为必填字段。字段集中的字段 字段集定义或字段自己的定义可以是必需的 定义。表达式处理这两种情况。
字段集注意事项
添加到字段集的字段可以属于以下两个类别之一:
- 如果某个字段被标记为“可用于字段集”,则该字段存在于 字段集,但开发人员尚未在打包的 Visualforce 页面上显示它。 管理员可以在部署字段集后显示字段,方法是将字段从“可用”列移动到“在字段集中”列。
- 如果字段标记为“在字段集中”,则开发人员已呈现 默认情况下,打包的 Visualforce 页面上的字段。管理员可以从中删除该字段 通过从字段中删除字段集后部署该页面 设置列。
开发人员列出显示字段的顺序决定了它们的顺序 出现在 Visualforce 页面上。作为包开发人员,请牢记以下最佳实践:
- 安装了字段集的订阅者可以添加您的页面的字段 没有考虑。没有办法有条件地省略某些字段 从字段集迭代,因此请确保任何字段都通过 字段集适用于所有字段类型。
- 我们建议您仅将非必要字段添加到字段集中。这 确保即使订阅者删除了字段集中的所有字段, 使用该字段集的 Visualforce 页面仍然有效。
注意
字段集可用于 API 版本 21.0 或 以上。
对全局变量的动态引用
Visualforce 页面可以使用动态 绑定以在其标记中引用全局变量。全局变量允许您访问 有关当前用户、组织的信息以及有关数据的架构详细信息。这 全局变量列表可在全局变量、函数和表达式中找到 运算符附录。引用全局变量与引用 sObjects 和 Apex 类相同,即 使用相同的基本模式,其中 是全局 变量:
reference
reference[expression]
对静态资源的动态引用 使用 $Resource
对静态资源的动态引用可能非常有用 用于为主题或其他视觉首选项提供支持。
要使用全局变量引用静态资源,请提供 表达式中静态资源的名称:。例如,如果您有一个返回 作为静态资源上传的图像的名称,引用它 这:。$Resource{! $Resource[StaticResourceName] }<apex:image value=”{!$Resource[customLogo]}”/>此示例演示如何在两个不同的视觉对象之间切换 主题。首先,创建一个使用以下代码命名的控制器扩展:
ThemeHandler
public class ThemeHandler {
public ThemeHandler(ApexPages.StandardController controller) { }
public static Set<String> getAvailableThemes() {
// You must have at least one uploaded static resource
// or this code will fail. List their names here.
return(new Set<String> {'Theme_Color', 'Theme_BW'});
}
public static List<SelectOption> getThemeOptions() {
List<SelectOption> themeOptions = new List<SelectOption>();
for(String themeName : getAvailableThemes()) {
themeOptions.add(new SelectOption(themeName, themeName));
}
return themeOptions;
}
public String selectedTheme {
get {
if(null == selectedTheme) {
// Ensure we always have a theme
List<String> themeList = new List<String>();
themeList.addAll(getAvailableThemes());
selectedTheme = themeList[0];
}
return selectedTheme;
}
set {
if(getAvailableThemes().contains(value)) {
selectedTheme = value;
}
}
}
}
关于这个班级的注意事项:
- 它有一个空的构造函数,因为没有默认值 控制器扩展的构造函数。
- 将上传的静态资源文件主题的名称添加到方法中。使用静态资源提供了有关如何创建和上传静态资源的详细信息,特别是: 包含多个文件的压缩存档。getAvailableThemes
- 最后两种方法提供主题列表和选定的 主题,用于 Visualforce 表单组件。
现在创建一个使用此控制器扩展的 Visualforce 页面:
<apex:page standardController="Account"
extensions="ThemeHandler" showHeader="false">
<apex:form >
<apex:pageBlock id="ThemePreview" >
<apex:stylesheet
value="{!URLFOR($Resource[selectedTheme], 'styles/styles.css')}"/>
<h1>Theme Viewer</h1>
<p>You can select a theme to use while browsing this site.</p>
<apex:pageBlockSection >
<apex:outputLabel value="Select Theme: " for="themesList"/>
<apex:selectList id="themesList" size="1" value="{!selectedTheme}">
<apex:actionSupport event="onchange" rerender="ThemePreview"/>
<apex:selectOptions value="{!themeOptions}"/>
</apex:selectList>
</apex:pageBlockSection>
<apex:pageBlockSection >
<div class="custom" style="padding: 1em;"><!-- Theme CSS hook -->
<h2>This is a Sub-Heading</h2>
<p>This is standard body copy. Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Quisque neque arcu, pellentesque in vehicula vitae, dictum
id dolor. Cras viverra consequat neque eu gravida. Morbi hendrerit lobortis
mauris, id sollicitudin dui rhoncus nec.</p>
<p><apex:image
value="{!URLFOR($Resource[selectedTheme], 'images/logo.png')}"/></p>
</div><!-- End of theme CSS hook -->
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
请注意有关此标记的以下事项:
- 该页面使用帐户标准控制器,但没有任何内容 与帐户有关。您必须指定控制器才能使用控制器 外延。
- 第一个包含主题选择小部件。使用 ,更改为 “选择”菜单将重新渲染整个 .这样标签就会得到 更新了 它的动态参考。<apex:pageBlockSection><apex:actionSupport><apex:pageBlock><apex:stylesheet>selectedTheme
- 此处选择的主题首选项仅保留在视图中 状态,但您可以轻松地将其保存到自定义状态 设置,并使其永久化。
- 包含每个图形和样式资源的 zip 文件 主题需要具有一致的结构和内容。那是。那里 需要是每个主题中的图像/徽标.png zip 文件等。
此全局变量只有两个动态引用 页面,但它们显示了如何访问样式表和图形资源。 您可以在页面上的每个标记中使用动态引用,并且 完全改变外观和感觉。$Resource<apex:image>
$Label并且类似于 ,因为它们允许您 访问组织管理员的文本值或保存的设置 或者用户自己可以在 Salesforce 中设置:
$Setup$Resource
- 自定义标签允许您创建可以在整个过程中一致使用的文本消息 应用。标签文本也可以翻译并自动显示在 用户的默认语言。
- 自定义设置允许您为应用程序创建设置,这些设置可以通过 管理员或用户自己。它们也可以是分层的,因此 用户级设置将覆盖角色级或组织级设置。
使用 $Action 对操作方法的动态引用
全局变量允许您 动态引用对象类型或特定记录上的有效操作。最 利用此功能的可能方法是创建一个 URL 来执行该操作。
$Action
例如,可以将表达式与提供 s对象。{!URLFOR($Action[objectName].New)}<apex:outputLink>getObjectName()下面是一个完全做到这一点的示例。控制器扩展查询 系统来学习用户可访问的所有自定义对象的名称,并呈现 它们的列表,以及用于创建新记录的链接。首先,创建一个控制器 名为 :
DynamicActionsHandler
public with sharing class DynamicActionsHandler {
public List<CustomObjectDetails> customObjectDetails { get; private set; }
public DynamicActionsHandler(ApexPages.StandardController cont) {
this.loadCustomObjects();
}
public void loadCustomObjects() {
List<CustomObjectDetails> cObjects = new List<CustomObjectDetails>();
// Schema.getGlobalDescribe() returns lightweight tokens with minimal metadata
Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
for(String obj : gd.keySet()) {
if(obj.endsWith('__c')) {
// Get the full metadata details only for custom items
Schema.DescribeSObjectResult objD = gd.get(obj).getDescribe();
if( ! objD.isCustomSetting()) {
// Save details for custom objects, not custom settings
CustomObjectDetails objDetails = new CustomObjectDetails(
obj, objD.getLabel(), objD.isCreateable());
cObjects.add(objDetails);
}
}
}
cObjects.sort();
this.customObjectDetails = cObjects;
}
public class CustomObjectDetails implements Comparable {
public String nameStr { get; set; }
public String labelStr { get; set; }
public Boolean creatable { get; set; }
public CustomObjectDetails(String aName, String aLabel, Boolean isCreatable) {
this.nameStr = aName;
this.labelStr = aLabel;
this.creatable = isCreatable;
}
public Integer compareTo(Object objToCompare) {
CustomObjectDetails cod = (CustomObjectDetails)objToCompare;
return(this.nameStr.compareTo(cod.nameStr));
}
}
}
那里 是此扩展中感兴趣的一些内容:
- 该方法使用 Apex 用于获取有关可用自定义对象的元数据信息的架构方法。该方法是 轻量级操作,用于获取有关可用对象的一小组元数据,以及 自定义设置。该方法扫描集合以查找具有名称的项 以“__c”结尾,表示它们是自定义对象或 设置。使用 更深入地检查这些项目,并保存选定的元数据 用于自定义对象。loadCustomObjectsSchema.getGlobalDescribegetDescribe
- 用于测试 无论一个项目是否是自定义对象,都可能感觉像是“黑客”, 但另一种选择是打电话,这很贵,而且有一个 调控器对 的调用次数的限制。扫描“__c”字符串作为 首先传递可能很长的对象列表会更有效率。if(obj.endsWith(‘__c’))obj.getDescribe().isCustom()getDescribe
- 此元数据保存在内部类中,该类充当简单的结构化 要保存的字段的容器。CustomObjectDetails
- CustomObjectDetails实现 可比较的界面,可以对自定义对象列表进行排序 每个对象的属性的详细信息,在本例中,自定义对象的 名字。
现在创建一个 Visualforce 页面 以下 标记:
<apex:page standardController="Account"
extensions="DynamicActionsHandler">
<br/>
<apex:dataTable value="{!customObjectDetails}" var="coDetails">
<apex:column >
<apex:facet name="header">Custom Object</apex:facet>
<apex:outputText value="{!coDetails.labelStr}"/>
</apex:column>
<apex:column >
<apex:facet name="header">Actions</apex:facet>
<apex:outputLink value="{!URLFOR($Action[coDetails.nameStr].New)}"
rendered="{!coDetails.creatable}">[Create]</apex:outputLink><br/>
<apex:outputLink value="{!URLFOR($Action[coDetails.nameStr].List,
$ObjectType[coDetails.nameStr].keyPrefix)}">[List]</apex:outputLink>
</apex:column>
</apex:dataTable>
</apex:page>
上 尚未分配特定记录的页面,仅有的两个有用操作 可用的是 New 和 List。在页面上 查询记录,全局 变量提供 View、Clone、Edit 和 Delete 等方法。某些标准对象 具有对其数据类型有意义的其他操作。
$Action
使用 $ObjectType 动态引用架构详细信息
全局变量提供对各种架构信息的访问 关于组织中的对象。用它来引用名称, 例如,对象上的标签和字段的数据类型。
$ObjectType
$ObjectType是一个“深” 全局变量,并提供在“双 动态“参考,如下所示:
$ObjectType[sObjectName].fields[fieldName].Type
下面是一个示例,它使用动态全局变量来提供 常规对象查看器。首先,创建一个新的控制器(不是扩展) 叫:
DynamicObjectHandler
public class DynamicObjectHandler {
// This class acts as a controller for the DynamicObjectViewer component
private String objType;
private List<String> accessibleFields;
public sObject obj {
get;
set {
setObjectType(value);
discoverAccessibleFields(value);
obj = reloadObjectWithAllFieldData();
}
}
// The sObject type as a string
public String getObjectType() {
return(this.objType);
}
public String setObjectType(sObject newObj) {
this.objType = newObj.getSObjectType().getDescribe().getName();
return(this.objType);
}
// List of accessible fields on the sObject
public List<String> getAccessibleFields() {
return(this.accessibleFields);
}
private void discoverAccessibleFields(sObject newObj) {
this.accessibleFields = new List<String>();
Map<String, Schema.SobjectField> fields =
newObj.getSObjectType().getDescribe().fields.getMap();
for (String s : fields.keySet()) {
if ((s != 'Name') && (fields.get(s).getDescribe().isAccessible())) {
this.accessibleFields.add(s);
}
}
}
private sObject reloadObjectWithAllFieldData() {
String qid = ApexPages.currentPage().getParameters().get('id');
String theQuery = 'SELECT ' + joinList(getAccessibleFields(), ', ') +
' FROM ' + getObjectType() +
' WHERE Id = :qid';
return(Database.query(theQuery));
}
// Join an Apex List of fields into a SELECT fields list string
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;
}
}
有许多值得注意的事情 在此控制器中:
- Visualforce 组件不能使用控制器扩展,因此这 类被写成控制器。没有构造函数 定义,因此该类使用默认构造函数。
- 要收集对象的元数据,控制器必须知道 对象。Visualforce 构造函数不能接受参数,因此无法知道 实例化时感兴趣的对象是什么。相反 元数据发现是由 public 属性的设置触发的。obj
- 此类中的多个方法使用系统架构发现 方法,与前面的例子略有不同。
下一部分是 Visualforce 组件,它也显示有关对象的架构信息 作为查询记录的特定值。创建一个新的 Visualforce 组件,该组件使用以下代码命名:
DynamicObjectViewer
<apex:component controller="DynamicObjectHandler">
<apex:attribute name="rec" type="sObject" required="true"
description="The object to be displayed." assignTo="{!obj}"/>
<apex:form >
<apex:pageBlock title="{!objectType}">
<apex:pageBlockSection title="Fields" columns="1">
<apex:dataTable value="{!accessibleFields}" var="f">
<apex:column >
<apex:facet name="header">Label</apex:facet>
<apex:outputText value="{!$ObjectType[objectType].fields[f].Label}"/>
</apex:column>
<apex:column >
<apex:facet name="header">API Name</apex:facet>
<apex:outputText value="{!$ObjectType[objectType].fields[f].Name}"/>
</apex:column>
<apex:column >
<apex:facet name="header">Type</apex:facet>
<apex:outputText value="{!$ObjectType[objectType].fields[f].Type}"/>
</apex:column>
<apex:column >
<apex:facet name="header">Value</apex:facet>
<apex:outputText value="{!obj[f]}"/>
</apex:column>
</apex:dataTable>
</apex:pageBlockSection>
<apex:pageBlockSection columns="4">
<apex:commandButton value="View"
action="{!URLFOR($Action[objectType].View, obj.Id)}"/>
<apex:commandButton value="Edit"
action="{!URLFOR($Action[objectType].Edit, obj.Id)}"/>
<apex:commandButton value="Clone"
action="{!URLFOR($Action[objectType].Clone, obj.Id)}"/>
<apex:commandButton value="Delete"
action="{!URLFOR($Action[objectType].Delete, obj.Id)}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:component>
请注意以下几点:
- 使用此组件的任何页面都必须查找记录。去做 因此,请对该对象使用标准控制器,并在 URL 中指定记录的 ID。例如,https://<Salesforce_instance>/apex/DynamicContactPage?id=003D000000Q5GHE。
- 所选记录将立即传递到组件的 obj 属性中。此参数用于所有对象 元数据发现。
- 三个双动态引用,以 开头, 显示每个字段的元数据,而正常动态引用 显示字段的实际值。$ObjectType[objectType].fields[f]
- 对于数据值,该值是 ,使用控制器中的 getter 方法,而不是 也许更自然,这是组件的参数。原因很简单, obj 属性已更新为加载数据 对于所有字段,而 REC 仍然存在 与标准控制器加载的内容相同,因此 仅加载了 Id 字段。{!obj[f]}{!rec[f]}
最后,新组件可用于创建任意数量的 使用该组件显示记录详细信息的简单 Visualforce 页面,以及 架构信息页面,例如以下两个页面:
<apex:page standardController="Account">
<c:DynamicObjectViewer rec="{!account}"/>
</apex:page>
<apex:page standardController="Contact">
<c:DynamicObjectViewer rec="{!contact}"/>
</apex:page>