动态 Visualforce 组件

Visualforce 的主要目的是 一种静态的标记驱动语言,允许开发人员创建与 Salesforce 外观。但是,有 是需要以编程方式创建页面的情况。通常,这是为了 实现标准难以或不可能实现的复杂用户界面行为 标记。

动态视觉力 组件提供了一种创建 Visualforce 页面的方法,该页面根据各种 状态,例如用户的权限或操作、用户或组织首选项、 正在显示的数据,等等。动态 Visualforce 组件不是使用标准标记,而是在 顶点。动态 Visualforce 组件在 Apex 中定义,如下所示 这:

Component.Component_namespace.Component_name

为 示例,则变为 .

<apex:dataTable>Component.Apex.DataTable

注意

标准元件参考包含 所有有效 Visualforce 组件的动态表示。在 Apex 中动态表示的 Visualforce 组件的行为类似于常规类。每 存在于标准 Visualforce 组件上的属性可作为 属性,以及 get 和 set 方法。例如,您 可以将组件上的属性操作为 遵循:

value<apex:outputText>

Component.Apex.OutputText outText = new Component.Apex.OutputText();
outText.value = 'Some dynamic output text.';

请考虑在以下情况下使用动态 Visualforce 组件:

  • 您可以使用动态 Visualforce 复杂控制逻辑中的组件,以组合方式组装组件 使用同等标准的 Visualforce 进行创作具有挑战性或不可能。例如,使用 标准 Visualforce 组件,您 通常使用具有全局公式函数的属性来控制组件的可见性。通过在 Apex 中编写控制逻辑,您可以选择显示组件 动态地具有更自然的机制。renderedIF()
  • 如果您知道将遍历具有某些字段的对象,但不是 具体到哪些对象,动态 Visualforce 组件可以“插入 in“,使用泛型 sObject 引用表示对象。查看更多 信息,请参阅使用相关列表的示例。

警告

动态视觉力 组件不是在组织中创建新 Visualforce 页面的主要方式。 现有 Visualforce 页面 不应以动态方式重写,对于大多数用例,标准的 Visualforce 组件是可以接受的,并且 首选。仅当页面必须时,才应使用动态 Visualforce 组件 以无法优雅地编码为静态的方式适应用户状态或操作 标记。

动态组件限制

并非 Visualforce 的每个功能都能使 在动态上下文中感知,因此某些组件不能动态使用。

  • 以下标准 Visualforce 组件在 Apex 中没有相应的动态表示:
    • <apex:attribute>
    • <apex:component>
    • <apex:componentBody>
    • <apex:composition>
    • <apex:define>
    • <apex:dynamicComponent>
    • <apex:include>
    • <apex:insert>
    • <apex:param>
    • <apex:variable>
  • 如果动态 Visualforce 组件 引用特定的 sObject 字段,该字段稍后被删除,即该字段的 Apex 代码 字段引用仍将编译,但页面在查看时将失败。此外,您还可以 创建对全局变量(如 或)的引用,然后删除引用的项,使用 类似的结果。请验证此类页面是否继续按预期工作。$Setup$Label
  • 动态 Visualforce 页面和 表达式比静态页面更严格地检查属性类型。
  • 您无法在动态上设置“直通”HTML 属性 组件。

创建和显示动态组件

注意

出于教学目的,本节中的示例特意简单。对于一个 有关何时可能从动态 Visualforce 组件中受益的更完整示例,请参阅使用相关列表的示例。在页面上嵌入动态 Visualforce 组件分为两部分:

  1. 添加标签 页面上的某个位置。此标记充当动态的占位符 元件。<apex:dynamicComponent>
  2. 在 您的控制器或控制器扩展。

标签有一个 required 属性——接受返回 动态组件。例如,如果要动态生成 部分标题不同 如果提交表单的截止日期已过,您可以 使用以下标记和控制器 法典:

<apex:dynamicComponent>componentValue

<apex:page standardController="Contact" extensions="DynamicComponentExample">
    <apex:dynamicComponent componentValue="{!headerWithDueDateCheck}"/>
    <apex:form>
        <apex:inputField value="{!Contact.LastName}"/> 
        <apex:commandButton value="Save" action="{!save}"/> 
    </apex:form> 
