动态 Visualforce 绑定

动态视觉力 绑定是编写显示信息的通用 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 时, 您将看到类似于以下内容的序列:

  1. 在默认状态下查看可自定义列表,仅包含帐户名称 显示字段。单击自定义列表
  2. 显示显示首选项屏幕。移动 某些字段添加到右侧的列表中,然后单击“显示这些字段” 字段
  3. 将显示自定义列表视图。

将动态引用与自定义对象一起使用,以及 包

包开发人员可以使用动态 Visualforce 绑定仅列出 用户可以访问的字段。在开发托管 软件包中包含 Visualforce 页面,该页面具有 显示对象上的字段。由于包开发人员不知道哪些字段 订阅者可以访问,他们可以定义一个动态页面,该页面为每个页面呈现不同的内容 订户。

以下示例使用一个自定义对象,该对象与使用 Visualforce 页面的页面布局打包在一起,以演示 不同的订阅用户如何查看同一页面。

  1. 使用以下字段和数据类型创建自定义对象(API 名称):BookBook__c
    • 标题: Text(255)
    • 作者: 文本(255)
    • ISBN: 文本(20)
    • 价格: 货币(5, 2)
    • 出版商: Text(255)
  2. 编辑“书籍”页面布局,使其首先显示自定义字段,并删除一些自定义字段 标准字段,例如“创建者”、“上次修改者”、“所有者”和“名称”。
  3. 创建新的自定义对象选项卡。将对象设置为“Book”,将选项卡样式设置为 书。
  4. 切换到 Book 选项卡并创建几个 Book 对象。值无关紧要,但 您确实需要一些记录才能实际存在。
  5. 创建使用以下命令调用的控制器扩展 法典:BookExtensionpublic 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; } }
  6. 创建 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>
  7. 由于控制器扩展将被打包,因此需要为 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 测试只是一个示例。什么时候 创建包含在包中的测试,验证所有行为,包括 阳性和阴性结果。
  8. 创建一个名为 的包,并添加自定义 对象,即 Visualforce 页面, 和 Apex 类。其他 引用的元素(例如页面的控制器扩展 Apex 类)是 自动包含。bookBundlebookExtensionTest
  9. 将软件包安装到 订阅者组织。bookBundle
  10. 安装软件包后,从书籍的对象管理设置中,添加 名为 Rating 的新字段。
  11. 创建新的 Book 对象。同样,记录的值实际上并不 事。
  12. 导航到带有 附加到 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>

创建和使用自定义组件

Salesforce 提供了一个 可用于开发 Visualforce 页面的标准预构建组件库,例如 和 。在 此外,您可以构建自己的自定义组件来扩充此库。本章概述了自定义组件和 如何创建它们:

<apex:relatedList><apex:dataTable>

  • 什么是自定义组件?
  • 自定义组件标记
  • 在 Visualforce 页面中使用自定义组件
  • 自定义组件属性
  • 自定义组件控制器
  • 定义自定义组件

什么是自定义组件?

类似于封装一件作品的方式 代码,然后在 程序中,您可以将通用设计模式封装在自定义组件中 ,然后在一个或多个 Visualforce 页面中多次重复使用该组件。

例如,假设您要使用 Visualforce 页面。相册中的每张照片都有自己的边框颜色和文本 显示在其下方的标题。而不是重复 Visualforce 标记 需要 要显示相册中的每张照片,您可以定义自定义组件 named 具有 image、边框颜色和标题,然后使用这些属性来显示 页面上的图像。定义后,组织中的每个 Visualforce 页面都可以 利用 与页面可以利用标准组件(如 或 )的方式相同。singlePhotosinglePhoto<apex:dataTable><apex:relatedList>与页面模板不同,页面模板也使开发人员能够重用 标记、自定义组件提供了更强大的功能和灵活性,因为:

  • 自定义组件允许开发人员定义可传递到 每个组件。然后,属性的值可以更改标记的方式 显示在最后一页上,以及执行的基于控制器的逻辑 该组件的实例。此行为与模板的行为不同, 无法从使用 模板添加到模板的定义本身。
  • 自定义组件描述显示在应用程序的组件中 参考对话框以及标准组件描述。模板 另一方面,描述只能通过“设置”区域引用 Salesforce,因为他们是 定义为页面。

定义自定义组件

要定义用于 Visualforce 页面的自定义组件,请执行以下操作:

  1. 在 Salesforce 的“设置”中,输入“快速” “查找”框,然后选择“Visualforce 组件”。Components
  2. 单击“新建”。
  3. 在“标签”文本框中,输入应用于 在设置工具中识别自定义组件。
  4. 在“名称”文本框中,输入应标识此名称的文本 Visualforce 标记中的自定义组件。此名称只能包含下划线和 字母数字字符,并且在您的组织中必须是唯一的。它必须以字母开头,而不是 包含空格,不以下划线结尾,不包含两个连续 强调。
  5. 在“说明”文本框中,输入自定义项的文本说明 元件。此描述与其他标准一起出现在组件参考中 单击“保存”后立即查看组件描述。
  6. 在正文文本框中,输入自定义的 Visualforce 标记 组件定义。单个组件最多可以容纳 1 MB 的文本,或者大约可以容纳 1,000,000 个字符。
  7. 单击“版本设置”以指定 Visualforce 的版本,然后 与此组件一起使用的 API。您还可以为任何托管软件包指定版本 安装在您的组织中。
  8. 单击保存”以保存更改并查看自定义 组件的详细信息屏幕,或单击“快速保存”以保存 更改并继续编辑组件。您的 Visualforce 标记必须在 您可以保存组件。

注意

您还可以通过添加对自定义组件的引用,在 Visualforce 开发模式下创建自定义组件 尚不存在的 Visualforce 页面标记。保存标记后,将显示一个快速修复链接,该链接显示 允许您创建新的组件定义(包括任何指定的 attributes) 基于您为组件提供的名称。

例如,如果您尚未定义 自定义组件命名并插入到现有页面标记中,单击“保存”后,快速修复允许 您可以定义一个新的自定义组件,该组件使用以下默认值命名 定义:myNewComponent<c:myNewComponent myNewAttribute=”foo”/>myNewComponent

<apex:component>
  <apex:attribute name="myattribute" type="String" description="TODO: Describe me"/>
  <!-- Begin Default Content REMOVE THIS -->
  <h1>Congratulations</h1>
  This is your new Component: mynewcomponent
  <!-- End Default Content REMOVE THIS -->
</apex:component>

您可以修改此项 通过输入快速从设置中定义 “查找”框,然后选择“Visualforce 组件”,然后 ,然后单击 myNewComponent 自定义项旁边的编辑 元件。Components

创建组件后,您可以在 http:// yourSalesforceOrgURL/apexcomponent/nameOfNewComponent 中查看它。 其中用于访问您的 Salesforce 组织的 URL (例如,MyDomainName.my.salesforce.com) 和 value of 是自定义组件定义上“名称”字段的值。yourSalesforceOrgURLnameOfNewComponent

该组件的显示方式就好像它是 Visualforce 页面一样。 因此,如果组件依赖于属性或组件的内容 标记的正文,此 URL 可能会生成您意想不到的结果。查看更多 准确测试自定义组件,将其添加到 Visualforce 页面,然后查看该页面。

自定义组件标记

自定义组件的所有标记都在 一个标签。 此标记必须是自定义组件定义中的顶级标记。 例如:<apex:component>

<apex:component>
    <b>
        <apex:outputText value="This is my custom component."/> 
    </b>
</apex:component>

请注意,标记可以是 Visualforce 和 HTML 标记的组合,就像其他 Visualforce 页面一样。对于更复杂的示例,您可以使用自定义组件来 创建一个跨多个 Visualforce 页面使用的表单。创建一个新的自定义组件,并复制以下内容 法典:

recordDisplay

<apex:component>
    <apex:attribute name="record" description="The type of record we are viewing."
                    type="Object" required="true"/>
                     
    <apex:pageBlock title="Viewing {!record}">   
        <apex:detail />
    </apex:pageBlock>
</apex:component>

接下来,创建一个名为 法典:

displayRecords

<apex:page >
  <c:recordDisplay record="Account" />
</apex:page>

为 此示例要正确呈现,您必须关联 Visualforce 页面 在 URL 中具有有效的客户记录。例如,如果是账户 ID,则 生成的 URL 应 是:

001D000000IRt53

https://Salesforce_instance/apex/displayRecords?id=001D000000IRt53

你 应该会看到一个页面,其中包含有关您作为 同上。现在,将代码替换为以下示例:

displayRecords

<apex:page>
  <c:recordDisplay record="Contact" />
</apex:page>

同样,在之前传入联系人的 ID 刷新页面。您应该会看到页面显示有关以下方面的信息 您的联系人。

自定义组件属性包含有关使用组件的更多信息。<apex:attribute>

在 Visualforce 页面中使用自定义组件

标记的正文是添加到标准 Visualforce 页面的标记,每当 组件包括在内。例如,以下 Visualforce 页面使用该组件 在自定义组件标记中定义(在此示例中,组件使用名称保存):<apex:component>myComponent

<apex:page standardController="Account">
   This is my <i>page</i>. <br/>
   <c:myComponent/>
</apex:page>

它产生以下输出:

This is my page.
This is my custom component.

要在 Visualforce 页面中使用自定义组件,您必须在 组件的名称,其中包含定义组件的命名空间。例如,如果 命名的组件在 命名空间调用,组件可以 在 Visualforce 页面中引用为 .myComponentmyNS<myNS:myComponent>

为了便于使用,在 命名空间作为关联页面也可以使用命名空间前缀。因此,如果定义了上述示例中的页面和组件 在同一命名空间中,可以将组件引用为 。c<c:myComponent>

如果要将内容插入到自定义组件中,请使用 <apex:componentBody> 标记。

与标准组件类似,当更新或编辑自定义组件时, 引用它的 Visualforce 页面也会更新。

管理自定义组件的版本设置

设置 Visualforce 的 Salesforce API 和 Visualforce 版本 页面或自定义组件:

  1. 编辑 Visualforce 页面或组件,然后单击版本设置。注意您只能修改 “版本设置”中页面或自定义组件的版本设置 选项卡,在“设置”中编辑页面或组件时。
  2. 选择 Salesforce API 的版本。这也是 与页面或组件一起使用的 Visualforce 版本。
  3. 点击保存

自定义组件属性

除了标准的 Visualforce 标记外,正文 标签还可以指定 在 Visualforce 页面中使用自定义组件时可以传递到自定义组件的属性。的价值观 然后,可以直接在组件中使用此类属性,也可以在组件的控制器中使用(如果适用)。但是,它们不能被使用 在组件控制器的构造函数中。<apex:component>

属性是用标签定义的。例如,以下 自定义组件定义指定了两个必需的属性,分别名为 和 。这些属性的值在自定义中引用 使用标准 Visualforce 表达式语言的组件定义 语法:<apex:attribute>valuetextColor{! }

<apex:component>
    <!-- Attribute Definitions -->
    <apex:attribute name="myValue" description="This is the value for the component."
                    type="String" required="true"/>
    <apex:attribute name="textColor" description="This is color for the text."
                    type="String" required="true"/>

    <!-- Component Definition -->
    <h1 style="color:{!textColor};">
        <apex:outputText value="{!myValue}"/> 
    </h1> 
</apex:component>

在 Visualforce 页面中使用此组件,并使用 以下标记:

<c:myComponent myValue="My value" textColor="red"/>

标签需要以下值 、 和 属性:

<apex:attribute>namedescriptiontype

  • 该属性定义了 自定义属性可以在 Visualforce 页面中引用。名称必须是唯一的 跨组件,并且不区分大小写。例如,如果两个属性是 named 和 ,包对它们的处理方式相同, 可能会导致意外行为。name“Model”“model”
  • 该属性定义了 在组件参考库中出现一次的属性的帮助文本 自定义组件已保存。自定义组件列在 参考库,其中包含可用的标准组件。description
  • 该属性定义 Apex 属性的数据类型。仅允许以下数据类型作为值 对于属性:typetype
    • 基元,例如 String、Integer 或 Boolean。
    • sObject,例如 Account、My_Custom_Object__c 或泛型 sObject 类型。
    • 使用数组表示法指定的一维列表,例如 String[], 或联系人[]。
    • 映射,使用 指定。您无需指定地图的 特定数据类型。type=”map”
    • 自定义 Apex 类。

有关其他属性的信息,请参阅 apex:attribute<apex:attribute>

默认自定义组件 属性

始终为自定义组件生成两个属性。这些属性 不需要包含在组件定义中:id允许自定义组件被其他组件引用的标识符 组件。如果未指定,则唯一标识符为 自动生成。rendered一个 Boolean 值,该值指定是否呈现自定义组件 在页面上。如果未指定,则此值默认为 true。

自定义组件控制器

与标准 Visualforce 页面类似,自定义组件可以 与用 Apex 编写的控制器相关联。此关联是通过将组件上的属性设置为 自定义控制器。您可以使用控制器执行其他逻辑,然后再返回 组件的标记到关联页面。controller

访问控制器中的自定义组件属性

若要访问关联的自定义组件控制器中自定义组件属性的值,请执行以下操作:

  1. 在自定义组件控制器中定义一个属性,以存储 属性。
  2. 定义属性的 getter 和 setter 方法。为 例:public class myComponentController { public String controllerValue; public void setControllerValue (String s) { controllerValue = s.toUpperCase(); } public String getControllerValue() { return controllerValue; } }通知 setter 修改值。
  3. 在标签中 组件定义,使用该属性将该属性绑定到您刚刚定义的类变量。为 例:<apex:attribute>assignTo<apex:component controller="myComponentController"> <apex:attribute name="componentValue" description="Attribute on the component." type="String" required="required" assignTo="{!controllerValue}"/> <apex:pageBlock title="My Custom Component"> <p> <code>componentValue</code> is "{!componentValue}" <br/> <code>controllerValue</code> is "{!controllerValue}" </p> </apex:pageBlock> Notice that the controllerValue has been upper cased using an Apex method. </apex:component>注意 当使用属性时,getter 必须定义 setter 方法或具有 和 值的属性。assignTogetset
  4. 将组件添加到页面。例如<apex:page> <c:simpleComponent componentValue="Hi there, {!$User.FirstName}"/> </apex:page>

页面的输出将如下所示:

请注意,Apex 控制器方法已更改,因此它以大写形式显示 字符。

controllerValue

使用静态资源

静态资源允许您上传 可以在 Visualforce 页面中引用,包括存档(如 .zip 和 .jar 文件), 图像、样式表、JavaScript 和其他文件。静态资源只能使用 在您的 Salesforce 组织内,因此您不能在此处为其他应用程序或 网站。使用静态资源比将文件上传到 “文档”选项卡,因为:

  • 您可以将相关文件的集合打包到 目录层次结构,并将该层次结构作为 .zip 或 .jar 存档上传。
  • 您可以通过以下方式在页面标记中按名称引用静态资源 使用全局 变量,而不是硬编码文档 ID。$Resource

提示

另外 使用静态资源引用 JavaScript 或级联样式表 (CSS) 比包含内联标记更可取。管理这个 使用静态资源的内容类型允许您具有一致的 所有页面的外观和一组共享的 JavaScript 功能。

一个 单个静态资源最多可以有 5 MB,一个组织最多可以有 250 MB 的 静态资源,总计。

  • 创建静态资源
  • 在 Visualforce 标记中引用静态资源
  • 使用 iframe 引用不受信任的第三方内容

创建静态资源

要创建静态资源,请执行以下操作:

  1. 在“设置”中,输入“快速” “查找”框,然后选择“静态资源”。Static Resources
  2. 点击新增功能.
  3. 在名称文本框中,输入用于标识 资源。此名称只能包含下划线和字母数字 字符,并且必须在您的组织中是唯一的。它必须以字母开头,不能包含空格, 不以下划线结尾,也不包含两个连续的下划线。注意如果你 在 Visualforce 标记中引用静态资源,然后更改 资源,则 Visualforce 标记将更新以反映该更改。
  4. 在“说明”文本区域中,指定 资源。
  5. 在文件文本框旁边,单击浏览到 导航到要上传的资源的本地副本。一个 单个静态资源最多可以有 5 MB,一个组织最多可以有 250 MB 的 静态资源,总计。
  6. 为用户会话设置缓存控制,包括 API 和 Experience Cloud 用户会话:
    • Private指定 Salesforce 服务器上缓存的静态资源数据不得 与其他用户共享。静态资源仅存储在缓存中,用于当前 用户的会话。注意在以下情况下,静态资源的缓存设置设置为私有 通过 Salesforce 站点访问,其访客用户的个人资料具有基于 IP 的限制 范围或登录时间。具有来宾用户配置文件限制的站点缓存静态 资源仅在浏览器中。此外,如果以前不受限制的站点成为 受限制时,静态资源最多可能需要 45 天才能从 Salesforce 缓存和任何中间缓存。Public指定共享 Salesforce 服务器上缓存的静态资源数据 与组织中的其他用户一起,以缩短加载时间。缓存后, 资源可供所有 Internet 流量访问,包括未经身份验证的 用户。
    标头上的 W3C 规范 字段定义包含有关缓存控制的更多技术信息。注意这 该功能仅适用于使用静态 资源。
  7. 点击保存

警告

如果您使用的是 WinZip,则必须安装最多的 最新版本。旧版本的 WinZip 可能会导致数据丢失。

在 Visualforce 标记中引用静态资源