</apex:page>
public class DynamicComponentExample {
    public DynamicComponentExample(ApexPages.StandardController con) { }
    public Component.Apex.SectionHeader getHeaderWithDueDateCheck() {
        date dueDate = date.newInstance(2011, 7, 4);
        boolean overdue = date.today().daysBetween(dueDate) < 0;

        Component.Apex.SectionHeader sectionHeader = new Component.Apex.SectionHeader();
        if (overdue) {
            sectionHeader.title = 'This Form Was Due On ' + dueDate.format() + '!';
            return sectionHeader;
        } else {
            sectionHeader.title = 'Form Submission';
            return sectionHeader;
        }
    }
}

你 单个页面上可以有多个组件。

<apex:dynamicComponent>

每 动态组件可以访问一组通用的方法和属性。您可以查看 此列表在 Apex 开发人员指南中标题为“组件类”的章节中。

动态自定义组件

动态使用自定义组件的工作方式与标准 Visualforce 组件完全相同。只 将命名空间更改为自定义组件的命名空间。您的自定义组件位于 命名空间,以便您可以创建一个命名空间 动态喜欢 这:

c

Component.c.MyCustomComponent myDy = new Component.c.MyCustomComponent();

为了方便您自己的组件,您可以省略命名空间,例如 所以:

Component.MyCustomComponent myDy = new Component.MyCustomComponent();

如果在包中使用第三方提供的组件,请使用命名空间 包装的 供应商:

Component.TheirName.UsefulComponent usefulC = new Component.TheirName.UsefulComponent();

通过构造函数传递属性

无需通过其属性设置组件属性,只需传入即可 一个或多个属性的列表,通过 构造 函数:

Component.Apex.DataList dynDataList = 
    new Component.Apex.DataList(id='myDataList', rendered=true);

如果 构造函数中未定义属性,即组件的默认值 用于该属性。有两个组件必须在构造函数中定义一个属性: 而不是通过属性:

  • Component.Apex.Detail必须已经传递给它的 构造函数,如果要显示 的 Chatter 信息和控件 一个记录。否则,此属性始终为 false。showChatter=true
  • Component.Apex.SelectList必须已经传递给它的 构造函数(如果希望用户能够选择多个选项) 一次。否则,此值始终为 。multiSelect=truefalse

这些值是布尔值,而不是字符串;你不需要把它们封闭起来 用单引号引起来。

警告

不能通过类构造函数传递属性 如果属性名称与 Apex 关键字匹配。例如,不能通过构造函数, 因为 List 是保留关键字。同样,无法在构造函数中定义属性,因为 这也是一个关键词。Component.Apex.RelatedListlistComponent.Apex.OutputLabelfor

定义表达式和任意 HTML

可以使用该属性添加表达式语言语句。追加在属性名称之前以传入表达式语句。 与静态标记一样,表达式必须使用语法进行包装。这是一个 例:

expressionsexpressions{! }

Component.Apex.Detail detail = new Component.Apex.Detail();
detail.expressions.subject = '{!Account.ownerId}';
detail.relatedList = false;
detail.title = false;

有效表达式包括引用标准对象和自定义对象上的字段的表达式。 全局变量和函数也可用,如下所示 例:

Component.Apex.OutputText head1 = new Component.Apex.OutputText();
head1.expressions.value = 
    '{!IF(CONTAINS($User.FirstName, "John"), "Hello John", "Hey, you!")}';

通过表达式传入值仅对支持值的属性有效。 在酒店外使用将被解释 从字面上看,不是作为表达。{! }expressions如果要包含纯 HTML,可以通过将属性设置为:

escapeComponent.Apex.OutputTextfalse

Component.Apex.OutputText head1 = new Component.Apex.OutputText();
head1.escape = false;
head1.value = '<h1>This header contains HTML</h1>';

定义分面

与定义表达式的方式类似,分面充当特殊属性 可用于动态组件。这是一个 例:

Component.Apex.DataTable myTable = new Component.Apex.DataTable(var='item');
myTable.expressions.value = '{!items}'; 
Component.Apex.OutputText header = 
    new Component.Apex.OutputText(value='This is My Header');
myTable.facets.header = header;

有关分面的详细信息,请参阅使用组件分面的最佳实践。

定义子节点

您可以使用以下命令将子节点添加到动态 Visualforce 组件 该物业。该属性充当对 a 对象清单。childComponentschildComponentsComponent.Apex下面是一个示例,说明如何使用子输入来构造 节点:

childComponents<apex:form>

public Component.Apex.PageBlock getDynamicForm() {
    Component.Apex.PageBlock dynPageBlock = new Component.Apex.PageBlock();

    // Create an input field for Account Name
    Component.Apex.InputField theNameField = new Component.Apex.InputField();
    theNameField.expressions.value = '{!Account.Name}';
    theNameField.id = 'theName';
    Component.Apex.OutputLabel theNameLabel = new Component.Apex.OutputLabel();
    theNameLabel.value = 'Rename Account?';
    theNameLabel.for = 'theName';
    
    // Create an input field for Account Number
    Component.Apex.InputField theAccountNumberField = new Component.Apex.InputField();
    theAccountNumberField.expressions.value = '{!Account.AccountNumber}';
    theAccountNumberField.id = 'theAccountNumber';
    Component.Apex.OutputLabel theAccountNumberLabel = new Component.Apex.OutputLabel();
    theAccountNumberLabel.value = 'Change Account #?';
    theAccountNumberLabel.for = 'theAccountNumber';
    
    // Create a button to submit the form
    Component.Apex.CommandButton saveButton = new Component.Apex.CommandButton();
    saveButton.value = 'Save';
    saveButton.expressions.action = '{!Save}';
    
    // Assemble the form components
    dynPageBlock.childComponents.add(theNameLabel);
    dynPageBlock.childComponents.add(theNameField);
    dynPageBlock.childComponents.add(theAccountNumberLabel);
    dynPageBlock.childComponents.add(theAccountNumberField);
    dynPageBlock.childComponents.add(saveButton);
    
    return dynPageBlock;
}

如果标记定义为:

<apex:form>
    <apex:dynamicComponent componentValue="{!dynamicForm}"/>
</apex:form>

然后 您的标记等效于以下静态 标记:

<apex:form>
    <apex:pageBlock>
        <apex:outputLabel for="theName"/>
        <apex:inputField value="{!Account.Name}" id="theName"/>
        <apex:outputLabel for="theAccountNumber"/>
        <apex:inputField value="{!Account.AccountNumber}" id="theAccountNumber"/>
        <apex:commandButton value="Save" action="{!save}"/>
    </apex:pageBlock>
</apex:form>

通知 等效静态标记中元素的顺序是 动态组件被添加到 ,而不是它们在 Apex 中声明的顺序 方法的代码。

childComponentsgetDynamicForm

延迟创建动态组件

默认情况下,定义动态组件的 Apex 方法在页面加载时执行 时间,在运行为页面定义的任何操作方法之前。将动态组件的属性设置为等待页面操作完成 在创建动态组件的方法运行之前。这使您能够设计 动态组件,根据页面的结果而变化,例如,页面 初始化操作或标注。

invokeAfterActiontrue下面是一个具有单个动态组件的页面,该组件是在 页面的 action 方法 , 是 完成。

pageActionUpdateMessage

<apex:page controller="DeferredDynamicComponentController" 
    action="{!pageActionUpdateMessage}" showHeader="false">
    
    <apex:dynamicComponent componentValue="{!dynamicComp}" invokeAfterAction="true"/>
  
</apex:page>

下面是提供动态组件定义的关联控制器: 并说明了属性的效果。

invokeAfterAction

public class DeferredDynamicComponentController {

    private String msgText { get; set; }

    public DeferredDynamicComponentController() {  
        this.msgText = 'The controller is constructed.';
    }
    
    public Component.Apex.OutputPanel getDynamicComp() {

        // This is the component to return
        Component.Apex.OutputPanel dynOutPanel= new Component.Apex.OutputPanel();
        dynOutPanel.layout = 'block';
        
        // Child component to hold the message text
        Component.Apex.OutputText msgOutput = new Component.Apex.OutputText();
        msgOutput.value = this.msgText;
        dynOutPanel.childComponents.add(msgOutput);
        
        return dynOutPanel;
    }
    