在 Visualforce 标记中引用静态资源的方式取决于是否要引用独立文件。 或者是否要引用存档中包含的文件 (例如 .zip 或 .jar 文件):

  • 要引用独立文件,请用作 合并字段,其中名称是 you 在上传资源时指定。为 例:$Resource.<resource_name><resource_name><apex:image url="{!$Resource.TestImage}" width="50" height="50"/><apex:includeScript value="{!$Resource.MyJavascriptFile}"/>
  • 要引用存档中的文件,请使用该函数。指定静态 上传存档时提供的资源名称 第一个参数,以及存档中所需文件的路径 与第二个。例如:URLFOR<apex:image url="{!URLFOR($Resource.TestZip, 'images/Bluehills.jpg')}" width="50" height="50"/><apex:includeScript value="{!URLFOR($Resource.LibraryJS, '/base/subdir/file.js')}"/>
  • 您可以在静态资源中的文件中使用相对路径 存档来引用存档中的其他内容。例如,在 CSS 中 名为 styles.css 的文件,您有以下内容 风格:table { background-image: url('img/testimage.gif') }什么时候 您在 Visualforce 页面中使用该 CSS,您需要确保 CSS 文件可以 找到图像。为此,请创建一个包含样式.css和img/testimage.gif的存档(例如zip文件)。 确保路径结构保留在存档中。然后上传 将文件存档为名为“style_resources”的静态资源。然后 在页面中,添加以下组件:<apex:stylesheet value="{!URLFOR($Resource.style_resources, 'styles.css')}"/>因为 静态资源包含样式表和图像,相对 样式表中的路径将解析并显示图像。
  • 通过自定义控制器,您可以动态引用 使用标记的静态资源的内容。首先,创建自定义控制器:<apex:variable>global class MyController { public String getImageName() { return 'Picture.gif';//this is the name of the image } }然后,参考代码中的方法:getImageName<apex:variable><apex:page renderAs="pdf" controller="MyController"> <apex:variable var="imageVar" value="{!imageName}"/> <apex:image url="{!URLFOR($Resource.myZipFile, imageVar)}"/> </apex:page>如果图像的名称在 zip 文件,您只需更改 中的返回值即可。getImageName

使用 iframe 引用不受信任的第三方内容

最好隔离从不受信任的来源下载的静态资源。您可以 使用 iframe 将第三方内容与 Visualforce 页面分开,以提供 额外的安全层,帮助您保护资产。

若要在单独的域中引用静态 HTML 文件,请用作合并 字段中,其中是您在 上传了资源。例如:$IFrameResource.<resource_name>resource_name

<apex:iframe src="{!$IFrameResource.TestHtml}" id ="theiframe" width="500" height="500"/>

iframe 标记将 JavaScript 注入到父文档和子 iframe 中,以 在两个元素之间建立安全的通信。父文档可以有 多个 iframe。每个唯一命名的静态资源都位于其自己的子域 或 中。force-user-content.comforceusercontent.com

对 iframe 的访问未经身份验证,因此它包含的任何第三方内容都无法访问 用户的会话 ID。

与父文档中的 iframe 进行通信

您可以在父文档中编写 JavaScript 代码以与 iframe 进行通信。

  • 发送消息至 iframe:SfdcApp.iframe.sendMessage('theiframe', { key1: value1, key2: value2 });
  • 接收来自以下位置的消息 iframe:SfdcApp.iframe.addMessageHandler('theiframe', function(data) { if(data.key1) { … } });
  • 从以下位置捕获错误 iframe:SfdcApp.iframe.addErrorHandler('theiframe', function(error) { console.log(error); });

在 iframe 中与父文档进行通信

您也可以从 iframe 文档以另一种方式进行通信。

  • 向父级发送消息 公文:LCC.onlineSupport.sendMessage('containerUserMessage', { key1: value1, key2: value2 });
  • 设置处理程序以接收来自父级的消息 公文:LCC.onlineSupport.addMessageHandler(function(message) { if(data.key1) { … } });自 删除此内容 处理器:LCC.onlineSupport.removeMessageHandler(function)
  • 为来自父级的消息错误设置处理程序 公文:LCC.onlineSupport.addMessageErrorHandler(function(message) { if(data.key1) { … } });自 删除此内容 处理器:LCC.onlineSupport.removeMessageErrorHandler(function)
  • 为其他类型的 错误:LCC.onlineSupport.addErrorHandler(function(message) { if(data.key1) { … } });自 删除此内容 处理器:LCC.onlineSupport.removeErrorHandler(function)

使用 Visualforce 覆盖按钮、链接和选项卡

您可以在 Salesforce Classic、Lightning Experience 和移动设备独立使用。您还可以覆盖 选项卡主页,当用户单击标准、自定义或外部对象选项卡时显示。要覆盖标准按钮或选项卡主页:

  1. 单击要执行的按钮或标签页主页旁边的“编辑” 覆盖。
  2. 选择 Visualforce 页面作为覆盖类型。
  3. 选择要在用户单击按钮或选项卡时运行的 Visualforce 页面。什么时候 使用 Visualforce 页面覆盖按钮时,您必须将标准控制器用于 显示按钮的对象。例如,要使用网页覆盖帐号上的“修改”按钮,网页标记必须包含代码上的属性。standardController=”Account”<apex:page><apex:page standardController="Account"> <!-- page content here --> </apex:page>使用 Visualforce 页面覆盖选项卡时,您可以 仅选择对该选项卡使用标准列表控制器的 Visualforce 页面 关联的对象、具有自定义控制器的页面或没有自定义控制器的页面 控制器。使用 Visualforce 页面覆盖列表时,您只能选择 使用标准列表控制器的 Visualforce 页面。使用 Visualforce 页面覆盖“新建”按钮时,您可以选择跳过 记录类型选择页。如果这样做,您创建的新记录不会转发到 记录类型选择页。Salesforce 假定您的 Visualforce 页面已经 处理记录类型。重要当 Salesforce 移动应用程序用户单击“新建”以创建产品时,用户必须选择记录类型 即使选择了“跳过记录类型选择页面”选项 在设置中。提示使用控制器扩展添加额外的 用作替代的 Visualforce 页面的功能。
  4. 点击保存

要删除覆盖:

  1. 从相应对象的管理设置中,转到按钮、链接和操作。
  2. 单击覆盖旁边的编辑
  3. 选择“无覆盖(默认行为)”。
  4. 单击“确定”。注意从 Classic 切换到 Lightning Experience,如果 URL 包含在 Classic 中,它将更改为 Lightning 中,并且您不会看到记录的覆盖 您正在导航到。nooverride=1nooverride=true

使用标准列表覆盖选项卡 控制器

可以使用使用标准列表控制器的页面进行替代 制表符。例如,如果创建一个名为 使用帐户标准列表控制器:

overrideAccountTab

<apex:page standardController="Account" recordSetVar="accounts" tabStyle="account">
  <apex:pageBlock >
    <apex:pageBlockTable value="{!accounts}" var="a">
      <apex:column value="{!a.name}"/>
    </apex:pageBlockTable>
  </apex:pageBlock>
</apex:page>

然后,您可以覆盖“帐户”选项卡 以显示该页面,而不是标准帐户主页。

  1. 从帐户的对象管理设置中,转到按钮、链接和 行动。
  2. 单击“帐户”选项卡的“编辑”。
  3. 从 Visualforce 页面 下拉列表中,选择 overrideAccountTab 页面。
  4. 点击保存

注意

通过设置页面级别,确保已将此页面提供给所有用户 适当地安全。

为 Visualforce 定义自定义按钮和链接

在创建自定义按钮或链接之前,请确定在 用户单击它。

  1. 从相应对象的管理设置中,转到按钮、链接和 行动。自定义按钮在用户对象或自定义主页上不可用。自定义按钮和链接可用于对象中的活动 任务和事件的管理设置。但是,您可以覆盖按钮 这适用于对象管理设置中的任务和事件 活动。
  2. 单击按钮以创建新按钮或链接。
  3. 输入以下属性。属性名称描述标签在用户页面上显示自定义按钮的文本,或者 链接。名字引用时使用的按钮或链接的唯一名称 从合并字段。此名称只能包含下划线和 字母数字字符,并且在您的组织中必须是唯一的。它必须 以字母开头,不包含空格,不以 下划线,并且不包含两个连续的下划线。命名空间前缀在打包上下文中,命名空间前缀是 15 个字符的字母数字标识符,用于区分您的 软件包及其内容来自其他开发人员的软件包 AppExchange。命名空间前缀不区分大小写。为 例如,ABC 和 abc 不被识别为唯一。你 命名空间前缀在所有 Salesforce 中必须是全局唯一的 组织。它使您的托管包处于您的控制之下 惟独。受保护组件受保护的组件不能链接或引用 由在订阅者组织中创建的组件。开发人员可以 在将来的版本中删除受保护的组件,而不使用 担心安装失败。但是,一旦一个组件 被标记为未受保护,并在全球范围内发布,开发者 无法删除它。描述用于区分按钮或链接并显示的文本 当管理员设置按钮和链接时。显示类型确定按钮或链接在页面布局中的可用位置。详情页面链接选择此选项可将链接添加到“自定义” 页面布局的“链接”部分。“详情页面”按钮选择此选项可将自定义按钮添加到 记录的详细信息页面。您可以添加详情页面 按钮到页面布局的“按钮”部分 只。列表按钮选择此选项可将自定义按钮添加到 列表视图、搜索结果布局或相关列表。 您可以将列表按钮添加到“相关列表”部分 页面布局或列表视图和搜索结果 仅限布局。对于列表按钮,Salesforce 自动选择显示器 复选框(用于多记录 Selection) 选项,其中包括 复选框,允许 用户选择要应用到的记录 “列表”按钮上的操作。取消选择此项 选项,如果您的自定义按钮不需要 用户选择记录。例如,一个按钮 导航到另一个页面。行为选择单击按钮或链接的结果。什么时候 适用时,某些设置具有默认值。例如 如果选择 , 新窗口的默认高度为 600 像素。Display in new window内容源要使用 Visualforce 页面,不能将其用作 主页。
  4. 完成后单击保存。单击“快速” 保存以保存并继续编辑。要查看指定的 URL,单击预览。在不保存你的 内容,单击取消
  5. 编辑相应选项卡或搜索布局的页面布局,以显示新的 按钮或链接。如果您为用户添加自定义链接,则该链接会自动添加到 用户详细信息页面的“自定义链接”部分。详情页面按钮可以是 仅添加到页面布局的“按钮”部分。
  6. (可选)使用设置设置窗口属性以打开按钮或链接 除了用户的默认浏览器设置。

使用标准列表添加自定义列表按钮 控制器

除了覆盖标准按钮和链接外,您还可以创建自定义列表 链接到使用标准列表控制器的页面的按钮。这些列表按钮可以 用于列表页、搜索结果以及对象的任何相关列表,并允许 对一组选定的记录执行操作。指示记录集 已选择,请使用表达式。{!selected}例如,将自定义按钮添加到商机的相关列表中,以便您 要编辑和保存所选记录的商机阶段和结束日期,请执行以下操作:

  1. 创建以下 Apex 类:public class tenPageSizeExt { public tenPageSizeExt(ApexPages.StandardSetController controller) { controller.setPageSize(10); } }
  2. 创建以下页面并调用它:oppEditStageAndCloseDate<apex:page standardController="Opportunity" recordSetVar="opportunities" tabStyle="Opportunity" extensions="tenPageSizeExt"> <apex:form > <apex:pageBlock title="Edit Stage and Close Date" mode="edit"> <apex:pageMessages /> <apex:pageBlockButtons location="top"> <apex:commandButton value="Save" action="{!save}"/> <apex:commandButton value="Cancel" action="{!cancel}"/> </apex:pageBlockButtons> <apex:pageBlockTable value="{!selected}" var="opp"> <apex:column value="{!opp.name}"/> <apex:column headerValue="Stage"> <apex:inputField value="{!opp.stageName}"/> </apex:column> <apex:column headerValue="Close Date"> <apex:inputField value="{!opp.closeDate}"/> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
  3. 使该页面可供所有用户使用。
    1. 在“设置”中,输入“快速查找”框,然后选择“Visualforce 页面”。Visualforce Pages
    2. 单击 oppEditStageAndCloseDate 的“安全性” 页。
    3. 将相应的配置文件添加到“已启用” 配置文件列表。
    4. 点击保存
  4. 在商机上创建自定义按钮。
    1. 从商机的对象管理设置中,转到 Buttons, 链接和操作。
    2. 单击按钮以创建新按钮或链接。
    3. 将“标签”设置为 。Edit Stage & Date
    4. 将显示类型设置为列表 按钮。
    5. 将“内容源”设置为“Visualforce 页面”。
    6. 从“内容”下拉列表中,选择 。oppEditStageAndCloseDate
    7. 点击保存
    8. 将显示一条警告,通知您该按钮不会 在更新页面布局之前显示。单击“确定”。
  5. 将自定义按钮添加到帐户页面布局。
    1. 从帐户的对象管理设置中,转到页面 布局。
    2. 单击相应页面布局的编辑。
    3. “相关列表”部分中,单击“商机”,然后单击可编辑字段以编辑 性能。
    4. 在“自定义按钮”部分中,选择“编辑舞台和” 可用按钮列表中的日期,并将其添加到 “所选按钮”列表。
    5. 单击“确定”。
    6. 点击保存

现在,当您访问帐户页面时,机会中会出现一个新按钮 相关列表。

新建按钮示例当您选择一个商机并单击“编辑阶段”和“时 日期,您将被带到您的自定义编辑页面。

自定义编辑页面示例

显示记录类型

Salesforce API 版本等于或大于 20.0 的 Visualforce 页面支持记录 类型。记录类型可让您提供不同的 不同用户的业务流程、选择列表值和页面布局。

在“设置”中创建记录类型后,在 Visualforce 中启用对该记录类型的支持无需对 你的部分。对象的 Visualforce 页面 使用记录类型的记录将遵循您的设置。记录类型字段命名为 。RecordTypeId您的记录类型定义通过以下方式影响标签的呈现:

<apex:inputField>

  • 如果标签引用选择列表 按记录类型筛选的字段:<apex:inputField>
    • 仅渲染的组件 显示与该记录类型兼容的选项。<apex:inputField>
    • 如果组件绑定到 具有呈现和可编辑的控制字段的依赖选择列表,仅选项兼容 同时显示记录类型和控制字段值。<apex:inputField>
  • 如果标记引用记录 type 字段:<apex:inputField>
    • 如果用户可以更改字段的记录类型,或为新的 字段中,组件呈现为 下拉列表。否则,它将呈现为只读文本。<apex:inputField>
    • 开发人员负责刷新页面或重新呈现筛选的页面 列表更改时的选择列表。

此外,标签的支持 对于记录类型,与行为的只读实现相同。

<apex:outputField><apex:inputField>

覆盖“新建”按钮时 在 Visualforce 页面中,您可以选择跳过记录类型 选择页面。如果这样做,您创建的新记录不会转发到 记录类型选择页。Salesforce 假设您的 Visualforce 页面已在处理记录类型。

高级示例

快速入门中的示例 tutorial 被视为入门示例,主要仅使用 Visualforce 标记。 除了 Visualforce 标记之外,高级示例还使用 Lightning Platform Apex 代码。

创建您的第一个自定义控制器

到目前为止,本教程中的所有示例都使用了标准帐户 控制器来定义每个页面的底层逻辑。但是,Visualforce 允许您 通过定义自定义控制器,将自己的逻辑和导航控件添加到页面。这 以下主题将介绍创建自定义控制器类和定义自定义控制器类的基础知识 可以与 Visualforce 标记交互的类方法:

  • 创建自定义控制器类
  • 定义 Getter 方法
  • 定义操作方法
  • 定义导航方法
  • 使用自定义列表控制器批量更新记录

注意

您只能在开发人员中使用 Salesforce 用户界面添加、编辑或删除 Apex Edition、Salesforce Enterprise Edition 试用组织或沙盒组织。在一个 Salesforce 生产组织,您只能使用 Ant 对 Apex 进行更改 迁移工具或 Lightning 平台 API 调用。compileAndTest

创建自定义控制器类

自定义控制器只是一个 Apex 类。例如,以下代码是有效的 虽然无效,但控制器类:

public class MyController {

}

您可以创建控制器类,并通过两种不同的方式将其添加到页面中:

  • 将 controller 属性添加到您的页面,并使用“快速修复”来 动态创建控制器类:
    1. 在页面编辑器中,将 controller 属性添加到标记中。例如:<apex:page><apex:page controller="MyController"> <apex:pageBlock title="Hello {!$User.FirstName}!"> This is your new page. </apex:pageBlock> </apex:page>
    2. 使用快速修复选项自动创建名为 我的控制器。
  • 在您选择的 Apex 编辑器中创建并保存控制器类,然后 在您的页面中引用它:
    1. 在应用程序中,从“设置”中,在“快速查找”框中输入“Apex 类”,然后选择“Apex” 类,然后单击“新建”以创建一个 新类。
    2. 返回到您的网页,然后将属性添加到代码中,如 上面的例子。controller<apex:page>

注意

一个页面一次只能引用一个控制器。您不能在标签中同时使用该属性和该属性。standardControllercontroller<apex:page>

一旦您保存了引用有效自定义控制器的页面,第二个控制器 编辑器选项卡位于页面编辑器旁边。此编辑器允许您向后切换 在页面标记和定义页面的 Apex 之间来回切换 逻辑。

自定义控制器 编辑 器

定义 Getter 方法

Visualforce 控制器类的主要任务之一是为开发人员提供一种显示数据库的方法 以及页面标记中的其他计算值。启用此功能的方法 功能类型称为 getter 方法,并且 通常被命名为 ,其中 是 方法返回的记录或基元值。getIdentifierIdentifier

例如,以下控制器有一个用于返回的 getter 方法 控制器的名称以字符串形式表示:

public class MyController {

    public String getName() {
        return 'MyController';
    }

}

若要在页面中显示 getter 方法的结果,请使用名称 的 getter 方法,表达式中没有前缀。例如,若要在页面标记中显示方法的结果,请使用:getgetName{!name}

<apex:page controller="MyController">
    <apex:pageBlock title="Hello {!$User.FirstName}!">
        This is your new page for the {!name} controller.
    </apex:pageBlock>
</apex:page>

在使用标准帐户控制器的早期示例中, 页面显示 URL(带有查询字符串 参数)。这是可能的 因为 Account 标准控制器包含一个名为 getter 的方法,该方法返回指定的 帐户记录。我们可以在自定义控制器中模拟此功能 使用以下代码:id{!account.<fieldName>}getAccount

public class MyController {

    public String getName() {
        return 'MyController';
    }

    public Account getAccount() {
        return [select id, name from Account 
                 where id = :ApexPages.currentPage().getParameters().get('id')]; 
    } 
}

注意为了正确呈现此示例, 您必须将 Visualforce 页面与 URL 中的有效客户记录相关联。例如,如果是账户 ID,则 生成的 URL 应为:

001D000000IRt53

https://Salesforce_instance/apex/MyFirstPage?id=001D000000IRt53

该方法使用嵌入式 SOQL 查询,返回页面 URL 中参数指定的帐户。 为了访问 ,该方法使用命名空间:getAccountididgetAccountApexPages

  • 首先,该方法返回当前页面的实例。 返回对 Visualforce 页面的引用,包括其查询字符串参数。currentPagePageReferencePageReference
  • 使用页面引用,使用该方法返回指定查询的映射 字符串参数名称和值。getParameters
  • 然后调用指定返回的方法 参数的值 本身。getidid

使用 MyController 的页面 控制器可以分别显示带有 OR 表达式的帐户名称或 ID 字段。 只有这些字段对页面可用,因为这些字段是 仅控制器中 SOQL 查询返回的字段。{!account.name}{!account.id}

为了更接近地模仿标准帐户控制器,我们可以添加 属性设置为 标记到 为页面提供与其他帐户页面相同的样式。标记 因为页面现在如下所示:tabStyle<apex:page>

<apex:page controller="MyController" tabStyle="Account">
    <apex:pageBlock title="Hello {!$User.FirstName}!">
        This is your new page for the {!name} controller. <br/>
        You are viewing the {!account.name} account.
    </apex:pageBlock>
</apex:page>

使用自定义 用于在页面上显示值的控制器

定义操作方法

操作方法在页面执行逻辑或导航时 事件发生,例如当用户单击按钮或将鼠标悬停在 页。可以通过在以下参数之一的参数中使用表示法从页面标记中调用操作方法 标签:

{! }action

  • <apex:commandButton> 创建一个按钮,该按钮调用 行动
  • <apex:commandLink> 创建一个调用操作的链接
  • <apex:actionPoller> 定期调用操作
  • <apex:actionSupport> 创建一个事件(例如 “onclick”、“onmouseover”等) 命名组件,调用操作
  • <apex:actionFunction> 定义了一个新的 JavaScript 调用操作的函数
  • <apex:page> 调用操作 加载页面时

例如,在页面中使用输入组件中描述的示例页面中,命令按钮绑定到 Account 标准控制器中的方法。我们可以适应以前的 示例,以便它现在使用 MyController 自定义控制器:save

<apex:page controller="MyController" tabStyle="Account">
    <apex:form>
        <apex:pageBlock title="Hello {!$User.FirstName}!">
            You are viewing the {!account.name} account. <p/>
            Change Account Name: <p/> 
            <apex:inputField value="{!account.name}"/> <p/>
            <apex:commandButton action="{!save}" value="Save New Account Name"/>
        </apex:pageBlock>
    </apex:form>
</apex:page>

注意

请记住,要使此页面显示帐户数据,有效的 ID 必须在页面的 URL 中将客户记录指定为查询参数。为 例:

https://Salesforce_instance/apex/myPage?id=001x000xxx3Jsxb

保存页面后 上面,Visualforce 编辑器提供了一个“快速修复”选项,用于将方法添加到 MyController 类中。如果 单击快速修复链接,MyController 现在如下所示:save

public class MyController {

    public PageReference save() {
        return null;
    }

    public String getName() {
        return 'MyController';
    }

    public Account getAccount() {
        return [select id, name from Account 
                 where id = :ApexPages.currentPage().getParameters().get('id')]; 
    } 
}

由快速生成的方法 fix 采用操作方法的标准签名:它是公共的,返回一个 PageReference,并且不包含任何参数。save最终,方法定义必须 使用新的帐户值更新数据库,但首先我们必须定义一个成员变量 保存从数据库中检索到的帐户信息。没有成员 变量,则从数据库中检索到的记录在 其值用于呈现页面,并且用户对记录的更新不能 保存。要引入此成员变量,需要更改控制器代码的两个部分:

save

  • 必须将成员变量添加到类中
  • 执行初始查询时必须设置成员变量getAccount
public class MyController {

    Account account;

    public PageReference save() {
        return null;
    }

    public String getName() {
        return 'MyController';
    }

    public Account getAccount() {
        if(account == null) 
            account = [select id, name, site from Account 
                       where id = :ApexPages.currentPage().getParameters().get('id')];
        return account; 
    } 
}

现在成员变量已经到位,该方法需要做的就是更新数据库:save

public class MyController {

    Account account;

    public PageReference save() {
        update account;
        return null;
    }

    public String getName() {
        return 'MyController';
    }

    public Account getAccount() {
        if(account == null) 
            account = [select id, name, site from Account 
                       where id = :ApexPages.currentPage().getParameters().get('id')];
        return account; 
    } 
}

一个更强大的解决方案,可能会抓住 各种异常,查找重复项,等等。因为这本来是一个简单的 例如,这些细节被遗漏了。save

若要测试此页,请更改“更改帐户名称”字段中的值 ,然后单击保存新帐户名称。与标准帐户一样 控制器示例中,页面只需使用新帐户名称进行刷新。在下一个 例如,我们将扩展保存操作,以便不是刷新当前页面, 它会将用户导航到不同的确认页面。

注意

要使页面正确呈现,必须在 URL 中指定有效的帐户 ID。为 例如,如果是帐户 ID, 使用以下 URL:001D000000HRgU6

https://Salesforce_instance/apex/MyFirstPage?id=001D000000HRgU6

定义导航方法

除了执行数据库更新和其他计算外, 自定义控制器操作方法可以将用户导航到不同的 page,通过返回一个 PageReference 对象。

PageReference 是对 页面的实例化。除其他属性外,PageReferences 由一个 URL 和一组查询参数名称和值组成。在自定义控制器或控制器扩展中, 您可以在以下选项之一中引用或实例化 PageReference 方式:

  • Page.existingPageName指已保存在组织中的 Visualforce 页面的 PageReference。通过推荐 以这种方式到页面,平台识别出这个控制器 或控制器扩展依赖于指定的 页面,并将防止在控制器时删除页面 或存在扩展。
  • PageReference pageRef = new PageReference('partialURL');创建对 Lightning 平台上托管的任何页面的 PageReference。例如 设置为是指位于 http:// mySalesforceInstance/apex/HelloWorld 的 Visualforce 页面。 同样,设置为引用 到指定记录的详细信息页面。‘partialURL’‘/apex/HelloWorld’partialURL‘/’ + ‘recordID此语法不太适合引用其他 Visualforce 页面,因为 PageReference 是在运行时构造的,而不是在编译时引用的 时间。运行时引用不可用于参照完整性 系统。因此,平台无法识别此控制器或 控制器扩展依赖于指定页面的存在,并且 不会发出错误消息以防止用户删除页面。Page.existingPageName
  • PageReference pageRef = new PageReference('fullURL');为 外部 URL。例如:PageReference pageRef = new PageReference('http://www.google.com');

在此示例中,假设您要将一个用户重定向到另一个用户 在他或她点击保存后,页面中新增了 URL。为此,首先通过导航创建名为 mySecondPage 的第二个页面 添加到以下 URL 并使用快速修复:

https://Salesforce_instance/apex/mySecondPage

然后将以下标记添加到 mySecondPage。为简单起见, 只需使用以下定义的基于标准控制器的页面即可 本教程前面部分:

<apex:page standardController="Account">
    Hello {!$User.FirstName}!
    <p>You are viewing the {!account.name} account.</p>
</apex:page>

现在返回到您在定义操作方法中构建的原始页面,并确保已在 URL 中指定了帐户查询参数。编辑 控制器中的方法 以便它返回您刚刚创建的新页面的 PageReference, “我的第二页”:idsave

public class MyController {

    Account account;

    public PageReference save() {
        update account;
        PageReference secondPage = Page.mySecondPage;
        secondPage.setRedirect(true);
        return secondPage; 
    }

    public String getName() {
        return 'MyController';
    }

    public Account getAccount() {
        if(account == null) 
            account = [select id, name, site from Account 
                       where id = :ApexPages.currentPage().getParameters().get('id')];
        return account; 
    } 
}

请注意,在上面的代码中,PageReference 的属性设置为 true。 如果未设置此属性,则 PageReference 将返回到 浏览器,但不进行导航 – 原始 URL 页面保持不变。如果要更改 URL,则由于 导航,您必须设置属性。redirectredirect

如果您现在测试该页面,请点击保存新帐户 Name 导航到 mySecondPage,但数据上下文为 lost — 也就是说,没有可用于 的值。其原因 当发生重定向时,控制器会清除上下文状态。 因此,我们需要重置 PageReference 参数中的查询字符串参数 地图:{!account.name}id

public class MyUpdatedController {

    Account account;

    public PageReference save() {
        update account;
        PageReference secondPage = Page.mySecondPage;
        secondPage.setRedirect(true);
        secondPage.getParameters().put('id',account.id); 
        return secondPage; 
    }

    public String getName() {
        return 'MyController';
    }

    public Account getAccount() {
        if(account == null) 
            account = [select id, name, site from Account 
                       where id = :ApexPages.currentPage().getParameters().get('id')];
        return account; 
    } 
}

创建向导

了解了 Visualforce 标记和控制器的基本功能后, 最后一个示例演示如何将它们一起使用以创建自定义的三步向导 允许用户与相关联系人、客户和 联系人角色:

  • 第一步捕获与客户和联系人相关的信息
  • 第二步捕获与商机相关的信息
  • 最后一步显示将创建哪些记录,并允许用户保存或 取消

要实现此向导,我们必须为 向导,以及一个自定义控制器,用于在每个页面和 跟踪用户输入的数据。

重要

跨多个使用的数据 Visualforce 页面必须 在第一页中定义,即使该页未使用数据。例如,如果 字段在三步流程的第二页和第三页是必需的,第一页也必须 包含字段。您可以通过将字段的属性设置为 来对用户隐藏此字段。renderedfalse

每个组件的代码都包含在以下各节中,但首先需要 了解创建它们的最佳过程,因为三个页面中的每一个都引用了 控制器,控制器引用这三个页面中的每一个。在看似一个 难题,你不能在没有页面的情况下创建控制器,但页面必须存在 在控制器中引用它们。我们可以通过首先定义完全为空的页面来解决这个问题,然后 创建控制器,然后向页面添加标记。因此,最好的程序 用于创建向导页面和控制器的流程如下:

  1. 导航到第一页的 URL,然后单击“创建页面” opptyStep1.https://Salesforce_instance/apex/opptyStep1
  2. 对向导中的其他页面重复上述步骤,然后 .opptyStep2opptyStep3
  3. 创建控制器 将其作为属性添加到某个网页上的代码中(例如, ,然后单击创建 Apex 控制器 newOpportunityController。糊 ,然后单击保存newOpportunityController<apex:page><apex:page controller=”newOpportunityController”>
  4. 现在返回到您创建的三个页面的编辑器,并在其代码中复制。 向导现在应按预期工作。

注意

虽然您可以创建一个空页面,但反之则不行 true – 为了使页面引用控制器,控制器必须与所有 的方法和属性。

Opportunity Wizard 控制器

以下 Apex 类是 New Customer 中所有三个页面的控制器 商机向导:

public class newOpportunityController {

   // These four member variables maintain the state of the wizard.
   // When users enter data into the wizard, their input is stored
   // in these variables. 
   Account account;
   Contact contact;
   Opportunity opportunity;
   OpportunityContactRole role;


   // The next four methods return one of each of the four member
   // variables. If this is the first time the method is called,
   // it creates an empty record for the variable.
   public Account getAccount() {
      if(account == null) account = new Account();
      return account;
   }

   public Contact getContact() {
      if(contact == null) contact = new Contact();
      return contact;
   }

   public Opportunity getOpportunity() {
      if(opportunity == null) opportunity = new Opportunity();
      return opportunity;
   }

   public OpportunityContactRole getRole() {
      if(role == null) role = new OpportunityContactRole();
      return role;
   }


   // The next three methods control navigation through
   // the wizard. Each returns a PageReference for one of the three pages
   // in the wizard. Note that the redirect attribute does not need to
   // be set on the PageReference because the URL does not need to change
   // when users move from page to page.
   public PageReference step1() {
      return Page.opptyStep1;
   }

   public PageReference step2() {
      return Page.opptyStep2;
   }

   public PageReference step3() {
      return Page.opptyStep3;
   }


   // This method cancels the wizard, and returns the user to the 
   // Opportunities tab
    public PageReference cancel() {
      PageReference opportunityPage = new PageReference('/006');
      opportunityPage.setRedirect(true);
      return opportunityPage; 
    }

   // This method performs the final save for all four objects, and
   // then navigates the user to the detail page for the new
   // opportunity.
   public PageReference save() {

      // Create the account. Before inserting, copy the contact's
      // phone number into the account phone number field.
      account.phone = contact.phone;
      insert account;

      // Create the contact. Before inserting, use the id field
      // that's created once the account is inserted to create
      // the relationship between the contact and the account.
      contact.accountId = account.id;
      insert contact;

      // Create the opportunity. Before inserting, create 
      // another relationship with the account.
      opportunity.accountId = account.id;
      insert opportunity;

      // Create the junction contact role between the opportunity
      // and the contact.
      role.opportunityId = opportunity.id;
      role.contactId = contact.id;
      insert role;

      // Finally, send the user to the detail page for 
      // the new opportunity.


      PageReference opptyPage = new ApexPages.StandardController(opportunity).view();
      opptyPage.setRedirect(true);

      return opptyPage;
   }

}

商机向导的第 1 步

下面的代码定义向导 () 的第一页,其中收集了有关关联联系人和客户的数据 来自用户:opptyStep1

<apex:page controller="newOpportunityController" tabStyle="Opportunity">
  <script>
  function confirmCancel() {
      var isCancel = confirm("Are you sure you wish to cancel?");
      if (isCancel) return true;
  
     return false;
  }  
  </script>
  <apex:sectionHeader title="New Customer Opportunity" subtitle="Step 1 of 3"/>
    <apex:form>
      <apex:pageBlock title="Customer Information" mode="edit">

        <!-- The pageBlockButtons tag defines the buttons that appear at the top
             and bottom of the pageBlock. Like a facet, it can appear anywhere in
             a pageBlock, but always defines the button areas.-->
        <!-- The Next button contained in this pageBlockButtons area
             calls the step2 controller method, which returns a pageReference to
             the next step of the wizard. -->
        <apex:pageBlockButtons>
          <apex:commandButton action="{!step2}" value="Next"/>
          <apex:commandButton action="{!cancel}" value="Cancel" 
                              onclick="return confirmCancel()" immediate="true"/>
        </apex:pageBlockButtons>
      <apex:pageBlockSection title="Account Information">

        <!-- Within a pageBlockSection, inputFields always display with their
             corresponding output label. -->
        <apex:inputField id="accountName" value="{!account.name}"/>
        <apex:inputField id="accountSite" value="{!account.site}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Contact Information">
        <apex:inputField id="contactFirstName" value="{!contact.firstName}"/>
        <apex:inputField id="contactLastName" value="{!contact.lastName}"/>
        <apex:inputField id="contactPhone" value="{!contact.phone}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

请注意以下有关向导第一页的标记的信息:

  • 标签可以采用 可选儿童 元素,用于控制组件页眉和页脚中显示的按钮。 标记在正文中出现的顺序无关紧要。在这个 页面上,标记包括显示在页面块区域页脚中的“下一步”按钮。<apex:pageBlock><apex:pageBlockButtons><apex:pageBlockButtons><apex:pageBlock><apex:pageBlockButtons>
  • 该向导依赖于 JavaScript 代码来显示一个对话框,询问用户是否想要 单击“取消”按钮时导航离开。虽然 为了简单起见,示例将 JavaScript 直接包含在标记中,这是一个更好的 练习将 JavaScript 代码放在静态资源中并引用该资源。
  • 在向导的此页中,“下一步”按钮调用控制器中的方法,该方法返回向导的下一步step2PageReference<apex:pageBlockButtons> <apex:commandButton action="{!step2}" value="Next"/> </apex:pageBlockButtons>命令按钮必须出现在窗体中,因为窗体组件本身是 负责刷新页面显示,基于新的.PageReference
  • 标签组织一个 用于显示的数据集。与表类似,an 由一列或多列组成,每列 跨越两个单元格 – 一个用于字段的标签,一个用于字段的值。每个组件 在标签正文中找到的被放置在一行的下一个单元格中,直到 已达到列数。此时,下一个组件将换行到下一个组件 行,并放置在第一个单元格中。<apex:pageBlockSection><apex:pageBlockSection><apex:pageBlockSection>某些组件(包括 )会自动跨越两个单元 一次,填写字段的标签和值。为 例如,在此页面的“联系信息”区域中,第一个 “姓名”字段位于第一列,“姓氏”字段位于第二列,“电话”字段换行为 下一行的第一列:<apex:inputField><apex:pageBlockSection title="Contact Information"> <apex:inputField id="contactFirstName" value="{!contact.firstName}"/> <apex:inputField id="contactLastName" value="{!contact.lastName}"/> <apex:inputField id="contactPhone" value="{!contact.phone}"/> </apex:pageBlockSection>
  • 上述代码中第一个标记的属性 excerpt 将用户的输入分配给联系人记录的 firstName 字段,该字段是 由 中的方法返回 控制器。value<apex:inputField>getContact

您的页面应如下所示:

新客户机会的第 1 步 巫师

商机向导的第 2 步

以下代码定义向导 () 的第二页,其中从用户那里收集有关商机的数据:opptyStep2

<apex:page controller="newOpportunityController" tabStyle="Opportunity">
  <script>
  function confirmCancel() {
      var isCancel = confirm("Are you sure you wish to cancel?");
      if (isCancel) return true;
  
     return false;
  }  
  </script>
  <apex:sectionHeader title="New Customer Opportunity" subtitle="Step 2 of 3"/>
  <apex:form>
    <apex:pageBlock title="Opportunity Information" mode="edit">
      <apex:pageBlockButtons>
        <apex:commandButton action="{!step1}" value="Previous"/>
        <apex:commandButton action="{!step3}" value="Next"/>
        <apex:commandButton action="{!cancel}" value="Cancel" 
                            onclick="return confirmCancel()" immediate="true"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Opportunity Information">
        <apex:inputField id="opportunityName" value="{!opportunity.name}"/>
        <apex:inputField id="opportunityAmount" value="{!opportunity.amount}"/>
        <apex:inputField id="opportunityCloseDate" value="{!opportunity.closeDate}"/>
        <apex:inputField id="opportunityStageName" value="{!opportunity.stageName}"/>
        <apex:inputField id="contactRole" value="{!role.role}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

请注意,尽管用于在窗体上放置“联系人的关闭日期”、“阶段”和“角色”字段的标记 与其他字段相同,标签会检查每个字段的数据类型以确定如何 来显示它。例如,单击“关闭日期”文本框会带来 向上显示一个日历,用户可以从中选择日期。<apex:inputField>您的页面应如下所示:

新客户机会的第 2 步 巫师

商机向导的第三步

最后一个代码块定义向导 () 的第三页,其中显示所有输入的数据。这 用户可以决定保存操作或返回上一步:opptyStep3

<apex:page controller="newOpportunityController" tabStyle="Opportunity">
  <script>
  function confirmCancel() {
      var isCancel = confirm("Are you sure you wish to cancel?");
      if (isCancel) return true;
  
     return false;
  }  
  </script>
  <apex:sectionHeader title="New Customer Opportunity" subtitle="Step 3 of 3"/>
  <apex:form>
    <apex:pageBlock title="Confirmation">
      <apex:pageBlockButtons>
          <apex:commandButton action="{!step2}" value="Previous"/>
          <apex:commandButton action="{!save}" value="Save"/>
          <apex:commandButton action="{!cancel}" value="Cancel" 
                              onclick="return confirmCancel()" immediate="true"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Account Information">
        <apex:outputField value="{!account.name}"/>
        <apex:outputField value="{!account.site}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Contact Information">
        <apex:outputField value="{!contact.firstName}"/>
        <apex:outputField value="{!contact.lastName}"/>
        <apex:outputField value="{!contact.phone}"/>
        <apex:outputField value="{!role.role}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Opportunity Information">
        <apex:outputField value="{!opportunity.name}"/>
        <apex:outputField value="{!opportunity.amount}"/>
        <apex:outputField value="{!opportunity.closeDate}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

请注意,向导的第三页只是将文本写入带有标记的页面。<apex:outputField>最终页面应如下所示:

新客户机会的第 3 步 巫师

高级 Visualforce Dashboard 组件

Visualforce 页面可用作仪表板组件。仪表板显示源报表中的数据 作为可视化组件,可以是图表、仪表、表格、指标或 Visualforce 页面。 这些组件提供了关键指标的快照和 组织的绩效指标。 每个仪表板最多可以有 20 个组件。

使用标准控制器的 Visualforce 页面不能 在仪表板中使用。要包含在仪表板中,Visualforce 页面必须没有控制器,使用自定义 控制器,或引用绑定到 StandardSetController 类的页面。如果 Visualforce 页面不满足这些要求,则它不会显示为 仪表板组件 Visualforce Page 下拉列表中的选项。以下示例显示了一个 Visualforce 页面,该页面可在仪表板中使用,并使用自定义 列表控制器。它显示与 一位名叫“Babara Levy”的联系人:

<apex:page controller="retrieveCase" tabStyle="Case">
    <apex:pageBlock>
        {!contactName}'s Cases
        <apex:pageBlockTable value="{!cases}" var="c">     
            <apex:column value="{!c.status}"/>
            <apex:column value="{!c.subject}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

此代码显示与 页:

public class retrieveCase {

    public String getContactName() {
        return 'Babara Levy';
    }

    public List<Case> getCases() {
        return [SELECT status, subject FROM Case
                WHERE Contact.name = 'Babara Levy' AND status != 'Closed' limit 5];
    }
}

在仪表板中运行的 Visualforce 页面的示例

集成 Visualforce 和 Google Charts

Google Charts 提供了一种动态 通过不同的可视化呈现数据。结合 Visualforce,Google Charts 可以提供更大的灵活性和分发潜力 而不是使用仪表板。由于图表是通过 URL 生成的, 无论图像位于何处,都可以共享和嵌入可视化效果 允许。

使用前有两个先决条件 Google Charts API。首先是确定如何对 数据。Google Charts API 有三种数据编码类型:文本、 简单且扩展。对于此示例,我们将仅使用简单的 编码。第二种是决定使用哪种类型的图表。为此 例如,用户将在条形图或折线图之间进行选择。自定义控制器有两个重要的 函数 — 和 — 对应于 以上要求:

init()create()

  • 该函数采用 一个数值,并将其转换为 Google Chart 的简单数据编码 类型。如需了解详情,请参阅 Google Charts API 文档中的简单编码数据格式。init()
  • 该函数构造向 Google Charts API 发出请求的 URL。create()

以下代码表示 Visualforce 页面的控制器:

/* This class contains the encoding algorithm for use with the 
   Google chartAPI. */
   
public class GoogleDataEncoding { 
    // Exceptions to handle any erroneous data
    public class EncodingException extends Exception {}
    public class UnsupportedEncodingTypeException 
           extends Exception {}  

    /* The encoding map which takes an integer key and returns the 
       respective encoding value as defined by Google. 
       This map is initialized in init() */
      private Map<Integer, String> encodingMap { get; set; }
    
   /* The maximum encoding value supported for the given encoding 
      type. This value is set during init() */
    private Integer encodingMax { get; set; }
    
    /* The minimum encoding value supported for the given encoding 
       type. This value is set during init() */
    private Integer encodingMin { get; set; }
    
    /* The encoding type according to Google's API. Only SIMPLE 
       is implemented. */
    public enum EncodingType { TEXT, SIMPLE, EXTENDED }
    
    /* The minimum value to use in the generation of an encoding 
       value. */
    public Integer min { get; private set; }
    
    /* The maximum value to use in the generation of an encoding 
       value. */
    public Integer max { get; private set; }
    
    // The encoding type according to the API defined by Google
    public EncodingType eType { get; private set; }       
    
    // Corresponds to the data set provided by the page 
    public String dataSet { get; set; }
    
    // Corresponds to the type of graph selected on the page 
    public String graph { get; set; }
    
    // The URL that renders the Google Chart
    public String chartURL { get; set; }  

    // Indicates whether the chart should be displayed 
    public Boolean displayChart { get; set; }
    
    public GoogleDataEncoding() {
        min = 0;
        max = 61;
        eType = EncodingType.SIMPLE;
        displayChart = false;
        init();
    } 
    
    public PageReference create() {
        String[] dataSetList = dataSet.split(',', 0);
        String mappedValue = 'chd=s:';
        
        chartURL = 'http://chart.apis.google.com/chart?chs=600x300'
         + '&amp;chtt=Time+vs|Distance&amp;chxt=x,y,x,y' 
         + '&amp;chxr=0,0,10,1|1,0,65,5'
         + '&amp;chxl=2:|Seconds|3:|Meters';
        
        if (graph.compareTo('barChart') == 0)
        {
            chartURL += '&amp;cht=bvs';
        }
        else if (graph.compareTo('lineChart') == 0)
        {
            chartURL += '&amp;cht=ls';
        }
        else
        {
            throw new EncodingException('An unsupported chart type' 
                + 'was selected: ' + graph + ' does not exist.');
        }
        
        for(String dataPoint : dataSetList)
        {
            mappedValue += 
               getEncode(Integer.valueOf(dataPoint.trim()));
        }
        
        chartURL += '&amp;' + mappedValue;
        displayChart = true;
        return null;
    }

    
    /* This method returns the encoding type parameter value that 
       matches the specified encoding type. */
   public static String getEncodingDescriptor(EncodingType t) {
        if(t == EncodingType.TEXT) return 't';
        else if(t == EncodingType.SIMPLE) return 's';
        else if(t == EncodingType.EXTENDED) return 'e';
        else return '';
    }  
    
    /* This method takes a given number within the declared 
       range of the encoding class and encodes it according to the 
       encoding type. If the value provided fall outside of the 
       declared range, an EncodingException is thrown. */
    public String getEncode(Integer d) {    
        if(d > max || d < min) {
            throw new EncodingException('Value provided ' + d 
                + ' was outside the declared min/max range (' 
                + min + '/' + max + ')');         
        } 
        else {
            return encodingMap.get(d);
        }
    }  
    
    /* This method initializes the encoding map which is then 
       stored for expected repetitious use to minimize statement 
       invocation. */
    private void init() {
        if(eType == EncodingType.SIMPLE) {
            encodingMax = 61;
            encodingMin = 0;
            encodingMap = new Map<Integer, String>();
            encodingMap.put(0,'A');
            encodingMap.put(1,'B');
            encodingMap.put(2,'C');
            encodingMap.put(3,'D');
            encodingMap.put(4,'E');
            encodingMap.put(5,'F');
            encodingMap.put(6,'G');
            encodingMap.put(7,'H');
            encodingMap.put(8,'I');
            encodingMap.put(9,'J');
            encodingMap.put(10,'K');
            encodingMap.put(11,'L');
            encodingMap.put(12,'M');
            encodingMap.put(13,'N');
            encodingMap.put(14,'O');
            encodingMap.put(15,'P');
            encodingMap.put(16,'Q');
            encodingMap.put(17,'R');
            encodingMap.put(18,'S');
            encodingMap.put(19,'T');
            encodingMap.put(20,'U');
            encodingMap.put(21,'V');
            encodingMap.put(22,'W');
            encodingMap.put(23,'X');
            encodingMap.put(24,'Y');
            encodingMap.put(25,'Z');
            encodingMap.put(26,'a');
            encodingMap.put(27,'b');
            encodingMap.put(28,'c');
            encodingMap.put(29,'d');
            encodingMap.put(30,'e');
            encodingMap.put(31,'f');
            encodingMap.put(32,'g');
            encodingMap.put(33,'h');
            encodingMap.put(34,'i');
            encodingMap.put(35,'j');
            encodingMap.put(36,'k');
            encodingMap.put(37,'l');
            encodingMap.put(38,'m');
            encodingMap.put(39,'n');
            encodingMap.put(40,'o');
            encodingMap.put(41,'p');
            encodingMap.put(42,'q');
            encodingMap.put(43,'r');
            encodingMap.put(44,'s');
            encodingMap.put(45,'t');
            encodingMap.put(46,'u');
            encodingMap.put(47,'v');
            encodingMap.put(48,'w');
            encodingMap.put(49,'x');
            encodingMap.put(50,'y');
            encodingMap.put(51,'z');
            encodingMap.put(52,'0');
            encodingMap.put(53,'1');
            encodingMap.put(54,'2');
            encodingMap.put(55,'3');
            encodingMap.put(56,'4');
            encodingMap.put(57,'5');
            encodingMap.put(58,'6');
            encodingMap.put(59,'7');
            encodingMap.put(60,'8');
            encodingMap.put(61,'9');
        }
    } 
}

Visualforce 页面需要两个输入元素:一个用于图表类型,一个用于 数据集。下面是构造要收集的表单的示例页面 这些信息:

<apex:page controller="GoogleDataEncoding">
    <apex:form >
        <apex:pageBlock 
               title="Create a Google Chart for Time and Distance">
            <apex:outputLabel 
               value="Enter data set, separated by commas: " 
               for="dataInput"/><br/>
            <apex:inputTextArea 
               id="dataInput" title="First Data Point" 
               value="{!dataSet}" rows="3" cols="50"/><br/>
            <apex:selectRadio value="{!graph}" 
               layout="pageDirection">
                <apex:selectOption itemValue="barChart" 
               itemLabel="Horizontal Bar Chart"/>
                <apex:selectOption itemValue="lineChart" 
               itemLabel="Line Chart"/>
            </apex:selectRadio>            
            <apex:commandButton action="{!create}" 
               value="Create"/>
        </apex:pageBlock>
    </apex:form>
    <apex:image url="{!chartURL}" alt="Sample chart" 
               rendered="{!displayChart}"/>
</apex:page>

对于示例,请输入以下序列 数字: .您的页面应呈现以下内容:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55

使用自定义列表控制器批量更新记录

要创建执行批量更新的页面,请使用 StandardSetController 类。

列表控制器跟踪两组记录:包含所有记录的主列表 由筛选器选择,以及包含用户选择的那些记录的辅助列表。 辅助列表通常建立在标准列表视图页面上,用户可以在其中 复选框以选择记录。然后,用户可以单击自定义列表按钮 导航到自定义批量更新页面,该页面使用 prototype 对象应用新的 字段值添加到用户的选择中。原型对象对所有记录进行操作 在用户的选择中。若要在自定义控制器中检索原型对象,请使用 StandardSetController 的方法。 例如,要启用 Opportunity 的批量更新,请使用单数术语来表示其 associated object () 设置所有字段值 选择中的记录:getRecordOpportunity

  1. 创建 Visualforce 页面 叫。massupdatestages
  2. 提供以下控制器:public class selectedSizeWorkaround { ApexPages.StandardSetController setCon; public selectedSizeWorkaround(ApexPages.StandardSetController controller) { setCon = controller; } public integer getMySelectedSize() { return setCon.getSelected().size(); } public integer getMyRecordsSize() { return setCon.getRecords().size(); } }
  3. 提供以下信息 标记:<apex:page standardController="Opportunity" recordSetVar="opportunities" extensions="selectedSizeWorkaround" showHeader="false" id="muopp" > <apex:form id="muform"> <apex:pageMessage summary="Selected Collection Size: {!mySelectedSize}" severity="info" id="mupms" /> <apex:pageMessage summary="Record Set Size: {!myRecordsSize}" severity="info" id="mupmr" /> <apex:pageBlock title="Opportunity Mass-Update" mode="edit" id="mub1"> <apex:pageMessages /> <apex:pageBlockSection id="mus1"> <apex:inputField value="{!opportunity.stagename}" id="stagename"> <apex:actionSupport event="onchange" rerender="muselectedlist"/> </apex:inputField> </apex:pageBlockSection> <apex:pageBlockButtons location="bottom" id="mubut"> <apex:commandButton value="Save" action="{!save}" id="butsav"/> <apex:commandButton value="Cancel" action="{!cancel}" id="butcan"/> </apex:pageBlockButtons> </apex:pageBlock> <apex:pageBlock title="Selected Opportunities" id="muselectedlist"> <apex:pageBlockTable value="{!selected}" var="opp" id="mutab"> <apex:column value="{!opp.name}" id="oppname"/> <apex:column value="{!opp.stagename}" id="oppstage"/> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
  4. 从商机的对象管理设置中,转到按钮、链接和 行动。
  5. 单击“新建”按钮或链接
  6. 将“按钮标签”设置为 ,并将“名称”设置为 。Mass Update StagesMassUpdateStages
  7. 将显示类型设置为和 确保显示复选框(用于多记录选择)是 检查。将“行为”设置为 ,并将“内容源”设置为 。单击您刚刚创建的页面的名称以将其关联 使用此按钮。List ButtonDisplay in existing window with sidebarVisualforce Page
  8. 点击保存
  9. 从商机的对象管理设置中,转到搜索布局。然后 单击“商机列表视图”旁边的“编辑”。
  10. 在“自定义按钮”下,将“批量更新阶段”按钮移动到“所选按钮” 列表。
  11. 点击保存
  12. 单击 Opportunity 选项卡。选择或创建一个筛选器,以显示一些现有的 您想要改变的机会。
  13. 您将在每个结果旁边看到复选框。单击任意数量的复选框 ,然后单击“批量更新阶段”按钮将所选阶段更改为任何值 你愿意。
  14. 点击保存

虽然此示例显示如何更新一个字段,但 原型对象可以被引用并应用于用户的选择;中的任何字段 用户未设置的原型对象不会影响所选记录。记得 字段的属性(例如其必需性)在原型中保持不变 对象。例如,如果您在页面上为必填字段添加输入字段,例如 as ,用户必须输入 字段的值。Opportunity.StageName

注意

你只需要当你 希望您的页面显示或引用用户选择的大小,或者 过滤集。这样的显示很有帮助,因为它为用户提供了有关 集合,该集合将由批量更新修改。selectedSizeWorkaround

自定义控制器和控制器扩展

标准控制器可以提供 Visualforce 页面所需的所有功能 因为它们包含用于标准页面的相同逻辑。例如,如果你 使用标准帐户控制器,单击 Visualforce 页面产生的行为与在 标准帐户编辑页面。但是,如果要覆盖现有功能,请通过以下方式自定义导航 应用程序,使用标注或 Web 服务,或者如果您需要更精细地控制方式 访问页面的信息,您可以编写自定义控制器或控制器 使用 Apex 的扩展:

  • 什么是自定义 控制器和控制器扩展?
  • 构建自定义 控制器
  • 构建一个 控制器扩展
  • 控制器 方法
  • Controller 类 安全
  • 考虑 用于创建自定义控制器和控制器扩展
  • 执行顺序 在 Visualforce 页面中
  • 测试自定义 控制器和控制器扩展
  • 验证规则 和自定义控制器
  • 使用 transient 关键字

什么是自定义控制器和控制器扩展?

自定义控制器是一个 Apex 类,用于实现页面的所有逻辑 无需使用标准控制器。当您希望 Visualforce 页面完全在系统中运行时,请使用自定义控制器 模式,该模式不强制执行当前用户的权限和字段级安全性。控制器扩展是一个 Apex 类,它扩展了 标准或自定义控制器。在以下情况下使用控制器扩展:

  • 您希望利用标准控制器的内置功能,但要覆盖一个或 更多操作,例如编辑、查看、保存或删除。
  • 您想要添加新操作。
  • 您想要构建 Visualforce 页面 尊重用户权限。尽管控制器扩展类在系统模式下执行, 如果控制器扩展扩展标准控制器,则来自标准控制器的逻辑 不在系统模式下执行。相反,它在用户模式下执行,其中权限, 字段级安全性,并应用当前用户的共享规则。

注意

自定义控制器和控制器扩展类在 系统模式,因此它们会忽略用户权限和字段级安全性。但是,您可以 选择是否遵循用户在组织范围内的默认值、角色层次结构和共享 规则,方法是使用 类定义。有关信息,请参阅《使用 、 和关键字》中的“使用 Apex 开发人员 指南。with sharingwith sharingwithout sharinginherited sharing

构建自定义控制器

自定义控制器是使用默认的无参数构造函数的 Apex 类 对于外部的顶级类。不能创建 包括参数。

  1. 在“设置”中,在“快速”中输入 Apex 类 “查找”框,然后选择“Apex 类”。
  2. 单击“新建”。
  3. 单击“版本设置”以指定 Apex 的版本 以及与此类一起使用的 API。如果您的组织已安装托管 软件包,您还可以指定每个软件包的版本 用于此类的托管包。对所有版本使用默认值。 这会将该类与最新版本的 Apex 和 API 相关联,如下所示 以及每个托管包。您可以指定旧版本的托管 软件包,如果您要访问与 最新的包版本。您可以指定旧版本的 Apex 和 用于维护特定行为的 API。
  4. 在类编辑器中,输入类的 Apex 代码。单个类可以是 长度不超过 100 万个字符,不包括注释、测试方法或 使用 @isTest 定义的类。
  5. 单击保存”以保存更改并返回到课程 详细信息屏幕,或单击“快速保存”以保存更改 ,然后继续编辑您的课程。您的 Apex 类必须正确编译才能 您可以保存您的课程。

下面的类是自定义控制器的简单示例:

public class MyController {

    private final Account account;

    public MyController() {
        account = [SELECT Id, Name, Site FROM Account 
                   WHERE Id = :ApexPages.currentPage().getParameters().get('id')];
    }

    public Account getAccount() {
        return account;
    }

    public PageReference save() {
        update account;
        return null;
    }
}

以下 Visualforce 标记显示了如何在 一页:

<apex:page controller="myController" tabStyle="Account">
    <apex:form>
        <apex:pageBlock title="Congratulations {!$User.FirstName}">
            You belong to Account Name: <apex:inputField value="{!account.name}"/>
            <apex:commandButton action="{!save}" value="save"/>
        </apex:pageBlock>
    </apex:form>
</apex:page>

由于组件的属性,自定义控制器与页面相关联。controller<apex:page>

与标准控制器和控制器扩展一样,自定义控制器方法可以 用符号引用 关联的页面标记。在上面的示例中,该方法由标记的属性引用,而标记使用其属性引用该方法。{! }getAccount<apex:inputField>value<apex:commandButton>saveaction

注意

与其他 Apex 类一样,所有自定义控制器都在系统模式下运行。因此,当前用户的凭据不用于 执行控制器逻辑,用户权限和字段级安全性执行 不适用。

您可以选择自定义控制器是否遵循用户的 使用类中的关键字的组织范围的默认值、角色层次结构和共享规则 定义。with sharing有关信息,请参阅《使用 、 和关键字》中的“使用 Apex 开发人员 指南。with sharingwithout sharinginherited sharing

自定义控制器还可用于 创建新记录。为 例:

public class NewAndExistingController {

    public Account account { get; private set; }

    public NewAndExistingController() {
        Id id = ApexPages.currentPage().getParameters().get('id');
        account = (id == null) ? new Account() : 
            [SELECT Name, Phone, Industry FROM Account WHERE Id = :id];
    }

    public PageReference save() {
        try {
            upsert(account);
        } catch(System.DMLException e) {
            ApexPages.addMessages(e);
            return null;
        }
        //  After successful Save, navigate to the default view page
        PageReference redirectSuccess = new ApexPages.StandardController(Account).view();
        return (redirectSuccess);
    }
}

以下 Visualforce 标记显示了如何在 一页:

<apex:page controller="NewAndExistingController" tabstyle="Account">
    <apex:form>
        <apex:pageBlock mode="edit">
            <apex:pageMessages/>
            <apex:pageBlockSection>
                <apex:inputField value="{!account.name}"/>
                <apex:inputField value="{!account.phone}"/>
                <apex:inputField value="{!account.industry}"/>
            </apex:pageBlockSection>
            <apex:pageBlockButtons location="bottom">
                <apex:commandButton value="Save" action="{!save}"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>

构建控制器扩展

控制器扩展是任何 Apex 类,其中包含一个构造函数,该构造函数采用单个 类型为 或 的参数,其中 是所需的自定义控制器的名称 以扩展。ApexPages.StandardControllerCustomControllerNameCustomControllerName

下面的类是控制器扩展的简单示例:

public class myControllerExtension {

    private final Account acct;
    
    // The extension constructor initializes the private member
    // variable acct by using the getRecord method from the standard
    // controller.
    public myControllerExtension(ApexPages.StandardController stdController) {
        this.acct = (Account)stdController.getRecord();
    }

    public String getGreeting() {
        return 'Hello ' + acct.name + ' (' + acct.id + ')';
    }
}

以下 Visualforce 标记 显示了如何在页面中使用上面的控制器扩展:

<apex:page standardController="Account" extensions="myControllerExtension">
    {!greeting} <p/>
    <apex:form>
        <apex:inputField value="{!account.name}"/> <p/>
        <apex:commandButton value="Save" action="{!save}"/>
    </apex:form>
</apex:page>

扩展使用组件的属性与页面相关联。extensions<apex:page>

与所有控制器方法一样,控制器扩展方法可以在页面标记中使用表示法进行引用。在 上面的例子,表达式 页面顶部引用了控制器扩展的方法。{! }{!greeting}getGreeting

由于此扩展与 Account 标准控制器结合使用,因此 还提供标准控制器方法。例如,标记中的属性检索 使用标准控制器功能的帐户。同样,标签引用标准 account 方法及其属性。value<apex:inputField><apex:commandButton>saveaction可以通过逗号分隔为单个页面定义多个控制器扩展 列表。这允许重写具有相同名称的方法。例如,如果 以下页面 存在:

<apex:page standardController="Account" 
    extensions="ExtOne,ExtTwo" showHeader="false">
    <apex:outputText value="{!foo}" />
</apex:page>

跟 以下 扩展:

public class ExtOne {
    public ExtOne(ApexPages.StandardController acon) { }

    public String getFoo() {
        return 'foo-One';
    }
}
public class ExtTwo {
    public ExtTwo(ApexPages.StandardController acon) { }

    public String getFoo() {
        return 'foo-Two';
    }
}

这 组件的值 呈现为 .覆盖由以下任一因素定义 方法在“最左边”扩展中定义,或者在 在逗号分隔列表中的第一个。因此,的方法覆盖了 的方法。

<apex:outputText>foo-OnegetFooExtOneExtTwo

注意

与其他 Apex 类一样,控制器扩展在 系统模式。因此,当前用户的凭据不用于 执行控制器逻辑,用户权限和字段级安全性执行 不适用。然而 如果控制器扩展扩展标准控制器,则来自标准控制器的逻辑 控制器不在系统模式下执行。相反,它在用户模式下执行,其中 当前用户的权限、字段级安全性和共享规则 应用。

您可以选择控制器扩展是否遵循用户的 使用类定义中的关键字实现组织范围的默认值、角色层次结构和共享规则。 有关信息,请参阅《使用 、 和关键字》中的“使用 Apex 开发人员 指南。with sharingwith sharingwithout sharinginherited sharing

构建自定义列表控制器

自定义列表控制器类似于标准列表控制器。自定义列表 控制器可以实现您定义的 Apex 逻辑,以显示或操作一组 记录。例如,您可以创建以下自定义列表控制器,基于 一个 SOQL 查询:

public class opportunityList2Con {
    // ApexPages.StandardSetController must be instantiated
    // for standard list controllers
    public ApexPages.StandardSetController setCon {
        get {
            if(setCon == null) {
                setCon = new ApexPages.StandardSetController(Database.getQueryLocator(
                    [SELECT Name, CloseDate FROM Opportunity]));
            }
            return setCon;
        }
        set;
    }

    // Initialize setCon and return a list of records
    public List<Opportunity> getOpportunities() {
        return (List<Opportunity>) setCon.getRecords();
    }
}

注意

这 返回的 sObject 列表是 变。例如,你不能调用它。您可以对 列表,但无法向列表添加项目或从列表中删除项目 本身。getRecords()clear()以下 Visualforce 标记 展示了如何在 页:

<apex:page controller="opportunityList2Con">
    <apex:pageBlock>
        <apex:pageBlockTable value="{!opportunities}" var="o">
            <apex:column value="{!o.Name}"/>
            <apex:column value="{!o.CloseDate}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

您还可以创建一个自定义列表控制器,该控制器使用反联接和半联接作为 SOQL 查询。以下代码是作为帐户的扩展实现的 标准 控制器:

public with sharing class AccountPagination {
    private final Account acct;  

    // The constructor passes in the standard controller defined
    // in the markup below
    public AccountPagination(ApexPages.StandardSetController controller) {
        this.acct = (Account)controller.getRecord(); 
    }    
    
    public ApexPages.StandardSetController accountRecords {
        get {
            if(accountRecords == null) {
                accountRecords = new ApexPages.StandardSetController(
                    Database.getQueryLocator([SELECT Name FROM Account WHERE Id NOT IN 
                        (SELECT AccountId FROM Opportunity WHERE IsClosed = true)]));
            }
            return accountRecords;
        }
        private set;
    }
    public List<Account> getAccountPagination() {
         return (List<Account>) accountRecords.getRecords();
    }  
}

这 显示这些记录的页面混合使用标准列表控制器操作,但 依赖于循环访问从自定义列表返回的记录 控制器:

<apex:page standardController="Account" recordSetVar="accounts" extensions="AccountPagination">
    <apex:pageBlock title="Viewing Accounts">
        <apex:form id="theForm">
            <apex:pageBlockSection >
                <apex:dataList value="{!accountPagination}" var="acct" type="1">
                    {!acct.name}
                </apex:dataList>
            </apex:pageBlockSection>
            <apex:panelGrid columns="2">
                <apex:commandLink action="{!previous}">Previous</apex:commandlink>
                <apex:commandLink action="{!next}">Next</apex:commandlink>
            </apex:panelGrid>
        </apex:form> 
    </apex:pageBlock>
</apex:page>

控制器方法

Visualforce 标记可以使用 以下类型的控制器扩展和自定义控制器方法:

  • 行动
  • 吸气剂
  • 二传手

操作方法

操作方法在页面执行逻辑或导航时 事件发生,例如当用户单击按钮或将鼠标悬停在 页。可以通过在以下参数之一的参数中使用表示法从页面标记中调用操作方法 标签:{! }action

  • <apex:commandButton> 创建一个按钮,该按钮调用 行动
  • <apex:commandLink> 创建一个调用操作的链接
  • <apex:actionPoller> 定期调用操作
  • <apex:actionSupport> 创建一个事件(例如 “onclick”、“onmouseover”等) 命名组件,调用操作
  • <apex:actionFunction> 定义了一个新的 JavaScript 调用操作的函数
  • <apex:page> 调用操作 加载页面时

例如,在“生成自定义控制器”的示例页中,控制器的方法 由标记的参数调用。其他 定义操作方法中讨论了操作方法的示例。saveaction<apex:commandButton>

Getter 方法

Getter 方法从控制器返回值。每个值 由控制器计算并显示在页面中必须具有相应的 getter 方法,包括任何布尔变量。例如,在生成自定义控制器的示例页中,控制器包含一个方法。此方法允许页面标记引用 带有表示法的控制器类。标记的参数使用此表示法来 访问帐户,并使用点表示法显示帐户的名称。Getter 方法 必须始终命名为 。getAccountaccount{! }value<apex:inputField>getVariable

重要

它 getter 方法的最佳实践是幂等的,即没有 副作用。例如,不要递增变量,写日志消息, 或将新记录添加到数据库。Visualforce 没有 定义调用 getter 方法的顺序,或者调用它们的次数 在处理请求的过程中调用。设计 getter 方法以产生 相同的结果,无论它们被调用一次还是多次用于单个页面 请求。

Setter 方法

Setter 方法将用户指定的值从页面标记传递给 控制器。控制器中的任何 setter 方法都会在 任何操作方法。

例如,以下标记显示一个页面 实现 Leads 的基本搜索功能。关联的控制器包括 getter 和 setter 方法,然后使用搜索文本来 当用户单击 Go! 时发出 SOSL 查询。虽然 标记不会显式调用搜索文本 setter 方法,它会执行 在操作方法之前,当 用户单击命令 按钮:doSearch

<apex:page controller="theController">
   <apex:form>
      <apex:pageBlock mode="edit" id="block">
         <apex:pageBlockSection>
            <apex:pageBlockSectionItem>
               <apex:outputLabel for="searchText">Search Text</apex:outputLabel>
               <apex:panelGroup>
                  <apex:inputText id="searchText" value="{!searchText}"/>
                  <apex:commandButton value="Go!" action="{!doSearch}" 
                                      rerender="block" status="status"/>
               </apex:panelGroup>
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
        <apex:actionStatus id="status" startText="requesting..."/>
        <apex:pageBlockSection title="Results" id="results" columns="1">
           <apex:pageBlockTable value="{!results}" var="l" 
                               rendered="{!NOT(ISNULL(results))}">
              <apex:column value="{!l.name}"/>
              <apex:column value="{!l.email}"/>
              <apex:column value="{!l.phone}"/>
           </apex:pageBlockTable>
        </apex:pageBlockSection>
      </apex:pageBlock>
   </apex:form>
</apex:page>

这 以下类是页面标记的控制器 以上:

public class theController {

    String searchText;
    List<Lead> results;

    public String getSearchText() {
        return searchText;
    }

    public void setSearchText(String s) {
        searchText = s;
    }

    public List<Lead> getResults() {
        return results;
    }

    public PageReference doSearch() {
        results = (List<Lead>)[FIND :searchText RETURNING Lead(Name, Email, Phone)][0];
        return null;
    }
}

而 从控制器访问值始终需要 getter 方法,它是 并不总是需要包含 setter 方法以将值传递到控制器中。如果 Visualforce 组件是 绑定到存储在控制器中的 sObject,sObject 的字段为 如果用户更改,则自动设置,只要保存或更新 sObject 即可 通过相应的动作方法。示例中显示了此行为的示例 页面。

Setter 方法必须始终命名为 。setVariable

重要

最佳做法是 setter 方法是幂等的,即没有副作用。例如,不要 递增变量、写入日志消息或向数据库添加新记录。Visualforce 没有 定义调用 setter 方法的顺序,或者调用它们的次数 在处理请求的过程中调用。设计 setter 方法以产生 相同的结果,无论它们被调用一次还是多次用于单个页面 请求。

使用自定义扩展获取和设置数据,或者 控制器

有 无法保证 Apex 方法和变量的处理顺序 控制器扩展或自定义控制器。因此,不允许控制器和 扩展类依赖于正在运行的另一个方法,请直接调用该方法。 这特别适用于设置变量和从数据库访问数据。

例如,在以下自定义控制器中,第一个方法 , 始终返回正确的 值,因为它不假定 contact 变量已存在。但是,第二种方法有时会返回 正确的值,但不是每次都正确 设置。getContactMethod1cgetContactMethod2c

public class conVsBad {
    Contact c;

    public Contact getContactMethod1() {
        if (c == null) c = [SELECT Id, Name FROM Contact LIMIT 1];
        return c;
    }

    public Contact getContactMethod2() {
        return c;
    }
}

这 以下自定义控制器具有完全相同的方法。但是,调用 ,因此变量始终被设置,并且始终包含 返回时的值正确。getContactMethod2contactMethod1c

public class conVsGood {
    Contact c;

    public Contact getContactMethod1() {
        if(c == null) c = [SELECT Id, Name FROM Contact LIMIT 1];
        return c;
    }

    public Contact getContactMethod2() {
        return getContactMethod1();
    }
}

这 以下标记显示调用这些控制器的两个页面。Visualforce 标记是 相同,只有控制器名称是 改变:

<apex:page controller="conVsGood">
    getContactMethod2(): {!contactMethod2.name}<br/>
    getContactMethod1(): {!contactMethod1.name}
</apex:page>
<apex:page controller="conVsBad">
    getContactMethod2(): {!contactMethod2.name}<br/>
    getContactMethod1(): {!contactMethod1.name}
</apex:page>

控制器类安全性

与其他 Apex 类一样,您可以指定用户是否可以在自定义中执行方法 控制器或基于用户配置文件的控制器扩展类。

注意

如果您在组织中安装了托管软件包,则只能为 Apex 设置安全性 包中声明为 或 的类 对于包含声明为 的方法的类。globalwebService

如果用户具有 Author Apex 权限,则可以访问所有 Apex 关联组织中的类,而不考虑个人的安全设置 类。

仅在顶层检查 Apex 类的权限。例如,类 A 调用类 B. 用户 X 有一个可以访问 A 类但不能访问 B 类的配置文件。用户 X 可以在 B类,但只能通过A类;用户 X 不能直接调用类 B。同样,如果 Visualforce 页面使用带有关联控制器的自定义组件,仅检查安全性 用于与页面关联的控制器。与自定义组件关联的控制器 无论权限如何,都会执行。

要从类列表页面设置 Apex 类安全性,请执行以下操作:

从类列表中设置 Apex 类访问权限 页

要从类详细信息页面设置 Apex 类安全性,请执行以下操作: 从类详细信息设置 Apex 类访问权限 页

处理大型数据集

Visualforce 自定义控制器和 控制器扩展受 Apex 调控器限制的约束。有关调速器的详细信息 限制,请参阅执行调控器和限制。此外,Visualforce 迭代组件(如 和)限制为 中最多 1,000 个项目 他们迭代的集合。<apex:pageBlockTable><apex:repeat>

有时,您的 Visualforce 页面可能会 需要处理或显示更大的数据集,但不需要对其进行修改 数据;例如,如果您要提供自定义报告和分析。Visualforce 为开发人员提供了”只读模式“,放宽了对数量的限制 可以在一个请求中查询的行,并增加对收集数量的限制 可以在页面内迭代的项目。您可以为整个页面指定只读模式,也可以在有某些限制的情况下指定只读模式 单个组件或方法。

注意

只有在以下情况下,才能循环访问大型数据集 为整个页面指定只读模式。

为整个页面设置只读模式

要为整个页面启用只读模式,请将组件上的属性设置为 。

readOnly<apex:page>true

例如,下面是一个以只读模式处理的简单页面:

<apex:page controller="SummaryStatsController" readOnly="true">
    <p>Here is a statistic: {!veryLargeSummaryStat}</p>
</apex:page>

此页面的控制器也很简单,但说明了如何 您可以计算在页面上显示的汇总统计信息:

public class SummaryStatsController {
    public Integer getVeryLargeSummaryStat() {
        Integer closedOpportunityStats = 
            [SELECT COUNT() FROM Opportunity WHERE Opportunity.IsClosed = true];
        return closedOpportunityStats;
    }
}

通常,对单个 Visualforce 页面请求的查询检索的行数可能不会超过 50,000 行。在只读模式下,此限制已放宽,最多允许查询 1,000,000 行。

除了查询更多行之外,该属性还增加了集合中可以 使用 、 和 等组件进行迭代。此限制从 1,000 增加到 项目到 10,000。下面是一个简单的控制器和页面演示:readOnly<apex:dataTable><apex:dataList><apex:repeat>

public class MerchandiseController {

    public List<Merchandise__c> getAllMerchandise() {
        List<Merchandise__c> theMerchandise = 
            [SELECT Name, Price__c FROM Merchandise__c LIMIT 10000];
        return(theMerchandise);
    }
}
<apex:page controller="MerchandiseController" readOnly="true">
    <p>Here is all the merchandise we have:</p>
    <apex:dataTable value="{!AllMerchandise}" var="product">
        <apex:column>
            <apex:facet name="header">Product</apex:facet>
            <apex:outputText value="{!product.Name}" />
        </apex:column>
        <apex:column>
            <apex:facet name="header">Price</apex:facet>
            <apex:outputText value="{!product.Price__c}" />
        </apex:column>
    </apex:dataTable>
</apex:page>

虽然对整个页面使用只读模式的 Visualforce 页面不能使用数据操作语言 (DML) 操作, 它们可以调用影响表单和其他用户的 getter、setter 和 action 方法 界面元素,进行其他只读查询,依此类推。

为控制器方法设置只读模式

Visualforce 控制器方法 可以使用 Apex 注释,但有一些重要的限制,即使页面本身不是只读模式。

ReadOnly带有注释的 Visualforce 控制器方法会自动利用只读模式。 但是,对注释的限制意味着 对于 Visualforce 控制器方法, 只读方法也必须具有批注。注解要求 方法是:

@ReadOnly@ReadOnly@RemoteAction@RemoteAction

  • Either 或globalpublic
  • static

启用只读模式 必须在顶部使用注释 level 方法调用。如果顶级方法调用没有注解,则对最大查询量的正常限制 对整个请求强制执行行,即使辅助方法使用 注释了 。@ReadOnly@ReadOnly@ReadOnly

在控制器方法上使用批注 允许您检索更大的记录集合作为 Visualforce 表达式的结果。但是,它 不会增加迭代组件的集合中的最大项数。如果 想要遍历更大的结果集合,需要为 整个页面。@ReadOnly

创建自定义控制器和控制器的注意事项 扩展

创建控制器扩展和自定义控制器时,请注意以下注意事项:

  • 除非一个类具有定义为 的方法,否则自定义扩展和控制器类和方法通常 定义为 。如果类包含 Web 服务方法,则必须将其定义为 。webServicepublicglobal
  • 从数据库返回数据时,请使用集、映射或列表。这将使您的代码 效率更高,因为代码对数据库的访问更少。
  • Visualforce 控制器扩展的 Apex 调控器限制和 自定义控制器与匿名块或 WSDL 方法的限制相同。为 有关调控器限制的详细信息,请参阅附录中的执行调控器和限制。
  • 如果要构建自定义控制器或控制器扩展,请注意这样做 不会无意中暴露通常对用户隐藏的敏感数据。考虑 在类上使用关键字 用于强制执行权限的定义。另外,使用受保护的 Web 服务时也要小心 作为配置文件的顶级入口点,但一旦它们被执行,就会在系统上下文中执行 发起。with sharing
  • Apex 方法和变量不按保证顺序实例化。查看更多 信息,请参阅使用自定义扩展获取和设置数据或 控制器。
  • 不能在“getxxx”中使用数据操作语言 (DML) 操作 方法。例如,如果控制器具有方法,则不能在该方法中使用 或 创建对象。getNameinsertupdate
  • 不能在构造函数方法中使用数据操作语言 (DML) 操作 控制器。
  • 您不能在 “getxxx”或“setxxx”方法,或者在 控制器的构造函数。@future
  • Primitive Apex 数据类型(如 String 或 Integer)按值传递给 组件的控制器。
  • 非原始 Apex 数据类型(如 lists 和 sObjects)通过引用传递给 组件的控制器。这意味着,如果组件的控制器更改了 帐户,更改可在页面的控制器中找到。
  • 如果您的组织使用个人帐户
    • 使用自定义引用客户记录的名称字段时 控制器使用您必须在 您的查询。<apex:inputField>isPersonAccount
    • 如果创建新帐户并设置名称,则记录将为 企业帐户。如果您创建一个新帐户并设置姓氏, 这将是一个个人帐户。
    • 最佳做法是创建一个自定义名称公式字段,该字段将正确呈现 个人帐户和企业帐户,然后使用该字段而不是 Visualforce 页面中的标准字段。
    • 如果您计划将 Visualforce 页面包含在 Salesforce AppExchange 软件包中, 在控制器或控制器扩展中,不能显式引用字段 仅存在于个人帐户中。

Visualforce 页面中的执行顺序

当用户查看 Visualforce 页面时,控制器、扩展和组件的实例会关联 页面由服务器创建。这些顺序 元素的执行会影响页面向 用户。要完全了解 Visualforce 页面上元素的执行顺序,您必须首先了解页面的生命周期,即页面在 用户会话的过程。页面的生命周期不是确定的 仅通过页面的内容,还取决于页面的请求方式。 有两种类型的 Visualforce 页面请求:

  • get 请求是初始请求 当用户输入 URL 或 单击链接或按钮,将用户带到新页面。
  • 回发请求是 当用户交互需要页面更新时进行,例如当 用户单击“保存”按钮并触发 保存操作。

有关这两种请求的具体细节,请举例说明 页面的生命周期,以及有关如何处理执行顺序的提示 在编写自己的自定义控制器和控制器扩展时, 看:

  • Visualforce 页面获取请求的执行顺序
  • Visualforce 页面回发请求的执行顺序
  • Visualforce 页面执行顺序示例

注意

Visualforce 页面请求的最大响应大小必须低于15兆字节.

Visualforce 页面获取请求的执行顺序

get 请求是初始请求 当用户输入 URL 或 单击链接或按钮,将用户带到新页面。下图显示了 Visualforce 页面如何与控制器扩展或自定义控制器进行交互 GET 请求期间的类:

在上图中,用户最初请求页面,或者 通过输入 URL 或单击链接或按钮。此初始页面 request 称为 get 请求

  1. 关联的自定义控制器上的构造函数方法或 调用控制器扩展类,实例化控制器 对象。
  2. 如果页面包含任何自定义组件,则会创建这些组件并 任何关联的自定义控制器或控制器上的构造函数方法 扩展被执行。如果在自定义组件上设置了属性 使用表达式,表达式在构造函数之后计算 被评估。
  3. 然后,页面对页面上的任何自定义组件执行任何属性。执行方法后,表达式 ,则计算组件上的属性,以及所有其他方法调用,例如获取 或设置属性值。assignToassignToaction<apex:page>
  4. 如果页面包含组件,则维护状态所需的所有信息 的数据库在页面请求之间被保存为加密的视图状态。每当页面出现以下情况时,视图状态都会更新: 更新。<apex:form>
  5. 生成的 HTML 将发送到浏览器。如果有任何客户端 页面上的技术,例如 JavaScript,浏览器执行 他们。

当用户与页面交互时,页面会联系控制器 对象,以执行 action、getter 和 setter 方法。一旦用户发出新的 get 请求,视图状态和 控制器对象将被删除。

注意

如果用户被重定向到 使用相同控制器和相同或适当子集的页面 的控制器扩展,则会发出回发请求。回发时 发出请求,视图状态保持不变。

如果用户交互需要页面更新,例如当 用户单击触发保存的“保存”按钮 操作时,将发出回发请求。更多信息 有关回发请求,请参见 Visualforce 页面回发请求的执行顺序。

有关 get 请求的特定示例,请参见 Visualforce 页面执行顺序示例。

Visualforce 页面回发请求的执行顺序

回发请求是 当用户交互需要页面更新时进行,例如当 用户单击“保存”按钮并触发 保存操作。下图显示了 Visualforce 页面如何与控制器扩展或自定义控制器进行交互 回发请求期间的类:

  1. 在回发请求期间,将对视图状态进行解码并使用 作为更新页面上值的基础。注意组件 替换为属性 设置为绕过此 阶段。换言之,操作已执行,但未执行 对输入执行验证,页面上不会更改任何数据。immediatetrue
  2. 对视图状态进行解码后,将计算表达式并 在控制器和任何控制器扩展上设置方法,包括 在为自定义组件定义的控制器中执行 set 方法。除非所有方法都 已成功执行。例如,如果其中一个方法更新 属性,并且由于验证规则或 数据类型不正确,数据不会更新,页面会重新显示 以及相应的错误消息。
  3. 将执行触发回发请求的操作。如果 该操作成功完成,数据将更新。如果回发 request 将用户返回到同一页面,视图状态将更新。注意上的属性 组件 在回发请求期间不计算。它仅在 GET 请求。action<apex:page>
  4. 生成的 HTML 将发送到浏览器。

如果回发请求指示页面重定向和重定向 是使用相同控制器和 原始页面的控制器扩展,回发请求 为该页面执行。否则,将执行 get 请求 页面。如果回发请求包含组件,则只有 返回回发请求的 ID 查询参数。<apex:form>

提示

可以使用 上的属性来控制是否执行回发或获取请求。如果设置为 true,则 get 请求被执行。将其设置为 false 不会忽略限制 当且仅当目标 使用相同的控制器和适当的扩展子集。如果设置为 false,则 Target 不满足这些要求,将发出 GET 请求。setRedirectpageReferencesetRedirectsetRedirect

一旦用户被重定向到另一个页面,视图状态和 控制器对象将被删除。

有关回发请求的特定示例,请参见 Visualforce 页面执行顺序示例。

Visualforce 页面执行示例 次序

以下示例说明了 Visualforce 页面作为用户的生命周期 与它交互。示例中使用的页面旨在显示有关以下内容的信息 帐户,页面上变量的值,并允许用户编辑详细信息 如果键值设置为除 false 之外的任何值,则帐户。

设置 Visualforce 页面 示例:

  1. 为名为componentControllerpublic class componentController { public String selectedValue { get; set { editMode = (value != null); // Side effect here - don't do this! selectedValue = value; } } public Boolean editMode {get; private set;} }
  2. 创建一个名为 :editMode<apex:component controller="componentController"> <apex:attribute name="value" type="String" description="Sample component." assignTo="{!selectedValue}"/> <p> Value = {!value}<br/> selectedValue = {!selectedValue}<br/> EditMode = {!EditMode} </p> </apex:component>
  3. 创建一个名为 :myControllerpublic with sharing class myController { private final Account account; public myController() { account = [select id, name, site, NumberOfEmployees, Industry from Account where id = :ApexPages.currentPage().getParameters().get('id')]; } public Account getAccount() { return account; } public PageReference save() { update account; return null; } public PageReference cancel() { return null; } }
  4. 创建一个名为lifecyclepublic with sharing class lifecycle { private final Account acct; Integer EmpAdd; public lifecycle(myController controller) { this.acct = (Account)controller.getAccount(); } public String getGreeting() { return acct.name + ' Current Information'; } public void resetEmp() { acct.numberofemployees = 10; update acct; } }
  5. 创建一个名为 :setEmps<apex:page controller="myController" tabStyle="Account" extensions="lifecycle" action="{!resetEmp}"> <apex:messages /> <apex:pageBlock title="{!greeting}"> <apex:outputLabel value="{!$ObjectType.account.fields.Name.label}: " for="acctName"/> <apex:outputField value="{!account.name}" id="acctName"/> <br/> <apex:outputLabel value="{!$ObjectType.account.fields.NumberOfEmployees.label}: " for="emps"/> <apex:outputField value="{!account.NumberOfEmployees}" id="emps"/> <br/> </apex:pageBlock> <apex:pageBlock title="Variable values"> <c:editMode value="{!$CurrentPage.parameters.key}"/> </apex:pageBlock> <apex:form rendered="{!$CurrentPage.parameters.key = 'true'}"> <apex:pageBlock title="Update the Account" id="thePageBlock"> <apex:pageBlockSection columns="1"> <apex:inputField id="aName" value="{!account.name}"/> <apex:inputField value="{!account.NumberOfEmployees}"/> <apex:pageBlockSectionItem> <apex:outputLabel value="{!$ObjectType.account.fields.Industry.label}" for="acctIndustry"/> <apex:actionRegion> <apex:inputField value="{!account.Industry}" id="acctIndustry"> <apex:actionSupport event="onchange" rerender="thePageBlock" status="status"/> </apex:inputField> </apex:actionRegion> </apex:pageBlockSectionItem> </apex:pageBlockSection> <apex:pageBlockButtons location="bottom"> <apex:commandButton action="{!save}" value="Save"/> <apex:commandButton action="{!cancel}" value="Cancel" immediate="true"/> </apex:pageBlockButtons> </apex:pageBlock> </apex:form> </apex:page>

获取请求示例一

对于第一个示例,请访问页面 使用格式为 https:// Salesforce_instance/apex/setEmps?id=recordId 的 URL, 其中 是实例的名称(例如,),并且是 组织中的客户记录(例如,)。您将看到一个页面,其内容类似于 以后:setEmpsSalesforce_instancena1recordID001D000000IRt53

让我们跟踪生命周期,看看为什么页面会显示它的作用。既然你已经 通过输入 URL 直接请求页面,此页面是 get 的结果 请求,而不是回发请求。

  1. 在 get 请求中发生的第一件事是 调用自定义控制器和控制器扩展。该方法是 控制器,方法是 扩展上的构造函数。这些对象被执行,并且这两个对象现在存在。 控制器现在有一个名为 的变量,它是使用 URL 中的参数的查询的结果,用于标识 要查询的帐户对象。该扩展现在有一个变量,称为 ,该变量是通过调用 控制器。该方法具有 无副作用。myControllerlifecycleaccountidacctgetAccountgetAccount
  2. get 请求的下一步是创建自定义组件并执行 关联控制器或控制器扩展上的构造函数方法。页面 包括一个自定义 元件:<c:editMode value="{!$CurrentPage.parameters.key}"/>这 自定义组件具有关联的控制器,但控制器没有 Explicit 构造函数。与所有没有显式的 Apex 对象一样 构造函数,对象是使用隐式的、无参数的、公共的 构造 函数。作为创建自定义组件的一部分,value 属性 在自定义组件上设置。在这种情况下,它等于 表达式 。由于我们没有在 URL 中指定属性,因此设置为 null。{!$CurrentPage.parameters.key}keyvalue
  3. 创建自定义组件后,将执行这些自定义组件上的所有属性。属性是 setter 将此属性的值赋给 关联的自定义组件控制器。自定义组件确实有一个方法,因此会执行它。该方法将 on 属性设置为属性。该属性设置为 null,因此设置为 null。assignToassignToeditModeassignToassignToselectedValuevaluevalueselectedValue
  4. get 请求的下一步是计算组件、表达式和 所需的 getter 和 setter 方法。虽然我们将在下面逐步介绍这些, 请记住,这些评估的顺序是不确定的,可能是 与以下内容不同:action<apex:page>
    • 组件 具有一个属性,该属性 调用 上的方法 扩展。该方法将对象上的字段设置为 10。<apex:page>actionresetEmpnumberofemployeesacct
    • 页面上有几个计算表达式。让我们关注三个:
      • <apex:pageBlock title=”{!greeting}”>该属性调用 getter 方法 生命周期扩展。这在页面上呈现为 “全球媒体时事信息”。title<apex:pageblock>getGreeting
      • <apex:form rendered=”{!$CurrentPage.parameters.key = ‘true’}”>设置了 on 属性 基于参数的值。我们在调用 页面,因此不会呈现表单。rendered<apex:form>keykey
      • Value = {!value}<br/> selectedValue = {!selectedValue}<br/> EditMode = {!EditMode}此表达式出现在自定义中 元件。我们已经讨论过了,并且已经准备好了 但是,对于 null,则 的值尚未 已知。 是 上的布尔变量。它是根据 是否是 等于 null:valueselectedValueEditModeEditModecomponentControllervalueset { selectedValue = value; // Side effect here - don't do this! editMode = (value != null); }由于为 null,因此设置为 自。注意 但是,setter 方法存在副作用 为。如 部分设置,我们还设置为 。由于为 null, 这不会改变任何事情,但这种行为有一个 影响在后面的示例中。valueEditModefalseEditModeeditModeselectedValuevaluevalue
    • 其他表达式和方法的计算方式类似 方式。
  5. 由于组件不是 呈现时,不会创建视图状态。<apex:form>
  6. get 请求的最后一步是将 HTML 发送到浏览器,浏览器 呈现 HTML。

获取请求示例二

对于第二个示例,使用格式为 https:// Salesforce_instance/apex/setEmps?id=recordId&key=false 的 URL 访问页面。 其中 是实例的名称(例如,),并且是 ID 组织中的客户记录(例如,)。与第一个示例不同,此示例包括 第二个参数 key=false。您将看到一个包含内容的页面 类似于以下内容:setEmpsSalesforce_instancena1recordID001D000000IRt53

让我们再次跟踪生命周期。此页面也是 get 请求的结果:

  1. 在 get 请求中发生的第一件事是 调用自定义控制器和控制器扩展。该方法是 上的构造函数 控制器和方法 是扩展的构造函数。这些被执行,现在有两个对象 存在。控制器现在有一个变量,称为 ,它是查询的结果,该查询使用 URL 中的参数来标识哪个 要查询的客户记录。该扩展现在有一个变量,称为 ,该变量是通过调用 控制器。myControllerlifecycleaccountidacctgetAccount
  2. get 请求的下一步是创建自定义组件并执行 关联控制器或控制器扩展上的构造函数方法。页面 包括一个自定义 元件:<c:editMode value="{!$CurrentPage.parameters.key}"/>这 自定义组件具有没有构造函数的关联控制器,因此 控制器对象是使用隐式的、无参数的、公共的 构造 函数。作为创建自定义组件的一部分,自定义组件上的属性 已设置。在这种情况下,它等于表达式 的结果。我们 将属性指定为 false,因此设置为 假。value{!$CurrentPage.parameters.key}keyvalue
  3. 创建自定义组件后,将执行这些自定义组件上的所有属性。该方法将 on 属性设置为属性。该属性设置为 false,因此设置为 false。assignToassignToselectedValuevaluevalueselectedValue
  4. get 请求的下一步是计算组件、表达式和 所需的 getter 和 setter 方法。虽然我们将在下面逐步介绍这些, 请记住,这些评估的顺序是不确定的,可能是 与以下内容不同:action<apex:page>
    • 组件 具有一个属性,该属性 调用 上的方法 扩展。该方法将对象上的字段设置为 10。<apex:page>actionresetEmpnumberofemployeesacct
    • 在页面上的表达式中,让我们看看我们选择的三个是如何计算的:<apex:pageBlock title=”{!greeting}”>属性 on 调用 getter 方法 on 生命周期扩展。它在页面上呈现为 “全球媒体时事信息”。title<apex:pageblock>getGreeting<apex:form rendered=”{!$CurrentPage.parameters.key = ‘true’}”>呈现的属性 on 是根据 参数。 我们设置为何时 调用页面,因此不会呈现表单。<apex:form>keykeyfalseValue = {!value}<br/> selectedValue = {!selectedValue}<br/> EditMode = {!EditMode}此表达式出现在自定义组件中。既然不是 null,是 设置为 。在 此点设置为 null。记得 但是,setter 方法具有 副作用。在本例中,副作用设置为属性 在自定义组件上。由于设置为 ,设置为 假。这说明了为什么你不应该使用副作用 在你的方法中。如果评估顺序不同,并且 的值是在 setter for 的计算结果仍为 null。执行 订单不保证,结果可能 更改下次访问此页面的时间。valueEditModetrueselectedValueEditModeselectedValuevaluevaluefalseselectedValueselectedValueEditModeselectedValueselectedValue警告不要在吸气剂中使用副作用 或二传手!
  5. 由于组件不是 呈现时,不会创建视图状态<apex:form>
  6. get 请求的最后一步是将 HTML 发送到浏览器,浏览器 呈现 HTML。

获取请求示例三

对于第三个示例,使用格式为 https:// Salesforce_instance/apex/setEmps?id=recordId&key=true 的 URL 访问页面。 其中 是实例的名称(例如,),并且是 组织中的客户记录(例如,)。与第二个示例不同,此示例设置 key=true。您将看到一个页面,其内容类似于 以后:setEmpsSalesforce_instancena1recordID001D000000IRt53

让我们再跟踪一次 get 请求生命周期:

  1. 在 get 请求中发生的第一件事是 调用自定义控制器和控制器扩展。该方法是 控制器,方法是 扩展上的构造函数。这些对象被执行,并且这两个对象现在存在。 控制器现在有一个变量,称为 ,它是查询的结果,该查询使用 URL 中的参数来标识哪个 要查询的客户记录。该扩展现在有一个变量,称为 ,该变量是通过调用 控制器。myControllerlifecycleaccountidacctgetAccount
  2. get 请求的下一步是创建自定义组件并执行 关联控制器或控制器扩展上的构造函数方法。页面 包括一个自定义 元件:<c:editMode value="{!$CurrentPage.parameters.key}"/>这 自定义组件具有没有构造函数的关联控制器,因此 控制器对象是使用隐式的、无参数的、公共的 构造 函数。作为创建自定义组件的一部分,自定义组件上的属性 已设置。在这种情况下,它等于表达式 的结果。我们 将属性指定为 true,则设置为 真。value{!$CurrentPage.parameters.key}keyvalue
  3. 创建自定义组件后,将执行这些自定义组件上的所有属性。该方法将 on 属性设置为属性。该属性设置为 true,因此设置为 true。assignToassignToselectedValuevaluevalueselectedValue
  4. get 请求的下一步是计算组件、表达式和 所需的 getter 和 setter 方法。虽然我们将在下面逐步介绍这些, 请记住,这些评估的顺序是不确定的,可能是 与以下内容不同:action<apex:page>
    • 组件 具有一个属性,该属性 调用 上的方法 扩展。该方法将对象上的字段设置为 10。<apex:page>actionresetEmpnumberofemployeesacct
    • 在页面上的表达式中,让我们看看我们选择的三个是如何计算的:<apex:pageBlock title=”{!greeting}”>属性 on 调用 getter 方法 on 生命周期扩展。它在页面上呈现为 “全球媒体时事信息”。title<apex:pageblock>getGreeting<apex:form rendered=”{!$CurrentPage.parameters.key = ‘true’}”>属性 on 是根据 参数。 我们设置为何时 调用页面,以便呈现表单。rendered<apex:form>keykeytrueValue = {!value}<br/> selectedValue = {!selectedValue}<br/> EditMode = {!EditMode}此表达式出现在自定义组件中。既然不是 null,是 设置为 。如 前面的示例设置为 null。副作用 在 setter 方法中设置为 。valueEditModetrueselectedValueEditModeselectedValuetrue
  5. 由于组件是 呈现时,将创建视图状态。<apex:form>
  6. get 请求的最后一步是将 HTML 发送到浏览器,浏览器 呈现 HTML。

回发请求示例

与前两个示例不同,第三个示例呈现了可编辑的最终页面 字段可单击按钮。要了解回发请求的工作原理,请使用最终的 页面,将帐户名称更改为“Pan Galactic Media”, 员工人数为 42 人“,行业人数为 ”其他”。然后 点击保存。这将启动回发请求:

  1. 回发请求中发生的第一件事是视图状态为 解码。视图状态包含呈现 页。如果在回发请求期间操作失败,则视图状态为 用于向用户显示页面。
  2. 接下来,对控制器和控制器上的所有表达式和方法进行计算 扩展被执行。在页面上的表达式中,让我们看看我们的 对选定的三个进行评估:<apex:pageBlock title=”{!greeting}”>属性 on 调用生命周期扩展上的 getter 方法。在我们的编辑中, 我们更改了帐户名称的值。因此,值更改为 “泛银河媒体当前信息。”title<apex:pageblock>getGreetinggreeting<apex:form rendered=”{!$CurrentPage.parameters.key = ‘true’}”>属性 on 是 根据参数的值进行设置。我们没有更改参数,因此值 在视图状态中。由于该值为 true,因此当 视图状态已创建,它仍然是 true,表单是 呈现。rendered<apex:form>keykeyValue = {!value}<br/> selectedValue = {!selectedValue}<br/> EditMode = {!EditMode}我们没有更改这些值中的任何一个,因此,对于每个 表达式,则使用视图状态中的值。
  3. 最后,保存操作(触发回发请求的操作)是 评价。保存操作是以下方法 控制器:public PageReference save() { update account; return null; }这 方法使用新数据更新记录。如果此方法失败,则 如果用户没有更新记录的权限,或者如果 有由更改触发的验证规则,页面是 与描述错误的错误消息一起显示。值 输入的用户不会丢失。它们保持用户单击时的状态 “保存”按钮。假设没有错误,则 对象上的数据会更新,视图状态会更新,并且,由于 触发回发的操作不包括页面重定向,即视图 状态已更新。生成的 HTML 将发送到浏览器:

测试自定义控制器和控制器扩展

控制器扩展和自定义控制器,与所有 Apex 脚本一样,应由 单元测试。单元测试是验证特定代码段是否为类的方法 工作正常。单元测试方法不带参数,不向 数据库,并在方法定义中使用关键字进行标记。testMethod

在为控制器扩展和自定义控制器类编写单元测试时,可以 设置可在测试中使用的查询参数。例如,以下 自定义控制器和标记基于控制器方法中的示例,但已扩展为在 URL 中显示以下查询参数 对于页面:.测试方法类 下面,它练习了此页面的功能:?qp=yyyy

public class thecontroller {

            private String firstName;
            private String lastName;
            private String company;
            private String email;
            private String qp;

            public thecontroller() {
                this.qp = ApexPages.currentPage().getParameters().get('qp');
            }

            public String getFirstName() {
                  return this.firstName;
            }

            public void setFirstName(String firstName) {
                  this.firstName = firstName;
            }

            public String getLastName() {
                  return this.lastName;
            }

            public void setLastName(String lastName) {
                  this.lastName = lastName;
            }

            public String getCompany() {
                  return this.company;
            }

            public void setCompany(String company) {
                  this.company = company;
            }

            public String getEmail() {
                  return this.email;
            }

            public void setEmail(String email) {
                  this.email = email;
            }

            public PageReference save() {
                PageReference p = null;
                
                if (this.qp == null || !'yyyy'.equals(this.qp)) {
                    p = Page.failure;
                    p.getParameters().put('error', 'noParam');
                } else {
                    try {
                        Lead newlead = new Lead(LastName=this.lastName, 
                                                FirstName=this.firstName, 
                                                Company=this.company, 
                                                Email=this.email);
                        insert newlead;
                    } catch (Exception e) {
                        p = Page.failure;
                        p.getParameters().put('error', 'noInsert');
                    }
                }
                
                if (p == null) {
                    p = Page.success;
                }
                
                p.setRedirect(true);
                return p;
            }
 }

控制器调用另外两个页面:成功页面和失败页面。文本 对于此示例,这些页面并不重要。它们只需要存在。

以下标记使用上面的控制器:

<apex:page controller="thecontroller" tabstyle="lead">
   <apex:pageBlock>
      <apex:form>
         <h1>Test page for adding leads</h1>
         <p>This is a test page for adding leads.</p>
         <p>First name: <apex:inputText value="{!FirstName}"></apex:inputText></p>
         <p>Last name: <apex:inputText value="{!LastName}"></apex:inputText></p>
         <p>Company: <apex:inputText value="{!Company}"></apex:inputText></p>
         <p>Email address: <apex:inputText value="{!Email}"></apex:inputText></p>
         <apex:commandButton action="{!save}" value="Save New Lead"/>
      </apex:form>
   </apex:pageBlock>
</apex:page>

以下类测试控制器:

@isTest
public class thecontrollerTests {

    public static testMethod void testMyController() {
        PageReference pageRef = Page.success;
        Test.setCurrentPage(pageRef);
      
        thecontroller controller = new thecontroller();
        String nextPage = controller.save().getUrl();

        // Verify that page fails without parameters
        System.assertEquals('/apex/failure?error=noParam', nextPage);

        // Add parameters to page URL
        ApexPages.currentPage().getParameters().put('qp', 'yyyy');
      
        // Instantiate a new controller with all parameters in the page
        controller = new thecontroller(); 
        controller.setLastName('lastname');
        controller.setFirstName('firstname');
        controller.setCompany('acme');
        controller.setEmail('firstlast@acme.com');
        nextPage = controller.save().getUrl();

        // Verify that the success page displays
        System.assertEquals('/apex/success', nextPage);
        Lead[] leads = [select id, email from lead where Company = 'acme'];
        System.assertEquals('firstlast@acme.com', leads[0].email);
    }
}

提示

如果您正在测试控制器,您可能会看到以下错误消息:

Method does not exist or incorrect signature: Test.setCurrentPage(System.PageReference)

验证规则和自定义控制器

如果用户在使用自定义控制器的 Visualforce 页面上输入数据,并且该数据导致验证 规则错误,该错误可以显示在 Visualforce 页面上。就像使用标准控制器的页面一样,如果验证 规则错误位置是与组件关联的字段, 错误显示在那里。如果设置了验证规则错误位置 在页面顶部,使用 中的组件 显示错误。但是,要获取页面信息, 自定义控制器必须捕获异常。<apex:inputField><apex:messages><apex:page>例如,假设您有以下内容 页:

<apex:page controller="MyController" tabStyle="Account">
  <apex:messages/>
  <apex:form>
   <apex:pageBlock title="Hello {!$User.FirstName}!">
     This is your new page for the {!name} controller. <br/>
     You are viewing the {!account.name} account.<br/><br/>
     Change Account Name: <p></p>
     <apex:inputField value="{!account.name}"/> <p></p>
     Change Number of Locations:
     <apex:inputField value="{!account.NumberofLocations__c}" id="Custom_validation"/> 
         <p>(Try entering a non-numeric character here, then hit save.)</p><br/><br/>
     <apex:commandButton action="{!save}" value="Save New Account Name"/>
   </apex:pageBlock>
  </apex:form>
</apex:page>

注意

这 必须在此页的 URL 中将有效客户记录的 ID 指定为查询参数 进行渲染。例如,http:// MyDomainName.salesforce.com/apex/myValidationPage?id=001x000xxx3Jsxb。你 需要编写一个自定义控制器,例如 以后:

public class MyController {
  Account account;

  public PageReference save() {
    try{
        update account;
       }
    catch(DmlException ex){
        ApexPages.addMessages(ex);
       }
    return null;
  }

  public String getName() {
    return 'MyController';
  }

  public Account getAccount() {
    if(account == null)
      account = [select id, name, numberoflocations__c from Account
        where id = :ApexPages.currentPage().getParameters().get('id')];
      return account;

  }
}

什么时候 用户保存页面,如果触发验证错误,则捕获异常并 在页面上显示,就像标准控制器一样。

使用 transient 关键字

使用关键字声明实例 无法保存且不应作为视图状态的一部分传输的变量 用于 Visualforce 页面。为 例:

transient

Transient Integer currentTotal;

您还可以在 Apex 中使用关键字 可序列化的类,即在控制器、控制器扩展或类中 实现 or 接口。此外,还可以在定义类型的类中使用 在可序列化类中声明的字段。transientBatchableSchedulabletransient

将变量声明为缩减视图 状态大小。关键字的一个常见用例是 Visualforce 页面上的字段,该字段仅在页面持续时间内需要 请求,但不应是页面视图状态的一部分,并且会使用过多的系统 在请求期间要多次重新计算的资源。transienttransient

某些 Apex 对象会自动被视为瞬态对象,也就是说,它们的值不会 保存为页面视图状态的一部分。这些对象包括:

  • 页面引用
  • XmlStream 类
  • 集合自动标记为暂时性,仅当它们的对象类型 保留会自动标记为暂时性保留,例如保存点的集合
  • 大多数对象由系统方法生成,例如 .Schema.getGlobalDescribe
  • JSONParser类实例。

静态变量也不会得到 通过视图状态传输。

以下示例包含一个 Visualforce 页面和一个自定义控制器。点击 页面上的“刷新”按钮使瞬态日期为 已更新,因为每次刷新页面时都会重新创建它。非瞬态 date 继续具有其原始值,该值已从视图中反序列化 状态,所以它保持不变。

<apex:page controller="ExampleController">
  T1: {!t1} <br/>
  T2: {!t2} <br/>
  <apex:form>
    <apex:commandLink value="refresh"/>
  </apex:form>
</apex:page>
public class ExampleController {

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

标准列表控制器

标准列表控制器允许您创建可以显示或处理一组记录的 Visualforce 页面。现有 Salesforce 页面的示例 使用一组记录的包括列表页、相关列表、 和大规模行动页面。标准列表控制器可与 以下对象:

  • 帐户
  • 资产
  • 运动
  • 联系
  • 合同
  • 想法
  • 机会
  • 次序
  • 产品2
  • 溶液
  • 用户
  • 自定义对象

以下主题包括有关使用 标准列表控制器:

  • 关联标准列表控制器 使用 Visualforce 页面
  • 使用列表控制器访问数据
  • 使用标准列表控制器操作
  • 将列表视图与标准列表控制器一起使用
  • 使用标准列表覆盖选项卡 控制器
  • 使用标准列表添加自定义列表按钮 控制器

关联标准列表控制器 使用 Visualforce 页面

使用标准列表控制器与使用标准列表控制器非常相似 控制器。首先在组件上设置属性,然后在同一组件上设置属性。standardController<apex:page>recordSetVar例如,要将页面与帐户的标准列表控制器相关联,请使用 以后 标记:

<apex:page standardController="Account" recordSetVar="accounts">

注意

什么时候 您在标签上使用该属性,不能同时使用该属性。standardController<apex:page>controller

该属性不仅指示 页面使用列表控制器,它设置记录集合的变量名称。此变量 可用于访问记录集合中的数据。recordSetVar

使用列表控制器访问数据

使用表达式语法访问记录

将页面与列表控制器关联后,可以使用以下命令对一组记录执行操作 表达式语言语法。例如,若要创建会计科目表,请使用以下命令 标记:

<apex:page standardController="Account" recordSetVar="accounts" tabstyle="account" sidebar="false">
  <apex:pageBlock>
    <apex:pageBlockTable value="{!accounts}" var="acc">
      <apex:column value="{!acc.name}"/>
    </apex:pageBlockTable>
  </apex:pageBlock>
</apex:page>

这 示例使用组件 生成数据表。属性已设置 添加到标准列表控制器加载的变量,该变量是循环访问的记录列表。

<apex:pageBlockTable>value{!accounts}<apex:pageBlockTable>

对于列表中的每条记录,将记录分配给变量。然后,在表中构造一个新行, 使用组件定义的行。该组件使用表示当前记录的变量, 以提取该记录的字段值。<apex:pageBlockTable>acc<apex:pageBlockTable><apex:column><apex:column>acc

列出的组织中的所有帐户名称的生成页面:

使用标准列表控制器时,返回的记录会自动对第一个记录进行排序 由当前视图定义的数据列。使用扩展或自定义列表控制器时,可以控制 sort 方法。

注意

此页面未在请求中指定筛选器,因此页面显示最后一个 使用过滤器。有关将筛选器与列表控制器一起使用的信息,请参阅将列表视图与标准列表控制器一起使用。

与 Lightning 平台 API 中的查询一样,您可以使用表达式语言语法来执行以下操作 从相关记录中检索数据。与标准控制器一样,您最多可以遍历 5 个 子与子关系的级别和父与子关系的一个级别。

带 ID 的访问记录

在典型的页面交互中,用户先从列表视图中选择记录,然后导航到 页面,然后 Visualforce 将它们发送到控制器。您还可以通过以下方式手动指定记录 将所选记录直接设置到控制器上。

标准列表控制器基于类。使用该方法从 Apex 控制器。StandardSetController ApexApexPages.StandardSetController.setSelected()

让我们看一些代码。此示例使用前面示例中的标记来显示 表中的帐户名称。然后,它包含自定义 Apex 代码来请求特定记录 显示。

<apex:page standardController="Account" recordSetVar="accounts" extensions="MyControllerExtension">
  <apex:pageBlock >
    <apex:pageBlockTable value="{!accounts}" var="acc">
      <apex:column value="{!acc.name}"/>
    </apex:pageBlockTable>
  </apex:pageBlock>
</apex:page>

public with sharing class MyControllerExtension {
    private ApexPages.StandardSetController setController;

    public MyControllerExtension(ApexPages.StandardSetController setController) {
        this.setController = setController;
        
        Account [] records = [SELECT Id, Name FROM Account LIMIT 30];
        setController.setSelected(records);
    }
}

标准列表控制器基于 Apex 类。检索分配给 list 控制器,使用 方法 .StandardSetControllerApexPages.StandardSetController.setSelected()

在 的构造函数中,将 SOQL 请求从 Account 对象中选择 ID 和 Name,并限制前 30 个结果。 然后,定义 记录在页面加载时被选中。MyControllerExtensionsetController.setSelected(records)

注意

标准列表控制器最多可以返回 10,000 条记录。自定义控制器可以工作 具有更大的结果集。请参阅使用大型数据集。

还可以通过将记录 ID 列表作为多个包含来将它们传递到 URL 中 查询参数。例如,具有三个帐户 ID 的 URL 如下所示: /apex/pageName?ids=001xx00account1&ids=001xx00account2&ids=001xx00account3

某些浏览器对 URL 的长度有硬性限制。如果您的 URL 包含太多 ID,则 达到该限制的可能性更大,导致您的页面行为不端。而不是 手动将 ID 包含在 URL 字符串中,最好将所选记录设置为 控制器。

使用标准列表控制器操作

操作方法在页面执行逻辑或导航时 事件发生,例如当用户单击按钮或将鼠标悬停在 页。可以通过在以下参数之一的参数中使用表示法从页面标记中调用操作方法 标签:{! }action

  • <apex:commandButton> 创建一个按钮,该按钮调用 行动
  • <apex:commandLink> 创建一个调用操作的链接
  • <apex:actionPoller> 定期调用操作
  • <apex:actionSupport> 创建一个事件(例如 “onclick”、“onmouseover”等) 命名组件,调用操作
  • <apex:actionFunction> 定义了一个新的 JavaScript 调用操作的函数
  • <apex:page> 调用操作 加载页面时

支持的操作方法如下表所示 由所有标准列表控制器。您可以关联这些操作 替换为任何包含属性的 Visualforce 组件。

action

行动描述
save插入新记录或更新现有记录 改变。此操作完成后,该操作会将用户返回到 原始页面(如果已知)或主页。save
quicksave插入新记录或更新现有记录 改变。与操作不同,不会重定向用户 到另一页。savequicksave
list返回标准列表页的 PageReference 对象,基于 在最近使用的该对象的列表筛选器上,当 未由 用户。filterId
cancel中止编辑操作。此操作完成后, 操作返回 用户到用户最初调用编辑的页面。cancel
first显示集中记录的第一页。
last显示集中记录的最后一页。
next显示集中记录的下一页。
previous显示集中记录的上一页。

在以下示例中,用户指定用于查看的筛选器 帐户记录。当用户单击 Go 时, 使用选定的筛选器显示标准列表页。

<apex:page standardController="Account" recordSetVar="accounts">
   <apex:form>
       <apex:selectList value="{!filterid}" size="1">
           <apex:selectOptions value="{!listviewoptions}"/>
       </apex:selectList>
       <apex:commandButton value="Go" action="{!list}"/>
   </apex:form>
</apex:page>

使用列表控制器进行分页

您可以使用列表控制器将分页添加到页面,方法是利用 和 操作。例如,如果 创建具有以下标记的页面:

nextprevious

<apex:page standardController="Account" recordSetvar="accounts">
  <apex:pageBlock title="Viewing Accounts">
  <apex:form id="theForm">
    <apex:pageBlockSection >
      <apex:dataList var="a" value="{!accounts}" type="1">
        {!a.name}
      </apex:dataList>
    </apex:pageBlockSection>
    <apex:panelGrid columns="2">
      <apex:commandLink action="{!previous}">Previous</apex:commandlink>
      <apex:commandLink action="{!next}">Next</apex:commandlink>
    </apex:panelGrid>
  </apex:form> 
  </apex:pageBlock>
</apex:page>

默认情况下,列表控制器返回 20 页面上的记录。控制上显示的记录数 每个页面,使用控制器扩展来设置 .有关控制器的信息 扩展,请参阅构建控制器扩展。

pageSize

注意

使用分页时,当存在 集合中已修改的行。这包括添加的任何新行 通过扩展操作添加到集合中。错误的处理 在这种情况下,消息遵循标准行为,可以 显示在页面上。例如,您可以使用 or 组件来显示 发送给用户的错误消息。<apex:pageMessages><apex:messages>

将列表视图与标准列表控制器一起使用

许多 Salesforce 页面都包含列表视图,允许您筛选记录 显示在页面上。例如,在商机主页上, 您可以选择仅查看您拥有的商机列表 从列表视图中选择 My Opportunity 下拉列表。在与列表控制器关联的页面上,您 还可以使用列表视图。例如,若要创建具有列表视图的简单帐户列表, 使用以下标记创建页面:

<apex:page standardController="Account" recordSetvar="accounts">
  <apex:pageBlock title="Viewing Accounts">
  <apex:form id="theForm">
    <apex:panelGrid columns="2">
      <apex:outputLabel value="View:"/>
      <apex:selectList value="{!filterId}" size="1">
        <apex:actionSupport event="onchange" rerender="list"/>
        <apex:selectOptions value="{!listviewoptions}"/>
      </apex:selectList>
    </apex:panelGrid>
    <apex:pageBlockSection >
      <apex:dataList var="a" value="{!accounts}" id="list">
        {!a.name}
      </apex:dataList>
    </apex:pageBlockSection>
  </apex:form> 
  </apex:pageBlock>
</apex:page>

当您打开该页面时,您会看到如下内容:

此页面与标准帐户控制器相关联,并且 组件 填充为 ,计算结果为用户可以看到的列表视图。当用户 从下拉列表中选择一个值,该值绑定到控制器的属性。 当更改时, 页面可用的记录会发生变化,因此,当 更新时, value 用于更新页面可用的记录列表。<apex:selectlist>{!listviewoptions}filterIdfilterId<apex:datalist>您还可以在编辑页面上使用视图列表,如下所示:

<apex:page standardController="Opportunity" recordSetVar="opportunities" 
              tabStyle="Opportunity"
    sidebar="false">
    <apex:form>
        <apex:pageBlock>
            <apex:pageMessages/>
            <apex:pageBlock>
                <apex:panelGrid columns="2">
                    <apex:outputLabel value="View:"/>
                    <apex:selectList value="{!filterId}" size="1">
                        <apex:actionSupport event="onchange" rerender="opp_table"/>
                        <apex:selectOptions value="{!listviewoptions}"/>
                    </apex:selectList>
                </apex:panelGrid>
            </apex:pageBlock>

            <apex:pageBlockButtons>
                <apex:commandButton value="Save" action="{!save}"/>
            </apex:pageBlockButtons>
            <apex:pageBlockTable value="{!opportunities}" var="opp" id="opp_table">
                <apex:column value="{!opp.name}"/>
                <apex:column headerValue="Stage">
                    <apex:inputField value="{!opp.stageName}"/>
                </apex:column>
                <apex:column headerValue="Close Date">
                    <apex:inputField value="{!opp.closeDate}"/>
                </apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

注意

如果用户更改列表视图,则在以下情况下会引发异常 集合中有已修改的行。错误消息的处理 在这种情况下,遵循标准行为,可以显示 在页面上。例如,您可以使用 or 组件来显示 发送给用户的错误消息。<apex:pageMessages><apex:messages>

使用列表控制器编辑记录

您也可以使用列表控制器编辑一组记录。为 例如,如果创建具有以下标记的页面:

<apex:page standardController="Opportunity" recordSetVar="opportunities" tabStyle="Opportunity" sidebar="false">
    <apex:form >
        <apex:pageBlock >
            <apex:pageMessages />
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!save}"/>
            </apex:pageBlockButtons>
            <apex:pageBlockTable value="{!opportunities}" var="opp">
                <apex:column value="{!opp.name}"/>
                <apex:column headerValue="Stage">
                    <apex:inputField value="{!opp.stageName}"/>
                </apex:column>
                <apex:column headerValue="Close Date">
                    <apex:inputField value="{!opp.closeDate}"/>
                </apex:column>
            </apex:pageBlockTable>      
        </apex:pageBlock>
    </apex:form>
</apex:page>

您会看到一个允许您更新的页面 并保存商机的“阶段”和“结束日期”,如下所示:

有关详细信息,请参阅使用自定义列表控制器批量更新记录。

注意

如果用户不呈现与列表控制器中的 、 或操作关联的命令按钮和链接,则不会呈现 具有适当的权限。同样,如果没有特定记录 与页面、命令按钮和链接相关联 操作不是 呈现。savequicksaveeditedit

标准控制器

Visualforce 控制器是一组指令,用于指定用户在用户执行以下操作时发生的情况 与关联的 Visualforce 标记中指定的组件进行交互,例如当用户 单击按钮或链接。控制器还提供对应显示的数据的访问 ,并且可以修改组件行为。

闪电网络平台提供了许多标准控制器,其中包含相同的 用于标准 Salesforce 页面的功能和逻辑。例如,如果您使用 标准帐户控制器,单击 Visualforce 页面产生的行为与在 标准帐户编辑页面。

每个 Salesforce 对象都存在一个标准控制器,可以使用 闪电平台 API。以下主题包括有关使用标准控制器的其他信息:

  • 关联一个 带有 Visualforce 页面的标准控制器
  • 访问数据 带标准控制器
  • 使用标准 控制器操作
  • 验证 规则和标准控制器
  • 设置页面样式,使用 标准控制器
  • 检查对象可访问性
  • 自定义控制器和控制器扩展

将标准控制器与 Visualforce 页面关联

要将标准控制器与 Visualforce 页面相关联,请使用标签上的属性,并为其分配任何 Salesforce 的名称 可以使用 Lightning 平台 API 查询的对象。standardController<apex:page>

例如,将页面与名为 MyCustomObject,请使用以下标记:

<apex:page standardController="MyCustomObject__c">
</apex:page>

注意

当您使用 上的属性时 标记,则不能同时使用该属性。standardController<apex:page>controller

使用标准控制器访问数据

每个标准控制器都包含一个 getter 方法,该方法返回由页面 URL 中的查询字符串参数指定的记录。这 方法允许关联的页面标记使用语法引用上下文记录上的字段,其中 是与 控制器。例如,使用帐户标准控制器的页面可用于返回当前在上下文中的帐户的字段值。id{!object}object{!account.name}name

注意

要使 getter 方法成功,URL 中查询字符串参数指定的记录必须是 与标准控制器类型相同。例如,使用帐户标准的页面 控制者只能返回帐户记录。如果查询字符串参数指定了联系人记录 ID,则不返回任何数据 通过表达式。idid{!account}与 Lightning 平台 API 中的查询一样,您可以使用合并字段语法进行检索 相关记录数据:

  • 您最多可以遍历五个级别的子级与父级关系。例如,如果 使用 Contact 标准控制器,您可以使用 (三级子级到父级 relationship) 返回客户记录所有者的姓名,即 与联系人关联。{!contact.Account.Owner.FirstName}
  • 您可以遍历一个级别的父子关系。例如,如果使用 帐户标准控制器,可用于返回与 当前处于上下文中的帐户。{!account.Contacts}

使用标准控制器操作

操作方法在页面执行逻辑或导航时 事件发生,例如当用户单击按钮或将鼠标悬停在 页。可以通过在以下参数之一的参数中使用表示法从页面标记中调用操作方法 标签:{! }action

  • <apex:commandButton> 创建一个按钮,该按钮调用 行动
  • <apex:commandLink> 创建一个调用操作的链接
  • <apex:actionPoller> 定期调用操作
  • <apex:actionSupport> 创建一个事件(例如 “onclick”、“onmouseover”等) 命名组件,调用操作
  • <apex:actionFunction> 定义了一个新的 JavaScript 调用操作的函数
  • <apex:page> 调用操作 加载页面时

支持的操作方法如下表所示 由所有标准控制器提供。您可以将这些操作与 任何包含属性的 Visualforce 组件。action

行动描述
save插入新记录或更新现有记录(如果该记录当前位于上下文中)。在此之后 操作完成,动作 将用户返回到原始页面(如果已知),或将用户导航到详细信息 页面。save
quicksave插入新记录或更新现有记录(如果该记录当前位于上下文中)。与操作不同,此页面不会重定向 用户到另一个页面。save
edit将用户导航到记录的编辑页面,即 目前在上下文中。此操作完成后,该操作会将用户返回到 用户最初调用操作的页面。edit
delete删除上下文中的记录。此操作完成后,该操作将刷新页面或发送 关联对象的用户要按 Tab 键。delete
cancel中止编辑操作。此操作完成后, 操作返回 用户到用户最初调用编辑的页面。cancel
list返回标准列表页的 PageReference 对象,基于 在该对象最近使用的列表筛选器上。例如 如果标准控制器是 ,并且用户查看的最后一个筛选列表是 New Last Week, 将显示上周创建的联系人。contact

例如,以下页面允许您更新帐户。 单击“保存”时,将在标准上触发操作 控制器,帐户将更新。

save

<apex:page standardController="Account">
  <apex:form>
    <apex:pageBlock title="My Content" mode="edit">
      <apex:pageBlockButtons>
        <apex:commandButton action="{!save}" value="Save"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="My Content Section" columns="2">
        <apex:inputField value="{!account.name}"/>
        <apex:inputField value="{!account.site}"/>
        <apex:inputField value="{!account.type}"/>
        <apex:inputField value="{!account.accountNumber}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

注意

请记住,要使此页面显示帐户数据,有效的 ID 必须在页面的 URL 中将客户记录指定为查询参数。为 例:

https://Salesforce_instance/apex/myPage?id=001x000xxx3Jsxb

注意

仅当用户具有适当的 、 、 或标准控制器中的操作时,才会呈现与 、 、 或操作关联的命令按钮和链接 权限。同样,如果没有特定记录与页面关联,则命令按钮和 不会呈现与 and 操作关联的链接。savequicksaveeditdeleteeditdelete

验证规则和标准控制器

如果用户在使用标准控制器的 Visualforce 页面上输入数据,并且该数据 导致验证规则错误,可以显示该错误 在 Visualforce 页面上。如果验证规则错误位置为 与组件关联的字段,则错误将显示在那里。 如果验证规则错误位置设置为顶部 ,请使用 OR 组件 以显示错误。<apex:inputField><apex:pageMessages><apex:messages><apex:page>

设置使用标准控制器的页面的样式

与标准控制器关联的任何页面都会自动继承 用于标准 Salesforce 页面的样式 与指定对象相关联。也就是说,指定 对象显示为选中状态,并使用选项卡的关联颜色 设置所有页面元素的样式。

您可以覆盖使用标准控制器的页面的样式 替换为属性 在标签上。 例如,以下页面使用 Account 标准控制器, 但呈现一个突出显示 Opportunity 选项卡的页面,并使用 Opportunity 选项卡的黄色:tabStyle<apex:page>

<apex:page standardController="Account" tabStyle="Opportunity">
</apex:page>

若要使用与 MyCustomObject 关联的样式,请执行以下操作:

<apex:page standardController="Account" tabStyle="MyCustomObject__c">
</apex:page>

要使用与自定义 Visualforce 选项卡关联的样式,请将 选项卡名称(非标签)的属性,后跟双下划线 和单词 tab。例如,要使用 Visualforce 的样式 选项卡,名称为 Source 和标签 Sources,请使用:

<apex:page standardController="Account" tabStyle="Source__tab">
</apex:page>

或者,您可以覆盖标准控制器页面样式 使用您自己的自定义样式表和内联样式。

检查对象可访问性

如果用户没有足够的权限来查看某个对象,则任何使用控制器渲染该对象的 Visualforce 页面都将无法访问。 为避免此错误,您应确保仅当用户有权访问关联的对象时,才会呈现 Visualforce 组件 与控制器。您可以像这样检查对象的可访问性:

{!$ObjectType.objectname.accessible}

此表达式返回 true 或 false 值。例如,要检查您是否有权访问标准 Lead 对象, 使用以下代码:

{!$ObjectType.Lead.accessible}

对于自定义对象,代码类似:

{!$ObjectType.MyCustomObject__c.accessible}

在哪里 自定义对象的名称。

MyCustomObject__c确保页面的一部分仅在用户 有权访问对象,请使用组件上的属性。例如,要在以下情况下显示页面块 用户有权访问 Lead 对象,您将执行以下操作:

render

<apex:page standardController="Lead">
	<apex:pageBlock rendered="{!$ObjectType.Lead.accessible}">
		<p>This text will display if you can see the Lead object.</p>
	</apex:pageBlock>
</apex:page>

如果用户无法访问对象,最好提供备用消息。为 例:

<apex:page standardController="Lead">
	<apex:pageBlock rendered="{!$ObjectType.Lead.accessible}">
		<p>This text will display if you can see the Lead object.</p>
	</apex:pageBlock>
	<apex:pageBlock rendered="{! NOT($ObjectType.Lead.accessible) }">
		<p>Sorry, but you cannot see the data because you do not have access to the Lead object.</p>
	</apex:pageBlock>
</apex:page>

呈现 PDF 格式的 Visualforce 页面 文件

您可以使用 PDF 生成 Visualforce 页面的可下载、可打印的 PDF 文件 渲染服务。通过更改标签将页面转换为 PDF。

<apex:page>

<apex:page renderAs="pdf">

呈现的 Visualforce 页面 PDF文件要么显示在浏览器中,要么被下载,这取决于 浏览器的设置。具体行为取决于浏览器、版本、 和用户设置,并且不受 Visualforce 的控制。以下页面包含一些帐户详细信息,并以 PDF 格式呈现 文件。

<apex:page standardController="Account" renderAs="pdf">

<apex:stylesheet value="{!URLFOR($Resource.Styles,'pdf.css')}"/>

<h1>Welcome to Universal Samples!</h1>

<p>Thank you, <b><apex:outputText value=" {!Account.Name}"/></b>, for 
   becoming a new account with Universal Samples.</p>

<p>Your account details are:</p>

<table>
<tr><th>Account Name</th>
    <td><apex:outputText value="{!Account.Name}"/></td>
    </tr>
<tr><th>Account Rep</th>
    <td><apex:outputText value="{!Account.Owner.Name}"/></td>
    </tr>
<tr><th>Customer Since</th>
    <td><apex:outputText value="{0,date,long}">
        <apex:param value="{!Account.CreatedDate}"/>
        </apex:outputText></td>
    </tr>
</table>
    
</apex:page>

Visualforce 页面 呈现为 PDF 文件

将 Visualforce 页面渲染为 PDF 格式 顶点

您可以使用 Apex 中的方法将 Visualforce 页面呈现为 PDF 数据。然后使用 Apex 代码将该 PDF 数据转换为电子邮件附件、文档、 喋喋不休的帖子,等等。

PageReference.getContentAsPDF()下面的示例是一个简单的三元素窗体,用于选择帐户和报表 格式,然后将生成的报告发送到指定的电子邮件 地址。

<apex:page title="Account Summary" tabStyle="Account"
    controller="PdfEmailerController">

    <apex:pageMessages />

    <apex:form >
        <apex:pageBlock title="Account Summary">
    
        <p>Select a recently modified account to summarize.</p>
        <p/>
        
        <apex:pageBlockSection title="Report Format">
        
            <!-- Select account menu -->
            <apex:pageBlockSectionItem>
                <apex:outputLabel for="selectedAccount" value="Account"/> 
                <apex:selectList id="selectedAccount" value="{! selectedAccount }" 
                                 size="1">
                    <apex:selectOption /> <!-- blank by default -->
                    <apex:selectOptions value="{! recentAccounts }" />
                </apex:selectList>
            </apex:pageBlockSectionItem>

            <!-- Select report format menu -->
            <apex:pageBlockSectionItem >
                <apex:outputLabel for="selectedReport" value="Summary Format"/> 
                <apex:selectList id="selectedReport" value="{! selectedReport }" 
                                 size="1">
                    <apex:selectOptions value="{! reportFormats }" />
                </apex:selectList>
            </apex:pageBlockSectionItem>

            <!-- Email recipient input field -->
            <apex:pageBlockSectionItem >
                <apex:outputLabel for="recipientEmail" value="Send To"/> 
                <apex:inputText value="{! recipientEmail }" size="40"/>
            </apex:pageBlockSectionItem>

        </apex:pageBlockSection>
            
        <apex:pageBlockButtons location="bottom">
            <apex:commandButton action="{! sendReport }" value="Send Account Summary" />
        </apex:pageBlockButtons>
    
    </apex:pageBlock>
    </apex:form>

</apex:page>

此页面是一个简单的用户界面。当您从 Apex 生成 PDF 文件时,所有 该操作位于 Apex 代码中。在此示例中,该代码位于指定为页面的 控制器。

PdfEmailerController

public with sharing class PdfEmailerController {
    
    // Form fields
    public Id selectedAccount    { get; set; }  // Account selected on Visualforce page
    public String selectedReport { get; set; }  // Report selected
    public String recipientEmail { get; set; }  // Send to this email
    
    // Action method for the [Send Account Summary] button
    public PageReference sendReport() {

        // NOTE: Abbreviated error checking to keep the code sample short
        //       You, of course, would never do this little error checking
        if(String.isBlank(this.selectedAccount) || String.isBlank(this.recipientEmail)) {
            ApexPages.addMessage(new 
                ApexPages.Message(ApexPages.Severity.ERROR, 
               'Errors on the form. Please correct and resubmit.'));
            return null; // early out
        }
        
        // Get account name for email message strings
        Account account = [SELECT Name 
                           FROM Account 
                           WHERE Id = :this.selectedAccount 
                           LIMIT 1];
        if(null == account) {
            // Got a bogus ID from the form submission
            ApexPages.addMessage(new 
                ApexPages.Message(ApexPages.Severity.ERROR, 
               'Invalid account. Please correct and resubmit.'));
            return null; // early out
        }
        
        // Create email
        Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
        message.setToAddresses(new String[]{ this.recipientEmail });
        message.setSubject('Account summary for ' + account.Name);
        message.setHtmlBody('Here\'s a summary for the ' + account.Name + ' account.');
        
        // Create PDF
        PageReference reportPage = 
            (PageReference)this.reportPagesIndex.get(this.selectedReport);
        reportPage.getParameters().put('id', this.selectedAccount);
        Blob reportPdf;
        try {
            reportPdf = reportPage.getContentAsPDF();
        }
        catch (Exception e) {
            reportPdf = Blob.valueOf(e.getMessage());
        }
        
        // Attach PDF to email and send
        Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
        attachment.setContentType('application/pdf');
        attachment.setFileName('AccountSummary-' + account.Name + '.pdf');
        attachment.setInline(false);
        attachment.setBody(reportPdf);
        message.setFileAttachments(new Messaging.EmailFileAttachment[]{ attachment });
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{ message });
        
        ApexPages.addMessage(new 
            ApexPages.Message(ApexPages.Severity.INFO,
           'Email sent with PDF attachment to ' + this.recipientEmail));

        return null; // Stay on same page, even on success
    }
    
    
    /***** Form Helpers *****/
    
    // Ten recently-touched accounts, for the Account selection menu
    public List<SelectOption> recentAccounts {
        get {
            if(null == recentAccounts){
                recentAccounts = new List<SelectOption>();
                for(Account acct : [SELECT Id,Name,LastModifiedDate 
                                    FROM Account 
                                    ORDER BY LastModifiedDate DESC 
                                    LIMIT 10]) {
                    recentAccounts.add(new SelectOption(acct.Id, acct.Name));
                }
            }
            return recentAccounts;
        }
        set;
    }
    
    // List of available reports, for the Summary Format selection menu
    public List<SelectOption> reportFormats {
        get {
            if(null == reportFormats) {
                reportFormats = new List<SelectOption>();
                for(Map <String,Object> report : reports) {
                    reportFormats.add(new SelectOption(
                        (String)report.get('name'), (String)report.get('label')));
                }
            }
            return reportFormats;
        }
        set;
    }

    
    /***** Private Helpers *****/
    
    // List of report templates to make available
    // These are just Visualforce pages you might print to PDF
    private Map<String,PageReference> reportPagesIndex;
    private List<Map<String,Object>> reports {
        get {
            if(null == reports) {
                reports = new List<Map<String,Object>>();
                // Add one report to the list of reports
                Map<String,Object> simpleReport = new Map<String,Object>();
                simpleReport.put('name',  'simple');
                simpleReport.put('label', 'Simple');
                simpleReport.put('page',   Page.ReportAccountSimple);
                reports.add(simpleReport);
                
                // Add your own, more complete list of PDF templates here

                // Index the page names for the reports
                this.reportPagesIndex = new Map<String,PageReference>();
                for(Map<String,Object> report : reports) {
                    this.reportPagesIndex.put(
                        (String)report.get('name'), (PageReference)report.get('page'));
                }
            }
            return reports;
        }
        set;
    }
}

这个 Apex 控制器在概念上可以分为四个部分。

  • 开头的三个公共属性捕获 表单上的三个输入元素。
  • 操作方法触发 单击“发送帐户摘要”按钮时。sendReport()
  • 两个公共帮助程序属性提供要在两个选择列表中使用的值 input 元素。
  • 末尾的私人帮助程序封装了可能的 PDF 报告列表 格式。您可以通过创建 Visualforce 页面来添加自己的报告,然后 在本节中为其添加一个条目。

当 action 方法触发时, 代码执行以下操作。

sendReport()

  • 它执行基本的错误检查,以确保表单字段具有 有用的值。注意对于必须 在与真人接触后幸存下来。在生产代码中执行更多 完成表单验证。
  • 接下来,它使用所选帐户的值来查找该帐户的名称 帐户。帐户名称用于添加到电子邮件的文本中。 此查找也是进一步验证表单值并确保 选择了真实账户。
  • 它使用该类来组合电子邮件,并设置“收件人”、“主题”和“正文”电子邮件 消息值。Messaging.SingleEmailMessage
  • 该代码为 选择报表格式,然后在其上设置页面请求参数。这 参数名为“id”,其值设置为所选帐户的 ID。这代表了一个特定的 请求在指定帐户的上下文中访问此页面。调用时, 引用 Visualforce 页面有权访问指定的帐户,并且页面将使用该帐户呈现 帐户的详细信息。PageReferencePageReferencegetContentAsPdf()
  • 最后,将 PDF 数据添加到附件中,并将附件添加到 之前创建的电子邮件。然后发送消息。

使用 时, 方法调用的返回类型为 ,其中 代表“二进制大对象”。在 Apex 中,数据类型表示非类型化二进制数据。只有当变量被添加到内容类型为 “application/pdf”,表示二进制数据成为 PDF 文件。PageReference.getContentAsPdf()BlobBlobreportPdfMessaging.EmailFileAttachment

此外,对 的调用是 包裹在块中。如果调用失败, 将希望的 PDF 数据替换为异常消息的版本 发短信。getContentAsPdf()try/catchcatchBlob

将 Visualforce 页面呈现为 PDF 数据在语义上被视为对各种外部服务的标注 原因。原因之一是渲染服务可能以与 外部服务可能会失败。例如,该页面引用了以下外部资源: 不可用。另一个例子是,当页面包含太多数据时,通常在 图像的形式 – 或渲染时间超过限制。因此,在将 Visualforce 页面渲染为 PDF 数据时,请始终将渲染调用包装在一个块中 顶点。getContentAsPdf()try/catch为了完整起见,下面是报告模板页面,该页面由 顶点 法典。

<apex:page showHeader="false" standardStylesheets="false"
    standardController="Account">
    
    <!-- 
    This page must be called with an Account ID in the request, e.g.:
    https://<salesforceInstance>/apex/ReportAccountSimple?id=001D000000JRBet
    -->

    <h1>Account Summary for {! Account.Name }</h1>
    
    <table>
        <tr><th>Phone</th>  <td><apex:outputText value="{! Account.Phone }"/></td></tr>
        <tr><th>Fax</th>    <td><apex:outputText value="{! Account.Fax }"/></td></tr>
        <tr><th>Website</th><td><apex:outputText value="{! Account.Website }"/></td></tr>
    </table>

    <p><apex:outputText value="{! Account.Description }"/></p>
        
</apex:page>

使用 Visualforce PDF 渲染时可用的字体

Visualforce PDF 渲染 支持一组有限的字体。要确保 PDF 输出按预期呈现,请使用 支持的字体名称。对于每种字体,列出的第一个名称是 推荐。

font-family

字体font-family
Arial Unicode MSArial Unicode MS
黑体无衬线无衬线对话
衬线次
邮差等宽邮差等宽DialogInput(对话输入)

注意

  • 这些规则适用于服务器端 PDF 呈现。在 Web 浏览器中查看页面 可以有不同的结果。
  • 使用此处未列出的值设置样式的文本使用“时间”。例如,如果您使用 单词“Helvetica”,它呈现为 Times,因为这不是 Helvetica 字体。我们建议使用“sans-serif”。
  • Arial Unicode MS 是唯一可用的多字节字体。这是唯一一种 为不使用 拉丁字符集。
  • Arial Unicode MS 不支持粗体或斜体。font-weight
  • 当页面呈现为 PDF 文件时,不支持 Web 字体。你可以使用 Visualforce 页面中的 Web 字体(当它们正常呈现时)。

测试字体呈现

您可以使用以下页面使用 Visualforce PDF 渲染测试字体渲染 发动机。

<apex:page showHeader="false" standardStylesheets="false" 
    controller="SaveToPDF" renderAs="{! renderAs }">

<apex:form rendered="{! renderAs != 'PDF' }" style="text-align: right; margin: 10px;">
    <div><apex:commandLink action="{! print }" value="Save to PDF"/></div>
    <hr/>
</apex:form>

<h1>PDF Fonts Test Page</h1>

<p>This text, which has no styles applied, is styled in the default font for the 
   Visualforce PDF rendering engine.</p>

<p>The fonts available when rendering a page as a PDF are as follows. The first 
listed <code>font-family</code> value for each typeface is the recommended choice.</p>

<table border="1" cellpadding="6">
<tr><th>Font Name</th><th>Style <code>font-family</code> Value to Use (Synonyms)</th></tr>
<tr><td><span style="font-family: Arial Unicode MS; font-size: 14pt; ">Arial 
    Unicode MS</span></td><td><ul>
   <li><span style="font-family: Arial Unicode MS; font-size: 14pt;">Arial Unicode MS</span></li>
    </ul></td></tr>
<tr><td><span style="font-family: Helvetica; font-size: 14pt;">Helvetica</span></td>
    <td><ul>
   <li><span style="font-family: sans-serif; font-size: 14pt;">sans-serif</span></li>
   <li><span style="font-family: SansSerif; font-size: 14pt;">SansSerif</span></li>
   <li><span style="font-family: Dialog; font-size: 14pt;">Dialog</span></li>
    </ul></td></tr>
<tr><td><span style="font-family: Times; font-size: 14pt;">Times</span></td><td><ul>
   <li><span style="font-family: serif; font-size: 14pt;">serif</span></li>
   <li><span style="font-family: Times; font-size: 14pt;">Times</span></li>
</ul></td></tr>
<tr><td><span style="font-family: Courier; font-size: 14pt;">Courier</span></td>
    <td><ul>
    <li><span style="font-family: monospace; font-size: 14pt;">monospace</span></li>
    <li><span style="font-family: Courier; font-size: 14pt;">Courier</span></li>
    <li><span style="font-family: Monospaced; font-size: 14pt;">Monospaced</span></li>
    <li><span style="font-family: DialogInput; font-size: 14pt;">DialogInput</span></li>
</ul></td></tr>
</table>

<p><strong>Notes:</strong>
<ul>
<li>These rules apply to server-side PDF rendering. You might see different results 
    when viewing this page in a web browser.</li>
<li>Text styled with any value besides those listed above receives the default font 
    style, Times. This means that, ironically, while Helvetica's synonyms render as 
    Helvetica, using "Helvetica" for the font-family style renders as Times. 
    We recommend using "sans-serif".</li>
<li>Arial Unicode MS is the only multibyte font available, providing support for the 
    extended character sets of languages that don't use the Latin character set.</li>
</ul>
</p>
 
</apex:page>

上一页使用以下控制器,该控制器提供了一个简单的“保存到 PDF” 功能。

public with sharing class SaveToPDF {

    // Determines whether page is rendered as a PDF or just displayed as HTML
    public String renderAs { get; set; }


    // Action method to "print" to PDF
    public PageReference print() {
        renderAs = 'PDF';
        return null;
    }

}

呈现为 PDF 时的组件行为

了解 Visualforce 如何 组件在转换为 PDF 时的行为对于创建呈现的页面至关重要 井。Visualforce PDF 渲染服务 呈现页面显式提供的静态 HTML 和基本 CSS。通常,不要使用 具有以下特点的组件:

  • 依靠 JavaScript 执行操作
  • 依赖 Salesforce 风格 床单
  • 使用样式表或图形等在页面本身或 静态资源

检查您的 Visualforce 页面 属于这些类别之一,右键单击页面上的任意位置并查看 HTML 源代码。如果 您会看到一个引用 JavaScript 的标记 (.js) 或引用 样式表 (.css),验证生成的 PDF 文件是否显示为 预期。

<script><link>

以 PDF 格式呈现时安全的组件

  • <apex:composition>(只要页面包含 PDF 安全组件)
  • <apex:dataList>
  • <apex:define>
  • <apex:facet>
  • <apex:include>(只要页面包含 PDF 安全组件)
  • <apex:insert>
  • <apex:image>
  • <apex:outputLabel>
  • <apex:outputLink>
  • <apex:outputPanel>
  • <apex:outputText>
  • <apex:page>
  • <apex:panelGrid>
  • <apex:panelGroup>
  • <apex:param>
  • <apex:repeat>
  • <apex:stylesheet>(只要 URL 不是 直接引用 Salesforce 风格 床单)
  • <apex:variable>

以 PDF 格式呈现时要谨慎使用的组件

  • <apex:attribute>
  • <apex:column>
  • <apex:component>
  • <apex:componentBody>
  • <apex:dataTable>

以 PDF 格式呈现时使用不安全的组件

  • <apex:actionFunction>
  • <apex:actionPoller>
  • <apex:actionRegion>
  • <apex:actionStatus>
  • <apex:actionSupport>
  • <apex:commandButton>
  • <apex:commandLink>
  • <apex:detail>
  • <apex:enhancedList>
  • <apex:flash>
  • <apex:form>
  • <apex:iframe>
  • <apex:includeScript>
  • <apex:inputCheckbox>
  • <apex:inputField>
  • <apex:inputFile>
  • <apex:inputHidden>
  • <apex:inputSecret>
  • <apex:inputText>
  • <apex:inputTextarea>
  • <apex:listViews>
  • <apex:message>
  • <apex:messages>
  • <apex:outputField>
  • <apex:pageBlock>
  • <apex:pageBlockButtons>
  • <apex:pageBlockSection>
  • <apex:pageBlockSectionItem>
  • <apex:pageBlockTable>
  • <apex:pageMessage>
  • <apex:pageMessages>
  • <apex:panelBar>
  • <apex:panelBarItem>
  • <apex:relatedList>
  • <apex:scontrol>
  • <apex:sectionHeader>
  • <apex:selectCheckboxes>
  • <apex:selectList>
  • <apex:selectOption>
  • <apex:selectOptions>
  • <apex:selectRadio>
  • <apex:tab>
  • <apex:tabPanel>
  • <apex:toolbar>
  • <apex:toolbarGroup>

由 Visualforce 添加或修改的 HTML 标签

默认情况下,Visualforce 自动将所需的 HTML 标记添加到页面,以确保结果是有效的 HTML(和 XML) 公文。您可以放松甚至覆盖此行为。

对于使用此自动行为的页面,Visualforce 会在两个上下文中添加 HTML 标记:更简单的请求上下文(最初加载页面时)和 呈现;和上下文, 当一个是 提交回来,使用标记发出 Ajax 请求,依此类推。GETPOSTBACK<apex:form><apex:actionXXX>

在上下文中,Visualforce 呈现的 HTML 有些宽松。它添加标签来包装页面,标签来包装页面的标题,以及添加到 页面使用 或 和 标记来包装页面的内容。GET<html><head><apex:stylesheet><apex:includeScript><body>

由其他 Visualforce 标记生成的 HTML 将是完整且有效的 HTML,并且您不能使用无效的静态 XML 保存 Visualforce 页面。 但是,HTML 由访问控制器方法、sObject 字段和其他的表达式添加 非 Visualforce 源未经验证 由 Visualforce 在返回之前。它 因此,可以通过请求返回无效的 XML 文档。GET

在上下文中,Visualforce 更加严格。因为 请求的内容可能需要插入到现有的 DOM 中,响应 HTML 是 后处理以确保其有效。这种“整理”修复了缺失和未关闭的标签,删除了 无效的标记或属性,否则会清除无效的 HTML,以便干净地插入 添加到它返回到的任何页面的 DOM 中。此行为旨在确保标记 更新现有 DOM(例如 )可以可靠地工作。POSTBACK<apex:actionHandler>

HTML5 文档类型的轻松整理

要放宽导致问题的 HTML5 应用程序的默认 HTML 整理,请将 到 “html-5.0” 和 API 版本设置为 28.0 或更高版本。

docType

从 API 版本 28.0 开始,Visualforce 页面的整理行为因上下文而异,因此不会剥离 HTML5 标记和属性 離開。Visualforce 始终验证 XML 保存时每个页面的正确性,并要求页面是格式正确的 XML,但 后处理整理不再删除请求的未知标签或属性。这应该使使用 HTML5 和 JavaScript 变得更加容易 广泛使用 HTML 属性的框架。docType=”html–5.0″POSTBACKPOSTBACK

值得记住的是,虽然现代浏览器非常擅长自己整理, 该行为不如呈现有效标记一致。减少 HTML 整理模式代表较小的安全网,以换取 显著提高灵活性。我们建议您仅在 HTML5 上使用这种轻松整理模式 需要它的页面,并带有 HTML 验证和调试工具。html–5.0

注意

在 API 版本 28.0 或更高版本中,如何确定页面的范围是不同的。将子页面添加到根目录时 页面使用 ,如果 中有任何页面 hierarchy 设置为 并且页面设置为 API 版本 28.0 或更高版本,则整个页面层次结构为 在模式下渲染。docType<apex:include>docType=”html–5.0″html–5.0

手动覆盖自动 <html> 和 <body> 标记 代

使用标记的 and 属性来禁止自动生成 和标记,以支持添加到页面的静态标记 你自己。

applyHtmlTagapplyBodyTag<apex:page><html><body>下面是一个说明如何操作的示例 这:

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

<html>
    <body>
        <header>
            <h1>Congratulations!</h1>
        </header>
        <article>
            <p>This page looks almost like HTML5!</p>
        </article>
    </body>
</html>

</apex:page>

这些属性彼此独立;您可以以 、 或 unset 的任意组合使用它们。当这两个属性都设置为 时,将保留默认的自动生成 和 标记。当任一设置为 时,您全权负责将相应的标签添加到 标记。在此模式下,Visualforce 不会阻止您创建无意义的标签组合或属性,甚至 现代浏览器适合。truefalsetrue<html><body>false

注意

如果出现以下情况,则始终生成一个部分 必需,而不考虑 和 的值。为 例如,如果使用 OR 标记、设置页面等,则会生成标记。<head>applyHtmlTagapplyBodyTag<head><apex:includeScript><apex:stylesheet>title有一个例外 统治。如果设置为 并且 除 之外的页面,则生成 no。例如, 以下代码会自动添加标记,但添加节: 行为不应对实际页面造成问题。

applyHtmlTagfalse<apex:includeScript><head><body><head>

<apex:page showHeader="false" applyHtmlTag="false">
<html>
    <apex:includeScript value="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"/>
</html>
</apex:page>

该属性在设置为 API 的 Visualforce 页面的标记中可用 版本 27.0 或更高版本。该属性在标签上可用 对于设置为 API 的 Visualforce 页面 版本 28.0 或更高版本。它们都有以下附加限制:

applyHtmlTag<apex:page>applyBodyTag<apex:page>

  • 该属性必须设置为页面,例如 .showHeaderfalse<apex:page showHeader=”false”>
  • 该属性必须设置为 “text/html”(默认值)。contentType
  • 使用顶层或最外层标记的值; 并且使用该标记添加的页面上的属性将被忽略。<apex:page>applyHtmlTagapplyBodyTag<apex:include>

创建一个空的 HTML5“容器”页面

当您想要绕过大部分 Visualforce 并添加自己的容器页面时,请使用空容器页面 标记。容器页面对 HTML5 和移动设备特别有用 发展 以及不需要标准 Visualforce 输出的其他 Web 应用。

您使用远程 对象、JavaScript 远程处理或其他 Lightning 平台 API,用于发出服务请求,然后使用 JavaScript 呈现结果。以下代码提供了一个示例容器页以启动 跟。

<apex:page docType="html-5.0" applyHtmlTag="false" applyBodyTag="false"
           showHeader="false" sidebar="false" standardStylesheets="false"
           title="Unused Title">
<html>
    
    <head>
        <title>HTML5 Container Page</title>
    </head>
    
    <body>
        <h1>An Almost Empty Page</h1>
  
        <p>This is a very simple page.</p>
    </body>
    
</html>
</apex:page>

该组件及其属性是 容器页面定义的核心。

<apex:page>

  • docType=”html-5.0″将页面设置为使用 现代 HTML5 文档类型。
  • applyHtmlTag=”false”并告诉 Visualforce 您的标记供应 和标记,这样它就不会生成自己的标记。applyBodyTag=”false”<html><body>注意如果将 或 设置为 false,则组件的属性为 忽视。applyHtmlTagapplyBodyTagtitle<apex:page>
  • 、 和 属性禁止显示标准标题、侧边栏、 以及将 Salesforce 用户界面和视觉设计添加到 Visualforce 的样式表 页面。它还会抑制 JavaScript 资源,例如有助于重定向的脚本 会话超时。showHeader=”false”sidebar=”false”standardStylesheets=”false”

容器页面中不需要该标记,但 包含它是个好主意。如果必须向元素添加值,则必须自行添加标记。在这种情况下,Visualforce 会将其任何必需的值添加到您的 .否则,Visualforce 会自行渲染以添加任何必要的值。<head><head><head><head><head>

您可以使用 Visualforce 组件, 例如 、 和 ,以引用页面上的静态资源。将 和 的输出添加到元素中。如果您没有添加一个,Visualforce 会添加自己的。输出将呈现在您放置的任何位置 页面。<apex:includeScript><apex:stylesheet><apex:image><apex:includeScript><apex:stylesheet><head><apex:image>

注意

“空”的 Visualforce 页面呈现了最少数量的 HTML 标记,但事实并非如此 完全是空的,或者没有你无法控制的资源。必不可少的 JavaScript 代码 仍添加了 Visualforce,例如 instrumentation。Visualforce 还会自动添加 添加标记所需的资源。例如,对 Remote 的引用 对象或 JavaScript 远程处理资源(如果在代码中使用它们)。

使用自定义文档类型

您可以使用标记上的属性为 Visualforce 页面指定不同的“文档类型”(文档类型或 DTD)。这将更改文档类型 声明。如果您使用 HTML5,并且还可能允许您解决浏览器兼容性问题。

docType<apex:page>

默认情况下,Visualforce 页面的文档类型为 HTML 4.01 Transitional。具体说来 页面以以下 DocType 声明开头:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

您可以使用标记上的属性为 Visualforce 页面指定不同的文档类型。docType<apex:page>

该属性采用 一个表示文档类型的字符串。字符串的格式 是:docType

<doctype>-<version>[-<variant>]

哪里

  • doctype是 或htmlxhtml
  • version是十进制版本 对doctype
  • variant,如果包括,则为:
    • strict、 或 对于所有文档类型和文档类型,或者transitionalframesethtmlxhmtl-1.0
    • <空白>或文档类型basicxhmtl-1.1

如果指定了无效的文档类型,则使用默认文档类型。有关的更多信息 有效的 HTML 文档类型,请参阅 W3C 网站上的列表。

注意

在 API 28.0 及更高版本中,如何确定页面的范围取决于整个页面层次结构,而不仅仅是主页。 使用标记将页面添加到主页时,如果层次结构中的任何页面设置为 ,则整个页面 层次结构在该模式下呈现。docType<apex:include>docType=”html-5.0″

自定义文档类型示例

要创建具有 XHTML 1.0 Strict 文档类型的 Visualforce 页面,请使用标记上的属性,并指定 值:docType<apex:page>xhtml-1.0-strict

<apex:page docType="xhtml-1.0-strict" title="Strictly XHTML" 
    showHeader="false" sidebar="false">
    <h1>This is Strict XHTML!</h1>
    <p>
        Remember to close your tags correctly:<br/>
        <apex:image url="/img/icon-person.gif" alt="Person icon"/>
    </p>
</apex:page>

注意

Visualforce 不会更改标记 由组件生成以匹配 doctype,也由标准 Salesforce 元素(如 标题和侧边栏。Salesforce 元素有效 对于大多数文档类型,并且与任何文档类型一起正常运行,但是如果您选择严格 doctype 并希望通过 HTML 验证测试,您可能需要禁止或 替换标准 Salesforce 元素。

使用自定义 ContentType

您可以使用标签上的属性为 Visualforce 页面指定不同的格式。这会将响应的 HTTP 标头设置为 页面的属性。

ContentType<apex:page>Content-TypeContentType该属性采用多用途 Internet 邮件扩展 (MIME) 媒体类型作为值,例如 、 、 或。

ContentTypeapplication/vnd.ms-exceltext/csvimage/gif

注意

浏览器可以行为 如果您设置了无效的 .有关有效 MIME 媒体类型的详细信息,请参阅 http://www.iana.org/assignments/media-types/。ContentType

Microsoft Excel 内容类型示例

要在 Microsoft Excel 电子表格中显示 Visualforce 页面数据,请使用标记上的属性,并指定 的值。contentType<apex:page>application/vnd.ms-excel

例如,以下页面构建了一个简单的联系人列表。这是一个简化的 在页面中构建数据表中所示示例的版本。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/myPage?id=001D000000JRBet -->

    <apex:pageBlock title="Contacts">
        <apex:pageBlockTable value="{!account.Contacts}" var="contact">
            <apex:column value="{!contact.Name}"/>
            <apex:column value="{!contact.MailingCity}"/>
            <apex:column value="{!contact.Phone}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

若要在 Excel 中显示此页,请将属性添加到标记中,如下所示:contentType<apex:page>

<apex:page standardController="Account" contentType="application/vnd.ms-excel">
    <apex:pageBlock title="Contacts">
        <apex:pageBlockTable value="{!account.Contacts}" var="contact">
            <apex:column value="{!contact.Name}"/>
            <apex:column value="{!contact.MailingCity}"/>
            <apex:column value="{!contact.Phone}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

如果页面在 Excel 中无法正确显示,请尝试其他 MIME 类型,如 .text/csv

在 Visualforce 组件上设置自定义 HTML 属性

您可以添加任意属性 到许多 Visualforce 组件 被“传递”到呈现的 HTML。例如,当将 Visualforce 与 JavaScript 一起使用时,这很有用 框架,例如 jQuery Mobile、AngularJS 和 Knockout,它们使用或其他属性作为钩子来激活框架 功能。

data-*传递属性还可用于提高 HTML5 功能(如“幻影”文本、客户端验证和帮助文本属性)的可用性。

placeholderpatterntitle

重要

这 HTML5 功能的行为由用户的浏览器决定,而不是由 Visualforce 决定,并且各不相同 从浏览器到浏览器。如果要使用这些功能,请尽早测试 并且通常在您计划支持的每个浏览器和设备上。要向 for 添加传递属性,请执行以下操作 例如,一个组件, 在属性前面加上“html-”,并将属性值设置为 正常。

<apex:outputPanel>

<apex:page showHeader="false" standardStylesheets="false" doctype="html-5.0">

    <apex:outputPanel layout="block" html-data-role="panel" html-data-id="menu">
        <apex:insert name="menu"/>    
    </apex:outputPanel>

    <apex:outputPanel layout="block" html-data-role="panel" html-data-id="main">
        <apex:insert name="main"/>    
    </apex:outputPanel>

</apex:page>

这将生成以下 HTML 输出。

<!DOCTYPE HTML>
<html>
<head> ... </head>
<div id="..." data-id="menu" data-role="panel">
    <!-- contents of menu -->
</div>

<div id="..." data-id="main" data-role="panel">
    <!-- contents of main -->
</div>
</html>

每 以“html-”开头的属性将传递到生成的 HTML,并使用 “html-”已删除。

注意

与内置属性冲突的直通属性 组件生成编译错误。以下 Visualforce 组件支持直通属性。

  • <apex:column>
  • <apex:commandButton>
  • <apex:commandLink>
  • <apex:component>
  • <apex:dataTable>
  • <apex:form>
  • <apex:iframe>
  • <apex:image>
  • <apex:includeScript>
  • <apex:input>
  • <apex:inputCheckbox>
  • <apex:inputField>
  • <apex:inputHidden>
  • <apex:inputSecret>
  • <apex:inputText>
  • <apex:inputTextarea>
  • <apex:messages>
  • <apex:outputField>
  • <apex:outputLabel>
  • <apex:outputLink>
  • <apex:outputPanel>
  • <apex:outputText>
  • <apex:page>
  • <apex:pageBlock>
  • <apex:pageBlockButtons>
  • <apex:pageBlockSection>
  • <apex:pageBlockSectionItem>
  • <apex:pageBlockTable>
  • <apex:panelBar>
  • <apex:panelBarItem>
  • <apex:panelGrid>
  • <apex:sectionHeader>
  • <apex:selectCheckboxes>
  • <apex:selectList>
  • <apex:selectOption>
  • <apex:selectOptions>
  • <apex:selectRadio>
  • <apex:stylesheet>
  • <apex:tab>
  • <apex:tabPanel>

有关各个组件的其他信息,包括 将传递属性添加到其位置的细节 呈现的 HTML,请参阅标准组件参考。创建无法使用支持传递的组件生成的 HTML 标记 属性,结合 Visualforce 标记替换为静态 HTML。例如,要创建 jQuery Mobile ,请将标记与 HTML 标记组合在一起 需要。

listview<apex:repeat>

<ul data-role="listview" data-inset="true" data-filter="true">
    <apex:repeat value="{! someListOfItems}" var="item">
        <li><a href="#">{! item.Name}</a></li>
    </apex:repeat>
</ul>

动态 Visualforce 不支持直通属性。