    public Object pageActionUpdateMessage() {
        this.msgText= 'The page action method has been run.';
        return null;
    }
}

跟 动态组件的默认行为,在构造函数中设置的值由 动态组件。在动态组件上进行设置会更改该行为。 页面等待 完成,然后创建动态组件,因此组件 显示已设置的值 在 Action 方法中 相反。

msgTextinvokeAfterAction=”true”pageActionUpdateMethodmsgTextpageActionUpdateMessage

注意

该属性可用 对于设置为 API 版本 31.0 或更高版本的页面中的动态组件。invokeAfterAction

延迟创建动态组件和其他操作

invokeAfterAction=”true”影响动态 组件在页面加载时立即生效,因为那是页面操作的时候 跑。设置将颠倒组件创建顺序和页面上的任何操作方法。 也就是说,以下所有组件上的方法的执行顺序都已更改。

invokeAfterAction=”true”action

  • <apex:actionFunction>
  • <apex:actionPoller>
  • <apex:actionSupport>
  • <apex:commandButton>
  • <apex:commandLink>
  • <apex:page>

When 设置为 动态组件,执行顺序如下。这是默认设置 动态组件的行为。

invokeAfterAction=”false”

  1. 调用动态组件的创建方法,该方法构造 元件。
  2. 调用 action 方法。
  3. 重新呈现页面。

When 设置为 动态组件,执行顺序如下。

invokeAfterAction=”true”

  1. 调用 action 方法。
  2. 调用动态组件的创建方法,该方法构造 元件。
  3. 重新呈现页面。

注意

在第二种情况下,如果操作方法返回 PageReference,则 Visualforce 将重定向 对新页面的请求,以及动态组件的创建方法 不会运行。为了避免可能的执行顺序错误,这是一个 创建动态组件的方法没有侧的最佳实践 影响。

使用相关列表的示例

动态 Visualforce 组件包括 当您不知道要引用的对象类型时,最好使用 与动态 Visualforce 绑定相反, 当您不知道要访问的字段时,最好使用。

以下使用动态 Visualforce 的场景构建了一个简单的 具有要访问的一组已知字段的可重用页面。页面及其自定义 对象被放置在一个非托管包中,并分布在同一个包中 组织。首先,创建一个名为 Classroom 的自定义对象。创建两个对象 – 一个命名,另一个命名,如下图所示:

Science 101Math 201

接下来,再创建两个自定义对象,分别称为 Student 和 Teacher。完成后 创建每个对象:

  1. 单击“自定义字段”下的“新建& 关系
  2. 选择“主从关系”,然后单击“下一步”。
  3. 从下拉列表中选择“课堂”,然后单击“下一步”。
  4. 继续单击“下一步”,保留所有默认值 完整。

创建以下对象和匹配关系:

  • 一个名为 的新学生和新老师 named ,两者都分配给 。Johnny WalkerMister PibbScience 101
  • 另一个名叫 的新学生和一位新老师 named ,两者都分配给 。Boont AmberDoctor PepperMath 201

现在,创建一个新的 Apex 页面,称为并粘贴 以下 法典:

DynamicClassroomList

public class DynamicClassroomList {

    private ApexPages.StandardSetController controller;
    private PageReference savePage;
    private Set<String> unSelectedNames;
    private Set<String> selectedNames;
    
    public List<String> selected { get; set; }
    public List<String> unselected { get; set; }
    public String objId { get; set; }
    public List<String> displayObjs {
        get; private set;
    }
    
    boolean idIsSet = false;
    
    public DynamicClassroomList() {
        init();
    }

    public DynamicClassroomList(ApexPages.StandardSetController con) {
        this.controller = con;
        init();
    }

    private void init() {
        savePage = null;
        unSelectedNames = new Set<String>();
        selectedNames = new Set<String>();
         
        if (idIsSet) {
            ApexPages.CurrentPage().getParameters().put('id', objId);
            idIsSet = false;
        }
    }
    
    public PageReference show() {
        savePage = Page.dynVFClassroom;
        savePage.getParameters().put('id', objId);
        return savePage;
    }

    public List<SelectOption> displayObjsList { 
        get {
            List<SelectOption> options = new List<SelectOption>();
            List<Classroom__c> classrooms = [SELECT id, name FROM Classroom__c];
            
            for (Classroom__c c: classrooms) {
                options.add(new SelectOption(c.id, c.name)); 
            }
            
            return options;
       }
    }
    
    public PageReference customize() {
        savePage = ApexPages.CurrentPage();
        savePage.getParameters().put('id', objId);
        
        return Page.dynamicclassroomlist;
    }

    // The methods below are for constructing the select list  

    public List<SelectOption> selectedOptions { 
        get {
            List<String> sorted = new List<String>(selectedNames);
            sorted.sort();
            List<SelectOption> options = new List<SelectOption>();
            for (String s: sorted) {
                options.add(new SelectOption(s, s));
            }
            return options;
        }
    }
    
    public List<SelectOption> unSelectedOptions { 
        get {
            Schema.DescribeSObjectResult R = Classroom__c.SObjectType.getDescribe();
            List<Schema.ChildRelationship> C = R.getChildRelationships(); 
            List<SelectOption> options = new List<SelectOption>();
            
            for (Schema.ChildRelationship cr: C) {
                String relName = cr.getRelationshipName();
                // We're only interested in custom relationships
                if (relName != null && relName.contains('__r')) {
                    options.add(new SelectOption(relName, relName));
                }
            }
            return options;
        }
    }


    public void doSelect() {
        for (String s: selected) {
            selectedNames.add(s);
            unselectedNames.remove(s);
        }
    }

    public void doUnSelect() {
        for (String s: unselected) {
            unSelectedNames.add(s);
            selectedNames.remove(s);
        }
    }

    public Component.Apex.OutputPanel getClassroomRelatedLists() {
        Component.Apex.OutputPanel dynOutPanel= new Component.Apex.OutputPanel();
        
        for(String id: selectedNames) {
           Component.Apex.RelatedList dynRelList = new Component.Apex.RelatedList();
           dynRelList.list = id;
           dynOutPanel.childComponents.add(dynRelList);
        }
        
        return dynOutPanel;
    }
}

后 尝试保存时,系统可能会提示您缺少 Visualforce 页面。点击链接 创建页面:接下来的代码块将填充该页面。创建一个名为 Visualforce 的页面,并粘贴以下内容 法典:

dynVFClassroom

<apex:page standardController="Classroom__c" recordSetVar="classlist"
    extensions="DynamicClassroomList">
    
    <apex:dynamicComponent componentValue="{!ClassroomRelatedLists}"/>
    
    <apex:form>
        
        <apex:pageBlock title="Classrooms Available" mode="edit">
            <apex:pageMessages/>
            <apex:selectRadio value="{!objId}">
                <apex:selectOptions value="{!displayObjsList}"/>
            </apex:selectRadio>
        </apex:pageBlock>

        <apex:commandButton value="Select Related Items" action="{!Customize}"/>
    </apex:form>
    
</apex:page>

最后,创建一个名为 .如果 您从一开始就一直在学习本教程,您应该已经 在构造控制器扩展时创建了此页面。粘贴以下内容 法典:

DynamicClassroomList

<apex:page standardController="Classroom__c" recordsetvar="listPageMarker" 
    extensions="DynamicClassroomList">
    <apex:messages/><br/>
    <apex:form>
        <apex:pageBlock title="Select Relationships to Display" id="selectionBlock">
            <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="{!DoSelect}" 
                        reRender="selectionBlock"/>
                    <br/>
                    <apex:commandButton value="<<" action="{!DoUnselect}" 
                        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>
        </apex:pageBlock>
        <br/>
        <apex:commandButton value="Show Related Lists" action="{!show}"/>
    </apex:form>
</apex:page>

这 是向用户提供选择哪个对象的选项的页面 要显示的关系。请注意,“selected”和 “未选择”列表通过动态方式填充。

组装控制器扩展和这些页面后,导航到组织中的 /apex/dynVFClassroom。你会看到一个 类似于以下内容的序列:

“课堂”关系选择的屏幕截图