Visualforce 图表

Visualforce 图表是一组组件,它们提供了一种简单直观的创建方式 Visualforce 页面中的图表和 自定义组件。

什么是 Visualforce Charting?

Visualforce 图表为您提供了一种简单的方法 根据您直接从 SOQL 查询创建的数据集创建自定义业务图表, 或者通过在您自己的 Apex 代码中构建数据集。通过组合和配置单个数据 系列中,您可以编写图表,以对您有意义的方式显示您的数据 组织。

呈现 Visualforce 图表 使用 JavaScript 的客户端。这允许图表是动画的和视觉上令人兴奋的,并且图表 数据可以异步加载和重新加载,这可以使页面感觉更灵敏。

为什么要使用 Visualforce Charting?

使用 Visualforce 图表时 标准 Salesforce 图表和仪表板 是不够的,或者当您希望编写组合图表和数据表的自定义页面时 以对组织更有用的方式。

Visualforce Charting 的替代品

Salesforce 提供了许多 仪表板和报表,支持各种业务图表。这些图表可以更简单 进行创建和自定义,因为它们不需要在 Visualforce 或 Apex 中编程。

Visualforce 图表旨在 灵活,但也易于使用。它通常提供条形图、折线图、面积图和饼图的变化 用于商业图形,以及雷达图、仪表图和散点图,以实现更专业 制图。如果您需要不同的图表类型,或者想要添加高级用户或页面交互, 您可能希望改用 JavaScript 图表库进行调查。这是更多的工作, 但允许更大的定制。例如,请参阅集成 Visualforce 和 Google Charts。在 Visualforce 页面中使用 JavaScript 提供了有关如何在 Visualforce 中使用 JavaScript 库的更多信息。

Visualforce 图表限制和 考虑

本部分列出了 Visualforce Charting 的注意事项和已知限制。

  • Visualforce 图表仅在 支持可缩放矢量图形 (SVG) 的浏览器。有关详细信息,请参阅 WC3 SVG 工作 组。
  • Visualforce 图表使用 JavaScript 绘制图表。Visualforce 图表不会显示在呈现为 PDF 的页面中。
  • 电子邮件客户端通常不支持在邮件中执行 JavaScript。不要在电子邮件中使用 Visualforce 图表,或者 电子邮件模板。
  • Visualforce 图表发送错误和 消息发送到 JavaScript 控制台。使 JavaScript 调试工具(如 Firebug)保持活动状态 在开发过程中。
  • 目前不支持动态(顶点生成的)图表组件。

Visualforce Charting 的工作原理

定义了 Visualforce 图表 使用一系列图表组件,然后将这些组件链接到要在 图表。使用 Visualforce 创建图表 以下内容:

  1. 编写一个 Apex 方法,用于查询、计算和包装图表数据以发送到 浏览器。
  2. 使用 Visualforce 图表组件定义图表。

当包含图表的页面加载时,图表数据绑定到图表组件,并且 生成绘制图表的 JavaScript。当 JavaScript 执行时,图表会被绘制进去 浏览器。

一个简单的图表示例

Visualforce 图表要求您创建一个图表容器组件,该组件 包含至少一个数据系列组件。您可以选择添加 附加系列组件、图表轴以及标签组件 例如图例、图表标签和数据点的工具提示。下面是一个简单的饼图和创建它的标记:

<apex:page controller="PieChartController" title="Pie Chart">
    <apex:chart height="350" width="450" data="{!pieData}">
        <apex:pieSeries dataField="data" labelField="name"/>
        <apex:legend position="right"/>
    </apex:chart>
</apex:page>

组件 定义图表容器,并将组件绑定到数据源, 控制器 方法。描述要在返回的数据中访问的标签和数据字段, 标记每个数据点并调整其大小。<apex:chart>getPieData()<apex:pieSeries>下面是关联的控制器:

public class PieChartController {
    public List<PieWedgeData> getPieData() {
        List<PieWedgeData> data = new List<PieWedgeData>();
        data.add(new PieWedgeData('Jan', 30));
        data.add(new PieWedgeData('Feb', 15));
        data.add(new PieWedgeData('Mar', 10));
        data.add(new PieWedgeData('Apr', 20));
        data.add(new PieWedgeData('May', 20));
        data.add(new PieWedgeData('Jun', 5));
        return data;
    }

    // Wrapper class
    public class PieWedgeData {

        public String name { get; set; }
        public Integer data { get; set; }

        public PieWedgeData(String name, Integer data) {
            this.name = name;
            this.data = data;
        }
    }
}

这个控制器刻意简单;你通常 发出一个或多个 SOQL 查询以收集数据。以下是示例所说明的要点:

  • 方法 返回简单对象的 List,使用的内部类 PieWedgeData 作为包装器。列表中的每个元素都用于创建数据点。getPieData()
  • PieWedgeData 类只是一组属性,本质上是 用作 name= store。value
  • 图表系列组件定义 PieWedgeData 类中用于确定的属性 系列中的每一点。在这个简单的例子中,没有 神秘,但在具有多个系列和轴的图表中,此约定 允许在一个 List 对象中高效返回整个数据集。<apex:pieSeries>

提供图表数据

Visualforce 图表通过 data 属性绑定到其数据源 在组件上。

<apex:chart>可以通过几种不同的方式提供数据:

  • 作为表示 控制器方法参考
  • 作为表示 JavaScript 函数
  • 作为表示 JavaScript 数组

通过控制器方法提供图表数据

向图表提供数据的最直接方法是使用 Visualforce 表达式,该表达式 引用控制器方法。只需在属性中引用控制器即可。

<apex:chart>data

在服务器端,编写一个返回对象列表的控制器方法,该方法可以 是您自己的 Apex 包装器对象,如简单图表示例、sObject 或对象中所示。该方法在服务器端进行评估,并将结果序列化为 JSON。在 客户端,这些结果由 直接使用 ,没有进一步的处理机会。AggregateResult<apex:chart>

为了用 sObjects 来说明这种技术,这里有一个简单的控制器,它返回一个 商机列表,以及其金额的条形图:

public class OppsController {
    
    // Get a set of Opportunities
    public ApexPages.StandardSetController setCon {
        get {
            if(setCon == null) {
                setCon = new ApexPages.StandardSetController(Database.getQueryLocator(
                      [SELECT name, type, amount, closedate FROM Opportunity]));
                setCon.setPageSize(5);
            }
            return setCon;
        }
        set;
    }
    
    public List<Opportunity> getOpportunities() {
         return (List<Opportunity>) setCon.getRecords();
    }
}
<apex:page controller="OppsController">
    <apex:chart data="{!Opportunities}" width="600" height="400">
        <apex:axis type="Category" position="left" fields="Name" title="Opportunities"/>
        <apex:axis type="Numeric" position="bottom" fields="Amount" title="Amount"/>
        <apex:barSeries orientation="horizontal" axis="bottom" 
            xField="Name" yField="Amount"/>
    </apex:chart>
    <apex:dataTable value="{!Opportunities}" var="opp">
        <apex:column headerValue="Opportunity" value="{!opp.name}"/>
        <apex:column headerValue="Amount" value="{!opp.amount}"/>
    </apex:dataTable>
</apex:page>

关于此示例,有两件重要事项需要注意:

  • Visualforce 图表 组件从 List of Opportunity sObjects 中访问数据属性 与简单图表示例中使用的简单 Data 对象相同。
  • 在 JavaScript 中,用作数据属性的对象字段名称区分大小写 而 Apex 和 Visualforce 中的字段名称是 区分大小写。请注意在轴和数据系列组件的 、 和 属性中使用精确的字段名称,或者 图表将静默失败。fieldsxFieldyField

使用 JavaScript 提供图表数据 功能

使用 JavaScript 远程处理或外部(非 Salesforce)访问数据 数据源,则为组件提供 JavaScript 函数的名称,该函数提供 数据。该 JavaScript 函数必须在 Visualforce 页面中定义或链接。

<apex:chart>

此函数有机会在之前操作结果 将其传递给 ,或执行其他用户界面或页面更新。<apex:chart>JavaScript 函数必须将回调函数作为参数, 并使用函数的数据结果对象调用回调。这 最简单的工作 JavaScript 函数如下所示:

<apex:page>
    <script>
    function getRemoteData(callback) {
       PieChartController.getRemotePieData(function(result, event) {
           if(event.status && result && result.constructor === Array) {
               callback(result);
           }
       });
    }
    </script>

    <apex:chart data="getRemoteData" ...></apex:chart>
</apex:page>

若要支持此图表,请将以下控制器方法添加到简单图表示例中定义的类:PieChartController

@RemoteAction
public static List<PieWedgeData> getRemotePieData() {
    List<PieWedgeData> data = new List<PieWedgeData>();
    data.add(new PieWedgeData('Jan', 30));
    data.add(new PieWedgeData('Feb', 15));
    data.add(new PieWedgeData('Mar', 10));
    data.add(new PieWedgeData('Apr', 20));
    data.add(new PieWedgeData('May', 20));
    data.add(new PieWedgeData('Jun',  5));
    return data;
}

通过 JavaScript 数组提供图表数据

您可以使用 Visualforce 使用非 Salesforce 数据绘制图表 sources,通过构建一个 JavaScript 数组,在页面中你自己的 JavaScript 代码中,以及 将该数组的名称提供给 。

<apex:chart>以下简单的代码对此进行了说明 技术:

<apex:page>
    <script>
    // Build the chart data array in JavaScript
    var dataArray = new Array();
    dataArray.push({'data1':33,'data2':66,'data3':80,'name':'Jan'});
    dataArray.push({'data1':33,'data2':66,'data3':80,'name':'Feb'});
    // ...
    </script>

    <apex:chart data="dataArray" ...></apex:chart>
</apex:page>

使用此技术时,如果您的数据来自非 Salesforce 源,则可能不会 根本不需要任何服务器端 Apex 代码。

图表数据格式

提供给 Visualforce 的数据 图表必须满足某些特定要求。数据集合中的每个元素都必须包含所有 组件中引用的字段 绑定到该数据源的层次结构。如果未提供所有字段,则客户端 抛出 JavaScript 错误,您可以在 JavaScript 控制台中查看该错误,例如 萤火虫。

<apex:chart>

Apex 方法提供的图表数据应为统一对象的列表。这些对象可以是 简单的包装器、sObject 或对象。 数据字段可以作为公共成员变量或属性进行访问。AggregateResult

JavaScript 方法提供的图表数据应该是数组的 JavaScript 数组。每个内部 数组表示记录或数据点。数据字段可作为名称:值对进行访问。 有关示例,请参阅通过 JavaScript 数组提供图表数据。

使用 Visualforce Charting 构建复杂图表

使用 Visualforce 图表将各种图表组件组装成一个复杂的图表组件 表示多组相关数据的图表。最终结果 可以非常复杂和引人注目。

图表控制器

后面的例子 在本主题中,请使用以下控制器,这是一个适度的扩展 控制器的简单图表示例。它包含更多数据,以及可以调用的方法 通过远程 JavaScript 调用:

public class ChartController {
    // Return a list of data points for a chart
    public List<Data> getData() {
        return ChartController.getChartData();
    }
    
    // Make the chart data available via JavaScript remoting
    @RemoteAction
    public static List<Data> getRemoteData() {
        return ChartController.getChartData();
    }

    // The actual chart data; needs to be static to be
    // called by a @RemoteAction method
    public static List<Data> getChartData() {
        List<Data> data = new List<Data>();
        data.add(new Data('Jan', 30, 90, 55));
        data.add(new Data('Feb', 44, 15, 65));
        data.add(new Data('Mar', 25, 32, 75));
        data.add(new Data('Apr', 74, 28, 85));
        data.add(new Data('May', 65, 51, 95));
        data.add(new Data('Jun', 33, 45, 99));
        data.add(new Data('Jul', 92, 82, 30));
        data.add(new Data('Aug', 87, 73, 45));
        data.add(new Data('Sep', 34, 65, 55));
        data.add(new Data('Oct', 78, 66, 56));
        data.add(new Data('Nov', 80, 67, 53));
        data.add(new Data('Dec', 17, 70, 70));
        return data;
    }
    
    // Wrapper class
    public class Data {
        public String name { get; set; }
        public Integer data1 { get; set; }
        public Integer data2 { get; set; }
        public Integer data3 { get; set; }
        public Data(String name, Integer data1, Integer data2, Integer data3) {
            this.name = name;
            this.data1 = data1;
            this.data2 = data2;
            this.data3 = data3;
        }
    }
}

注意

本主题的图表示例中未使用该方法,但 它说明了如何将数据生成方法重用 服务器端和 JavaScript 远程处理方法。@RemoteAction

创建一个简单的 折线图

这是一个简单的折线图,它绘制了以下图表之一 数据集中的三个数据系列,“Opportunity Closed-Won”, 在一个日历年内:

<apex:page controller="ChartController">
    <apex:chart height="400" width="700" data="{!data}">
    	  <apex:axis type="Numeric" position="left" fields="data1" 
            title="Opportunities Closed" grid="true"/>
    	  <apex:axis type="Category" position="bottom" fields="name" 
            title="Month of the Year">
    	</apex:axis>
    	<apex:lineSeries axis="left" fill="true" xField="name" yField="data1"
          markerType="cross" markerSize="4" markerFill="#FF0000"/>
   </apex:chart>
</apex:page>

关于此示例的注意事项:

  • 折线图和条形图要求您定义 的 X 轴和 Y 轴 图表。
  • 垂直轴在图表的左侧定义,并且 衡量当月完成的商机的美元金额。
  • 水平轴在图表底部定义,并且 表示日历年的月份。
  • 实际的折线图(即组件)绑定到特定轴。<apex:lineSeries>
  • 您可以使用许多标记属性来区分 图表中的每一条线。

添加第二个数据 系列

添加第二个数据序列,其单位为 测量很简单。在这里,“机会关闭-失去” 数据集将添加为第二行系列:

<apex:page controller="ChartController">
    <apex:chart height="400" width="700" data="{!data}">
    	  <apex:axis type="Numeric" position="left" fields="data1,data2" 
            title="Opportunities Closed" grid="true"/>
    	  <apex:axis type="Category" position="bottom" fields="name" 
            title="Month of the Year">
    	  </apex:axis>
    	  <apex:lineSeries axis="left" fill="true" xField="name" yField="data1"
        	  markerType="cross" markerSize="4" markerFill="#FF0000"/>
    	  <apex:lineSeries axis="left" xField="name" yField="data2" 
            markerType="circle" markerSize="4" markerFill="#8E35EF"/>
    </apex:chart>
</apex:page>

需要注意的重要一点是,fields 属性如何将 和 fields 绑定到垂直方向 该组件。这允许图表引擎确定适当的 轴的刻度和刻度线。data1data2<apex:axis>

添加条形图 带第二轴的系列

要添加另一个数据系列,但 根据一组不同的单位绘制图表,您需要添加第二个单位 立轴。以下示例显示了一个数据系列,“收入 按月“添加为条形图:

<apex:page controller="ChartController">
    <apex:chart height="400" width="700" data="{!data}">
    	  <apex:axis type="Numeric" position="left" fields="data1,data2" 
            title="Opportunities Closed" grid="true"/>
        <apex:axis type="Numeric" position="right" fields="data3" 
            title="Revenue (millions)"/>
    	  <apex:axis type="Category" position="bottom" fields="name" 
            title="Month of the Year"/>
    	  <apex:lineSeries axis="left" fill="true" xField="name" yField="data1"
        	  markerType="cross" markerSize="4" markerFill="#FF0000"/>
    	  <apex:lineSeries axis="left" xField="name" yField="data2" 
            markerType="circle" markerSize="4" markerFill="#8E35EF"/>
        <apex:barSeries orientation="vertical" axis="right" 
            xField="name" yField="data3"/>
    </apex:chart>
</apex:page>

请注意以下几点:

  • 要添加具有新度量单位的数据系列,您需要添加 图表右侧的第二个垂直轴。
  • 您最多可以有四个不同的轴,每个轴对应一个边 图表。
  • 条形图设置为垂直方向,并绑定到 右轴。将水平条形图绑定到上轴或下轴。

添加图例、标签、 和图表提示

您可以提高 通过添加图表图例、系列标签并确保 图表标签是可读的:

<apex:page controller="ChartController">
    <apex:chart height="400" width="700" data="{!data}">
        <apex:legend position="right"/>
        <apex:axis type="Numeric" position="left" fields="data1"
            title="Opportunities Closed" grid="true"/>
        <apex:axis type="Numeric" position="right" fields="data3"
            title="Revenue (millions)"/>
        <apex:axis type="Category" position="bottom" fields="name"
            title="Month of the Year">
            <apex:chartLabel rotate="315"/>
        </apex:axis>
        <apex:barSeries title="Monthly Sales" orientation="vertical" axis="right"
            xField="name" yField="data3">
            <apex:chartTips height="20" width="120"/>
        </apex:barSeries>
        <apex:lineSeries title="Closed-Won" axis="left" xField="name" yField="data1"
            fill="true" markerType="cross" markerSize="4" markerFill="#FF0000"/>
        <apex:lineSeries title="Closed-Lost" axis="left" xField="name" yField="data2"
            markerType="circle" markerSize="4" markerFill="#8E35EF"/>
    </apex:chart>
</apex:page>

请注意以下有关添加的内容:

  • 数据系列组件的顺序决定了分层 绘制时的图表元素。在前面的示例中,条形图位于前台。在此示例中,条形图 已放置在后台,因为组件在之前 这两个组件。<apex:barSeries><apex:lineSeries>
  • 组件 可以处于以下四个位置中的任何一个:左、右、上或下。这 图例放置在图表的边界内;在此示例中 图例压缩了图表本身的水平宽度。<apex:legend>
  • 使用数据系列组件属性添加图例标题。title
  • 要旋转底部图表轴的标签,组件是 包含在它所影响的组件中。<apex:chartLabel><apex:axis>
  • 该组件启用滚动更新工具提示,以提供其他信息 关于包含该数据点的系列中的每个数据点。<apex:chartTips>

使用刷新的数据更新图表

使用组件使用新的或更新的数据重新绘制图表, 或者使用 JavaScript 远程处理和您自己的 JavaScript 代码。

<apex:actionSupport>

<apex:actionSupport>允许您仅使用 Visualforce 更新图表。JavaScript 远程处理需要您编写一些 JavaScript 代码,但提供了更多 灵活性和更平滑的过渡。

  • 使用 <apex:actionSupport> 刷新图表数据 通过将组件添加到影响图表数据的 Visualforce 用户界面元素中,
    更新该图表以响应用户的操作。<apex:actionSupport>
  • 使用 JavaScript Remoting
    刷新图表数据 使用自定义 JavaScript 定期更新 Visualforce 图表,或响应用户的操作。JavaScript 代码可以响应复杂的用户活动或计时器事件,并在需要时使用 JavaScript 远程处理来检索新的图表数据。

使用 <apex:actionSupport 刷新图表数据>

更新 Visualforce 图表 通过将组件添加到 Visualforce 用户界面元素来响应用户的操作 这会影响图表的数据。

<apex:actionSupport>以下标记显示一个饼图,可以通过以下方式进行更新 从图表旁边的菜单中选择新年:

<apex:page controller="PieChartRemoteController">
    <apex:pageBlock title="Charts">

        <apex:pageBlockSection title="Standard Visualforce Charting">

            <apex:outputPanel id="theChart">
            <apex:chart height="350" width="450" data="{!pieData}">
                <apex:pieSeries dataField="data" labelField="name"/>
                <apex:legend position="right"/>
            </apex:chart>
            </apex:outputPanel>
            
            <apex:form>
                <apex:selectList value="{!chartYear}" size="1">
                    <apex:selectOptions value="{!chartYearOptions}"/>
                    <apex:actionSupport event="onchange" reRender="theChart" 
                        status="actionStatusDisplay"/>
                </apex:selectList>
                <apex:actionStatus id="actionStatusDisplay"
                    startText="loading..." stopText=""/>
            </apex:form>
            
        </apex:pageBlockSection>
        
    </apex:pageBlock>
</apex:page>

此标记通过将图表的属性设置为 Visualforce 表达式,将图表组件附加到其数据源。该表达式调用控制器方法,该方法返回 数据。该图表包装在属性为 的 中。data{!pieData}getPieData()<apex:outputPanel>idtheChart

组件用于提交新年份 当图表需要更新时,返回到页面的控制器。标签显示年份 可用于图表,并且每当菜单更改时,子标记都会提交表单。 图表的 , , 在属性中使用,以限制更新到 图表,而不是重新加载整个页面。最后,组件在图表时提供状态消息 令人耳目一新。很容易将最小的文本消息替换为动画图形或 文本效果。<apex:form><apex:selectList><apex:actionSupport>id<apex:outputPanel>theChart<apex:actionSupport>reRender<apex:actionStatus>

PieChartRemote控制器

此页面的控制器是饼图的扩展 控制器在一个简单的图表示例中使用。

public class PieChartRemoteController {

    // The year to be charted
    public String chartYear { 
        get {
            if (chartYear == Null) chartYear = '2013';
            return chartYear;
        }
        set;
    }
    
    // Years available to be charted, for <apex:selectList>
    public static List<SelectOption> getChartYearOptions() {
        List<SelectOption> years = new List<SelectOption>();
        years.add(new SelectOption('2013','2013'));
        years.add(new SelectOption('2012','2012'));
        years.add(new SelectOption('2011','2011'));
        years.add(new SelectOption('2010','2010'));
        return years;
    }
    
    public List<PieWedgeData> getPieData() {
        // Visualforce expressions can't pass parameters, so get from property
        return PieChartRemoteController.generatePieData(this.chartYear);
    }
    
    @RemoteAction
    public static List<PieWedgeData> getRemotePieData(String year) {
        // Remoting calls can send parameters with the call
        return PieChartRemoteController.generatePieData(year);
    }

    // Private data "generator"
    private static List<PieWedgeData> generatePieData(String year) {
        List<PieWedgeData> data = new List<PieWedgeData>();
        if(year.equals('2013')) {
            // These numbers are absolute quantities, not percentages
            // The chart component will calculate the percentages
            data.add(new PieWedgeData('Jan', 30));
            data.add(new PieWedgeData('Feb', 15));
            data.add(new PieWedgeData('Mar', 10));
            data.add(new PieWedgeData('Apr', 20));
            data.add(new PieWedgeData('May', 20));
            data.add(new PieWedgeData('Jun',  5));
        }
        else {
            data.add(new PieWedgeData('Jan', 20));
            data.add(new PieWedgeData('Feb', 35));
            data.add(new PieWedgeData('Mar', 30));
            data.add(new PieWedgeData('Apr', 40));
            data.add(new PieWedgeData('May',  5));
            data.add(new PieWedgeData('Jun', 10));
        }
        return data;
    }

    // Wrapper class
    public class PieWedgeData {

        public String name { get; set; }
        public Integer data { get; set; }

        public PieWedgeData(String name, Integer data) {
            this.name = name;
            this.data = data;
        }
    }
}

此控制器支持以两种不同的方式向 Visualforce 图表提供数据:

  • 使用 Visualforce 表达式、 、 调用实例方法。{!pieData}getPieData()
  • 使用 JavaScript 远程处理,从 JavaScript 方法调用静态方法。@RemoteActiongetRemotePieData()

使用 JavaScript Remoting 刷新图表数据

定期更新 Visualforce 图表,或使用 自定义 JavaScript。JavaScript 代码可以响应复杂的用户活动 或计时器事件,并在需要时使用 JavaScript 远程处理检索新的图表数据。以下标记显示一个饼图,可以通过以下方式进行更新 从图表旁边的菜单中选择新年:

<apex:page controller="PieChartRemoteController">
    <script>
    function retrieveChartData(callback) {
       var year = document.getElementById('theYear').value;
       Visualforce.remoting.Manager.invokeAction(
           '{!$RemoteAction.PieChartRemoteController.getRemotePieData}',
           year,
           function(result, event) {
               if(event.status && result && (result.constructor === Array)) {
                   callback(result);
                   RemotingPieChart.show();
               }
               else if (event.type === 'exception') {
                   document.getElementById("remoteResponseErrors").innerHTML = event.message + 
                       '<br/>' + event.where;
               }
               else {
                   document.getElementById("remoteResponseErrors").innerHTML = event.message;
               }                   
           },
           { escape: true }
       );
    }
    function refreshRemoteChart() {
        var statusElement = document.getElementById('statusDisplay');
        statusElement.innerHTML = "loading...";
        retrieveChartData(function(statusElement){
                return function(data){
                    RemotingPieChart.reload(data);
                    statusElement.innerHTML = '';
                };
            }(statusElement)
        );
    }
    </script>

    <apex:pageBlock title="Charts">

        <apex:pageBlockSection title="Visualforce Charting + JavaScript Remoting">

            <apex:chart height="350" width="450" data="retrieveChartData" 
                name="RemotingPieChart" hidden="true">
                <apex:pieSeries dataField="data" labelField="name"/>
                <apex:legend position="right"/>
            </apex:chart>
            
            <div>
                <select id="theYear" onChange="refreshRemoteChart();">
                    <option value="2013">2013</option>
                    <option value="2012">2012</option>
                    <option value="2011">2011</option>
                    <option value="2010">2010</option>
                </select>
                <span id="statusDisplay"></span>
                <span id="remoteResponseErrors"></span>
            </div>

        </apex:pageBlockSection>

    </apex:pageBlock>
</apex:page>

此标记通过设置 图表的属性 更改为返回数据的 JavaScript 函数的名称。的名称 函数以字符串形式提供。dataretrieveChartData

静态 HTML 菜单 显示可用于图表的年份。菜单未关联 具有任何类型的表单元素,并且从不提交其值 直接返回控制器。相反,每当菜单发生更改时,菜单的属性都会调用 JavaScript 函数 。有 两个额外的静态 HTML 元素:两个带有 ID 的标签。当页面加载时,标记为空,并会更新 通过 JavaScript 在必要时显示状态和错误消息。<select><select>onChangerefreshRemoteChart()<span><span>Visualforce 标记前面的两个 JavaScript 函数是 Visualforce 图表和提供数据的控制器方法之间的粘合剂。有三个环节 在函数和图表组件之间:

@RemoteAction

  1. 图表组件的属性设置为“retrieveChartData”,即名称 的第一个 JavaScript 函数。这会告诉图表组件 使用 JavaScript 函数加载其数据。图表组件 仅直接调用一次,当首次创建图表时,并且 最初加载数据。dataretrieveChartData()
  2. 当调用第二个 JavaScript 函数 时,将发生重新加载。 这是菜单中的第二个链接。当年份菜单更改时,将调用该菜单,并重新调用函数以加载 一组新数据。refreshRemoteChart()theYearrefreshRemoteChart()retrieveChartData()
  3. 调用时,它提供了一个匿名函数作为回调,该函数处理 返回时调用的结果。此回调通过调用 来更新图表。这 chart 本身是通过设置属性来命名的,并且是 Visualforce 图表创建后可用的 JavaScript 函数,它接受新数据,然后重新绘制 图表。refreshRemoteChart()retrieveChartData()@RemoteActionRemotingPieChart.reload(data)RemotingPieChartnamereload()

此图说明了不同组件之间的这些链接 的页面:

图表初始加载的顺序很简单:命名调用以获取其初始 data,并在有数据时调用。然后,图表随即出现。<apex:chart>RemotePieChartretrieveChartData()retrieveChartData()RemotePieChart.show()

更新更为复杂。从菜单中选择新年份时,将触发菜单的事件,该事件将调用该函数。 依次调用 函数, 当返回新数据时,(通过 提供的回调 ) 调用 。而且,图表会更新。theYearonChangerefreshRemoteChart()refreshRemoteChart()retrieveChartData()@RemoteActionretrieveChartData()refreshRemoteChart()RemotePieChart.reload()

以下是其他一些需要注意的事项:

  • 用途 属性 要防止图表在有数据之前显示,请 显示。加载图表数据后,该函数将调用以显示图表。这和提供 对于比使用 实现的图表动画更平滑。<apex:chart>hidden=”true”retrieveChartData()RemotingPieChart.show()RemotingPieChart.reload()<apex:actionSupport>
  • 该函数将 HTML 设置为“loading…” 消息,然后通过调用 更新数据,然后 匿名回调函数将其设置为空字符串以隐藏 返回数据并更新图表后的消息。它 比使用多一点工作,效果基本相同。您可以轻松显示“忙碌” 使用相同技术的动画或图形。refreshRemoteData()statusElement<span>retrieveChartData()<apex:actionStatus>

PieChartRemote控制器

此页面的控制器是饼图的扩展 控制器在一个简单的图表示例中使用。

public class PieChartRemoteController {

    // The year to be charted
    public String chartYear { 
        get {
            if (chartYear == Null) chartYear = '2013';
            return chartYear;
        }
        set;
    }
    
    // Years available to be charted, for <apex:selectList>
    public static List<SelectOption> getChartYearOptions() {
        List<SelectOption> years = new List<SelectOption>();
        years.add(new SelectOption('2013','2013'));
        years.add(new SelectOption('2012','2012'));
        years.add(new SelectOption('2011','2011'));
        years.add(new SelectOption('2010','2010'));
        return years;
    }
    
    public List<PieWedgeData> getPieData() {
        // Visualforce expressions can't pass parameters, so get from property
        return PieChartRemoteController.generatePieData(this.chartYear);
    }
    
    @RemoteAction
    public static List<PieWedgeData> getRemotePieData(String year) {
        // Remoting calls can send parameters with the call
        return PieChartRemoteController.generatePieData(year);
    }

    // Private data "generator"
    private static List<PieWedgeData> generatePieData(String year) {
        List<PieWedgeData> data = new List<PieWedgeData>();
        if(year.equals('2013')) {
            // These numbers are absolute quantities, not percentages
            // The chart component will calculate the percentages
            data.add(new PieWedgeData('Jan', 30));
            data.add(new PieWedgeData('Feb', 15));
            data.add(new PieWedgeData('Mar', 10));
            data.add(new PieWedgeData('Apr', 20));
            data.add(new PieWedgeData('May', 20));
            data.add(new PieWedgeData('Jun',  5));
        }
        else {
            data.add(new PieWedgeData('Jan', 20));
            data.add(new PieWedgeData('Feb', 35));
            data.add(new PieWedgeData('Mar', 30));
            data.add(new PieWedgeData('Apr', 40));
            data.add(new PieWedgeData('May',  5));
            data.add(new PieWedgeData('Jun', 10));
        }
        return data;
    }

    // Wrapper class
    public class PieWedgeData {

        public String name { get; set; }
        public Integer data { get; set; }

        public PieWedgeData(String name, Integer data) {
            this.name = name;
            this.data = data;
        }
    }
}

此控制器支持以两种不同的方式向 Visualforce 图表提供数据:

  • 使用 Visualforce 表达式、 、 调用实例方法。{!pieData}getPieData()
  • 使用 JavaScript 远程处理,从 JavaScript 方法调用静态方法。@RemoteActiongetRemotePieData()

控制图表的外观

Visualforce 图表是高度可定制的。您可以组合各种类型的 数据系列,控制图表中大多数元素的颜色,并控制 标记、线条等的外观。您可以自定义以下内容:

  • 数据系列元素的线条和填充颜色。
  • 填充颜色和线条的不透明度。
  • 数据点的标记形状和颜色。
  • 连接线的线宽。
  • 突出显示数据元素。
  • 轴的刻度线和网格线样式。
  • 图例、标签和“工具提示”样式的变换注释。

提供此控件的许多组件和属性 在标准组件参考中进行了说明。某些效果需要属性和 组件,并在本文档中进行了更完整的解释。

图表颜色

默认情况下,图表颜色与内置报告的颜色相匹配 和分析图表,以便您可以创建视觉上一致的仪表板。 如果要创建自己的配色方案,可以自定义 大多数图表元素的颜色。

提供一组颜色定义来绘制数据系列元素 (条形图、饼图楔形等),请使用该属性。设置为指定要用于每个颜色的颜色 图表中的数据系列。在数据系列组件上设置以仅指定该系列的颜色。colorSet<apex:chart colorSet=”…”>colorSet

A 是一个字符串 这是以逗号分隔的 HTML 样式十六进制颜色定义的列表。 例如。颜色按顺序使用。当到达列表的末尾时, 序列从头开始。colorSetcolorSet=”#0A224E,#BF381A,#A0D8F1,#E9AF32,#E07628″下面是一个饼图,它使用 馅饼楔形颜色:

<apex:pageBlockSection title="Simple colorSet Demo">
    <apex:chart data="{!pieData}" height="300" width="400" background="#F5F5F5">
        <apex:legend position="left"/>
        <apex:pieSeries labelField="name" dataField="data1"
            colorSet="#37241E,#94B3C8,#4D4E24,#BD8025,#816A4A,#F0E68C"/>
    </apex:chart>
</apex:pageBlockSection>

使用该属性可设置整个图表的背景色。

background

您可以将 除 .其他详细信息 以及用于配置其他图表元素颜色的更多选项 针对特定数据系列组件进行了描述。colorSet<apex:radarSeries>colorSet

图表布局和注释

为了使您的图表更易于理解,请添加一个有意义的图例 轴范围和标签,以及数据元素上的提示或标签。

默认情况下,所有图表都有一个图例。要隐含默认图例, 设置。控制图例的位置和图例的间距 条目,将组件添加到图表中。将图例放在四个边中的任何一个边上 使用该属性的图表。使用属性 控制图例中使用的文本样式。该属性是一个字符串,指定 CSS 样式的速记字体属性。例如。<apex:chart legend=”false”><apex:legend>positionfontfont<apex:legend position=”left” font=”bold 24px Helvetica”/>

适当的轴缩放和标记可能意味着 难以辨认或具有误导性的图表,以及清晰且清晰的图表 有说服力的。默认情况下,组件会根据 在属性中设置的数据字段上。自动缩放确保所有数据都适合图表 但图表可能不会以有意义的数字开头或结尾。用 和属性来覆盖 自动缩放。若要设置刻度线的间隔,请使用该属性。此属性是 一个整数,指定两端之间的步数 的轴。使用 、 和 属性添加行或 图表的阴影,以便更轻松地将测量值与 规模。<apex:axis type=”Numeric”>fieldsminimummaximumstepsdashSizegridgridFill您可以将图表标签应用于坐标区和数据序列。当 是 的子项时,将绘制标签 在轴的外侧。当是数据系列组件的子组件时,标签将绘制在 或 靠近图表上的数据元素。使用属性设置 标签。使用该属性设置标签的绘制位置。使用 和 属性调整文本 的标签,使其适合图表。

<apex:chartLabel><apex:axis><apex:chartLabel>fielddisplayorientationrotate

注意

该属性不起作用 当组件与组件一起使用时。orientation<apex:chartLabel><apex:pieSeries>此示例图表使用了其中许多组件和属性 要创建有意义的视觉设计,请执行以下操作:

<apex:chart data="{!data}" height="400" width="500">
    <apex:legend position="left" font="bold 14px Helvetica"/>
    <apex:axis type="Numeric" position="left" title="Closed Won" grid="true"
        fields="data1,data2,data3" minimum="0" maximum="225" steps="8" dashSize="2">
        <apex:chartLabel />
    </apex:axis>
    <apex:axis type="Category" position="bottom" fields="name" title="2012">
        <apex:chartLabel rotate="315"/>
    </apex:axis>
    <apex:barSeries orientation="vertical" axis="left" 
        xField="name" yField="data1,data2,data3" stacked="true"/>
</apex:chart>

条形图

条形图是 Visualforce 中提供的几种线性数据系列图表之一。 线性系列图是针对标准矩形网格绘制的图表。

线性序列中的每个数据元素都由坐标描述。数据序列定义如何在网格上绘制坐标。图表绘制拉伸的条形 在原点轴和坐标之间。该属性确定原点是否 axis 是左轴 (Y) 或底轴 (X)。为源自 图表的左侧,对于柱形图,柱形从 图表底部。X,Y<apex:barSeries>X,Yorientation<apex:barSeries orientation=”horizontal”><apex:barSeries orientation=”vertical”>要为每个柱线间隔绘制多个数据点,请将柱线分组或堆叠在 单个标记。单个图表中的多个标签绘制 彼此叠加,遮挡了除最后一个数据系列之外的所有数据。创建垂直列 图表中,将所有要分组或堆叠的字段添加到属性中:

<apex:barSeries><apex:barSeries>yField

<apex:barSeries orientation="vertical" axis="left" 
    xField="name" yField="data1,data2,data3"/>

由 默认情况下,数据字段中的数据字段在图表上分组。要将它们堆叠在一起,请设置 .

<apex:barSeries>stacked=”true”

使用属性调整间距 在分组的柱线之间。使用该属性可调整组之间的间距。使用 和 属性 调整图表轴与条形本身之间的间距。guttergroupGutterxPaddingyPadding默认情况下,堆积条形图或分组条形图的图例标题使用 属性。在前面的示例中, 默认标题为“data1”、“data2”和 “数据 3”。若要为图例提供更有意义的标题,请使用组件的属性。使用逗号 单独的项目。例如:

yFieldtitle<apex:barSeries>title=”MacDonald,Promas,Worle”

<apex:chart data="{!data}" height="400" width="500">
    <apex:legend position="left"/>
    <apex:axis type="Numeric" position="left" title="Closed Won" grid="true"
        fields="data1,data2,data3" dashSize="2">
        <apex:chartLabel/>
    </apex:axis>
    <apex:axis type="Category" position="bottom" fields="name" title="Stacked Bars">
        <apex:chartLabel rotate="315"/>
    </apex:axis>
    <apex:barSeries orientation="vertical" axis="left" stacked="true"
        xField="name" yField="data1,data2,data3" title="MacDonald,Promas,Worle"/>
</apex:chart>

其他线性系列图

其他线性数据序列图表包括 、 和 。

<apex:areaSeries><apex:lineSeries><apex:scatterSeries>您可以将线性数据系列图表组合在同一图形上,但 要创建有意义的图表,请记住以下几点:

  • 数据系列图表按 在 Visualforce 标记中定义它们。
  • 首先定义图表,因为它们通常需要在后台进行,因为 它们不能是透明的。<apex:barSeries>

这些组件类似于堆积条形图,不同之处在于图表 绘制为阴影区域,由连接点的线定义 系列而不是作为单独的条形。与其他数据合并 series,请使用属性 使面积图部分透明。这

<apex:areaSeries><apex:areaSeries>opacityopacityattribute 是介于 0.0 和 1.0 之间的浮点数,其中 0.0 是完全透明的,1.0是完全不透明的。这是 区域系列与酒吧系列相结合:

<apex:chart height="400" width="700" animate="true" data="{!data}">
    <apex:legend position="left"/>
    <apex:axis type="Numeric" position="left" title="Closed Won" grid="true"
        fields="data1,data2,data3">
        <apex:chartLabel />
    </apex:axis>
    <apex:axis type="Numeric" position="right" fields="data1" 
        title="Closed Lost" />
    <apex:axis type="Category" position="bottom" fields="name" 
        title="Month of the Year">
        <apex:chartLabel rotate="315"/>
    </apex:axis>
    <apex:areaSeries axis="left" tips="true" opacity="0.4" 
        xField="name" yField="data1,data2,data3"/>
    <apex:barSeries orientation="vertical" axis="right" 
        xField="name" yField="data1">
        <apex:chartLabel display="insideEnd" field="data1" color="#333"/>
    </apex:barSeries>
</apex:chart>

默认情况下,面积图的图例标题使用字段名称 在属性中。在 在前面的示例中,默认标题为“data1”, “data2”和“data3”。给传说 更有意义的标题,请使用组件的属性。使用逗号分隔项目。例如:

yFieldtitle<apex:areaSeries>title=”MacDonald,Promas,Worle”

<apex:chart height="400" width="700" animate="true" data="{!data}">
    <apex:legend position="left"/>
    <apex:axis type="Numeric" position="left" fields="data1,data2,data3" 
        title="Closed Won" grid="true">
        <apex:chartLabel />
    </apex:axis>
    <apex:axis type="Category" position="bottom" fields="name" title="2011">
        <apex:chartLabel rotate="315"/>
    </apex:axis>
    <apex:areaSeries axis="left" xField="name" tips="true" 
        yField="data1,data2,data3" title="MacDonald,Picard,Worlex"  />
</apex:chart>

与图表一样,图表使用线条连接一系列点。您可以填写 线下的区域。与图表不同,图表不会堆叠。当图表未填充时,您可以选择放置多个序列 在同一图表中。线系列可以显示数据点的标记 您可以定义标记的颜色和大小,以及 连接线。这是一张结合了三个线系列的图表, 其中之一已填写:

<apex:areaSeries><apex:lineSeries><apex:areaSeries><apex:lineSeries><apex:lineSeries>

<apex:chart height="400" width="700" animate="true" legend="true" data="{!data}">
    <apex:legend position="left"/>
    <apex:axis type="Numeric" position="left" title="Volatility" grid="true"
        fields="data1,data2,data3">
        <apex:chartLabel />
    </apex:axis>
    <apex:axis type="Category" position="bottom" title="Month" grid="true"
        fields="name">
        <apex:chartLabel />
    </apex:axis>
    <apex:lineSeries axis="left" xField="name" yField="data1" 
        strokeColor="#0000FF" strokeWidth="4"/>
    <apex:lineSeries axis="left" fill="true" xField="name" yField="data2"
        markerType="cross" markerSize="4" markerFill="#FF0000"/>
    <apex:lineSeries axis="left" xField="name" yField="data3" 
        markerType="circle" markerSize="4" markerFill="#8E35EF">
        <apex:chartTips height="20" width="120"/>
    </apex:lineSeries>
</apex:chart>

注意

如果数值轴未填充,则组件可能无法按预期填充 随着它向上和向右移动,按顺序增加。解决方案是 在将数据传递到图表之前,手动设置轴并对值进行排序。<apex:lineSeries>type=”Category”

图表就像没有连接线的图表。通过改变标记大小, 类型和颜色,很容易在 相同的图表。<apex:scatterSeries><apex:lineSeries>

饼图

对图表最常见的自定义是颜色 和标签。使用前面示例中演示的属性和组件。

<apex:pieSeries>colorSet<apex:chartLabel>若要创建环形图而不是饼图,请设置属性。该属性是介于 0 和 100 表示孔半径的百分比。 下面是一个简单的环形图:

donutdonut

<apex:chart data="{!pieData}" height="400" width="500" background="#F5F5F5">
    <apex:legend position="left"/>
    <apex:pieSeries labelField="name" dataField="data1" donut="50">
        <apex:chartLabel display="middle" orientation="vertical" 
            font="bold 18px Helvetica"/>
    </apex:pieSeries>
</apex:chart>

仪表图

仪表图显示针对定义的单个测量值 轴或刻度。虽然它绘制了一个数字,但你可以改变 轴和图表颜色来传达该数字的含义。使用标记的 和 属性定义范围 的价值观。使用标记的属性来指示当前值是好值还是坏值。这是 指示指标完全在可接受范围内的图表:

minimummaximum<apex:axis>colorSet<apex:gaugeSeries>

<apex:chart height="250" width="450" animate="true" data="{!data}">
    <apex:axis type="Gauge" position="gauge" title="Transaction Load"
        minimum="0" maximum="100" steps="10"/>
    <apex:gaugeSeries dataField="data1" donut="50" colorSet="#78c953,#ddd"/>
</apex:chart>

注意

仪表图不支持图例或标注。

雷达图

雷达图类似于折线图,但它们使用圆形 轴而不是线性网格。

使用 、 和 属性设置样式, 标记的大小和颜色。使用 and 属性设置连接线的颜色和粗细。 (可选)设置为 填充该系列所包围的区域,并使其透明 其他系列仍然可见。该属性是介于 0.0 和 1.0 之间的浮点数,其中 0.0 是完全透明的,1.0是完全不透明的。markerTypemarkerSizemarkerFillstrokeColorstrokeWidthfill=trueopacityopacity下面是一个雷达图的示例,以及 创建它:

<apex:chart height="530" width="700" legend="true" data="{!data}">
    <apex:legend position="left"/>
    <apex:axis type="Radial" position="radial">
        <apex:chartLabel />
    </apex:axis>
    <apex:radarSeries xField="name" yField="data1" tips="true" opacity="0.4"/>
    <apex:radarSeries xField="name" yField="data2" tips="true" opacity="0.4"/>
    <apex:radarSeries xField="name" yField="data3" tips="true" 
        markerType="cross" strokeWidth="2" strokeColor="#f33" opacity="0.4"/>
 </apex:chart>

ref

将电子邮件与 Visualforce 集成

Visualforce 可用于向您的任何联系人、潜在客户或 其他收件人。也可以创建可重复使用的电子邮件模板 利用 Visualforce 迭代您的 Salesforce 记录的能力。 以下主题说明如何操作:

  • 使用 Visualforce 发送电子邮件
  • Visualforce 电子邮件模板

使用 Visualforce 发送电子邮件

可以使用 Visualforce 发送电子邮件,方法是创建自定义控制器以 传递消息。Apex 类处理可用的出站电子邮件功能 到 Salesforce。Messaging.SingleEmailMessage以下主题演示了通过 Visualforce 发送电子邮件时可用的许多功能:

  • 使用 Messaging 类创建自定义控制器
  • 创建电子邮件附件

使用 Messaging 类创建自定义控制器

使用 Apex 命名空间的自定义控制器至少需要 电子邮件。您将需要一个页面作为表单来填写主题和正文,并且 发送电子邮件。Messaging创建一个新页面,称为并使用 以下 法典:

sendEmailPage

<apex:page controller="sendEmail">
	<apex:messages />
	<apex:pageBlock title="Send an Email to Your 
			{!account.name} Representatives">
		<p>Fill out the fields below to test how you might send an email to a user.</p>
		<br />
		<apex:dataTable value="{!account.Contacts}" var="contact" border="1">
			<apex:column >
				<apex:facet name="header">Name</apex:facet>
				{!contact.Name}
			</apex:column>
			<apex:column >
				<apex:facet name="header">Email</apex:facet>
				{!contact.Email}
			</apex:column>
		</apex:dataTable>
    
		<apex:form >
		<br /><br />
			<apex:outputLabel value="Subject" for="Subject"/>:<br />     
			<apex:inputText value="{!subject}" id="Subject" maxlength="80"/>
			<br /><br />
			<apex:outputLabel value="Body" for="Body"/>:<br />     
			<apex:inputTextarea value="{!body}" id="Body"  rows="10" cols="80"/>           
			<br /><br /><br />
			<apex:commandButton value="Send Email" action="{!send}" /> 
		</apex:form>
	</apex:pageBlock>
</apex:page>

请注意,在页面标记中,帐户 ID 是从页面的 URL 中检索的。为 此示例要正确呈现,您必须将 Visualforce 页面与有效的 URL 中的帐户记录。例如,如果是帐户 ID,则生成的 URL 应 是:

001D000000IRt53

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

使用 Visualforce 显示字段值提供了有关检索记录 ID 的更多信息。下面的代码创建一个名为实现该类的控制器,并使用与 帐户作为 收件人:

sendEmailMessaging.SingleEmailMessage

public class sendEmail {
	public String subject { get; set; }
	public String body { get; set; }

	private final Account account;

	// Create a constructor that populates the Account object
	public sendEmail() {
		account = [select Name, (SELECT Contact.Name, Contact.Email FROM Account.Contacts) 
				from Account where id = :ApexPages.currentPage().getParameters().get('id')];
	}

	public Account getAccount() {
		return account;
	}

	public PageReference send() {
		// Define the email
		Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); 

    String addresses;
    if (account.Contacts[0].Email != null)
    {
        addresses = account.Contacts[0].Email;
        // Loop through the whole list of contacts and their emails
        for (Integer i = 1; i < account.Contacts.size(); i++) 
        {
            if (account.Contacts[i].Email != null)
            {
                addresses += ':' + account.Contacts[i].Email;
            }
        }
    }

		String[] toAddresses = addresses.split(':', 0);

		// Sets the paramaters of the email
		email.setSubject( subject );
		email.setToAddresses( toAddresses );
		email.setPlainTextBody( body );
    
		// Sends the email
		Messaging.SendEmailResult [] r = 
			Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});   
		
		return null;
	}
}

请注意,在控制器中:

  • 电子邮件的主题和正文通过单独的 Visualforce 页面进行设置,并且 传递到控制器中。
  • 发送电子邮件的方法称为 。此名称必须与 Visualforce 按钮的操作名称匹配 发送电子邮件。send()
  • 电子邮件的收件人,即存储在 中的电子邮件地址,来自 关联帐户中可用的联系人。编制 来自联系人、潜在顾客或其他记录的收件人,最好循环 通过所有记录来验证是否为每个记录定义了一个电子邮件地址。这 帐户 ID 是从页面的 URL 中检索的。toAddresses[]

sendEmailPage 上的表单示例

创建电子邮件附件

如果要在电子邮件中添加附件,只需添加几行 代码添加到自定义控制器。电子邮件附件是文件类型。若要创建附件,需要使用 Apex 类。您必须定义 对象的文件名和内容。BlobMessaging.EmailFileAttachmentEmailFileAttachment

添加 PDF 附件

以下示例演示了如何将 Visualforce 页面转换为呈现为 将 PDF 转换为电子邮件附件。首先,创建一个名为:

PageReferenceattachmentPDF

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

  <h1>Account Details</h1>
  
  <apex:panelGrid columns="2">

      <apex:outputLabel for="Name" value="Name"/>
      <apex:outputText id="Name" value="{!account.Name}"/>
      
      <apex:outputLabel for="Owner" value="Account Owner"/>
      <apex:outputText id="Owner" value="{!account.Owner.Name}"/>
      
      <apex:outputLabel for="AnnualRevenue" value="Annual Revenue"/>
      <apex:outputText id="AnnualRevenue" value="{0,number,currency}">
          <apex:param value="{!account.AnnualRevenue}"/>
      </apex:outputText>
      
      <apex:outputLabel for="NumberOfEmployees" value="Employees"/>
      <apex:outputText id="NumberOfEmployees" value="{!account.NumberOfEmployees}"/>
      
  </apex:panelGrid>

</apex:page>

注意

请参阅渲染 PDF 文件的最佳实践,了解建议在 PDF 中使用哪些组件的详细信息 附件。接下来,创建对象 在您的自定义方法中 控制器。在调用之前必须放置以下示例:

EmailFileAttachmentsend()Messaging.sendEmail

// Reference the attachment page, pass in the account ID
    PageReference pdf = Page.attachmentPDF;
    pdf.getParameters().put('id',(String)account.id);
    pdf.setRedirect(true);

    // Take the PDF content
    Blob b = pdf.getContent();

    // Create the email attachment
    Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
    efa.setFileName('attachment.pdf');
    efa.setBody(b);

如果 您的对象已命名,然后您关联附件 喜欢 这:

SingleEmailMessageemail

email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa});

将自定义组件定义为 附件

通过创建自定义组件并在 Visualforce 电子邮件表单上使用它,然后 呈现电子邮件的 PDF,用户可以看到他们正在尝试的内容的预览 发送。以下标记定义了一个名为 电子邮件:

attachment

<apex:component access="global">
  <h1>Account Details</h1>
  
  <apex:panelGrid columns="2">

      <apex:outputLabel for="Name" value="Name"/>
      <apex:outputText id="Name" value="{!account.Name}"/>
      
      <apex:outputLabel for="Owner" value="Account Owner"/>
      <apex:outputText id="Owner" value="{!account.Owner.Name}"/>
      
      <apex:outputLabel for="AnnualRevenue" value="Annual Revenue"/>
      <apex:outputText id="AnnualRevenue" value="{0,number,currency}">
          <apex:param value="{!account.AnnualRevenue}"/>
      </apex:outputText>
      
      <apex:outputLabel for="NumberOfEmployees" value="Employees"/>
      <apex:outputText id="NumberOfEmployees" value="{!account.NumberOfEmployees}"/>
      
  </apex:panelGrid>
</apex:component>

替换您的页面,例如 这:

attachmentPDF

<apex:page standardController="account" renderAs="PDF">
    <c:attachment/>
</apex:page>

然后添加自定义组件,渲染在你之前的底部:

sendEmailPage

<apex:pageBlock title="Preview the Attachment for {!account.name}">
    <c:attachment/>
</apex:pageBlock>

如果要同时更改附件和预览,则需要修改自定义组件 仅在一个位置。attachment

示例:发送带有 附件

以下示例显示了前面的 sendEmail 示例,其中包含一个自定义组件,该组件将 Visualforce 页面添加为 附件。首先, 控制器:

public class sendEmail {
    public String subject { get; set; }
    public String body { get; set; }

    private final Account account;

    // Create a constructor that populates the Account object
    public sendEmail() {
        account = [SELECT Name, 
                  (SELECT Contact.Name, Contact.Email FROM Account.Contacts) 
                   FROM Account 
                   WHERE Id = :ApexPages.currentPage().getParameters().get('id')];
    }

    public Account getAccount() {
        return account;
    }

    public PageReference send() {
        // Define the email
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); 

        // Reference the attachment page and pass in the account ID
        PageReference pdf =  Page.attachmentPDF;
        pdf.getParameters().put('id',(String)account.id); 
        pdf.setRedirect(true);

        // Take the PDF content
        Blob b = pdf.getContent();

        // Create the email attachment
        Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
        efa.setFileName('attachment.pdf');
        efa.setBody(b);

        String addresses;
        if (account.Contacts[0].Email != null) {
            addresses = account.Contacts[0].Email;
            // Loop through the whole list of contacts and their emails
            for (Integer i = 1; i < account.Contacts.size(); i++) {
                if (account.Contacts[i].Email != null) {
                    addresses += ':' + account.Contacts[i].Email;
                }
            }
        }

        String[] toAddresses = addresses.split(':', 0);

        // Sets the paramaters of the email
        email.setSubject( subject );
        email.setToAddresses( toAddresses );
        email.setPlainTextBody( body );

        email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa});

        // Sends the email
        Messaging.SendEmailResult [] r = 
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});   
		
        return null;
    }
}

接下来,Visualforce 页面 发送 电子邮件:

<apex:page controller="sendEmail">
    <apex:messages/>
    <apex:pageBlock title="Send an Email to Your {!account.name} Representatives">
        <p>Fill out the fields below to test how you might send an email to a user.</p>

        <apex:dataTable value="{!account.Contacts}" var="contact" border="1">
            <apex:column>
                <apex:facet name="header">Name</apex:facet>
                {!contact.Name}
            </apex:column>
            <apex:column>
                <apex:facet name="header">Email</apex:facet>
                {!contact.Email}
            </apex:column>
        </apex:dataTable>
    
        <apex:form><br/><br/>
            <apex:outputLabel value="Subject" for="Subject"/>: <br/>     
            <apex:inputText value="{!subject}" id="Subject" maxlength="80"/>
            <br/><br/>

            <apex:outputLabel value="Body" for="Body"/>: <br/>     
            <apex:inputTextarea value="{!body}" id="Body" rows="10" cols="80"/>           
            <br/><br/>

            <apex:commandButton value="Send Email" action="{!send}"/> 
        </apex:form>
    </apex:pageBlock>

    <apex:pageBlock title="Preview the Attachment for {!account.name}">
        <c:attachment/>
    </apex:pageBlock>
</apex:page>

Visualforce 电子邮件模板

开发人员和管理员可以使用 Visualforce 创建电子邮件模板。与标准 HTML 电子邮件模板相比,使用 Visualforce 的优势在于,Visualforce 使您能够对以下数据执行高级操作: 发送给收件人。尽管 Visualforce 电子邮件模板使用标准的 Visualforce 组件,但它们的创建方式并不相同。Visualforce 电子邮件模板始终使用以命名空间开头的组件。另外:

messaging

  • 所有 Visualforce 电子邮件模板必须包含在单个标记中。 这类似于在单个标记中定义的常规 Visualforce 页面。<messaging:emailTemplate><apex:page>
  • 标签必须包含单个标签或单个标签。<messaging:emailTemplate><messaging:htmlEmailBody><messaging:plainTextEmailBody>
  • 有几个标准的 Visualforce 组件不能在 中使用。这些 包括 、所有相关组件和所有输入 组件,例如 .如果您尝试使用这些组件保存 Visualforce 电子邮件模板,则会显示一条错误消息。<messaging:emailTemplate><apex:detail><apex:pageBlock>pageBlock<apex:form>

以下主题提供了更多详细信息:

  • 创建 Visualforce 电子邮件模板
  • 在 Visualforce 电子邮件模板中使用自定义样式表
  • 添加附件
  • 在 Visualforce 电子邮件模板中使用自定义控制器

创建 Visualforce 电子邮件模板

  1. 执行下列操作之一:
    • 如果您有权编辑公共模板,请从“设置”中输入“快速查找”框,然后选择“经典电子邮件模板”。Email Templates
    • 如果您无权编辑公共模板,请转到您的个人 设置。输入“快速查找”框,然后选择“电子邮件模板”或“我的” 模板 – 以出现的模板为准。Templates
  2. 单击“新建模板”。
  3. 选择 Visualforce,然后单击下一步。你 无法使用 Visualforce 电子邮件模板发送群发电子邮件。
  4. 选择要在其中存储模板的文件夹。
  5. 若要使模板可供使用,请选择“可用于” 使用复选框。
  6. 在“电子邮件模板名称”中输入名称。
  7. 如有必要,请更改模板唯一名称。这个唯一的名称 指使用 Lightning 平台 API 时的组件。在托管包中,这 唯一名称可防止软件包安装中的命名冲突。此名称只能包含 下划线和字母数字字符,并且在组织中必须是唯一的。它必须开始 对于字母,不包含空格,不以下划线结尾,不包含两个 连续下划线。使用“模板唯一名称”字段,您可以 更改托管包中某些组件的名称,这些更改将反映出来 在订阅者的组织中。
  8. 如果需要,请从“编码”下拉列表中选择其他字符集。
  9. 输入模板的描述。模板名称和描述都适用于你的 仅供内部使用。
  10. 在电子邮件主题中输入模板的主题行。
  11. 在“收件人类型”下拉列表中,选择收件人的类型 接收从模板创建的电子邮件。
  12. 如果需要,在“与类型相关”下拉列表中,选择对象 模板从中检索合并字段数据。
  13. 点击保存
  14. 在“在 Salesforce Classic 中查看和编辑电子邮件模板”页面上,单击“编辑” 模板
  15. 输入 Visualforce 电子邮件模板的标记文本。注意如果您包括 图像,我们建议将其上传到“文档”选项卡以引用图像的副本 在我们的服务器上。例如:<apex:image id="Logo" value="https://yourInstance.salesforce.com/servlet/servlet.ImageServer? id=015D0000000Dpwc&oid=00DD0000000FHaG&lastMod=127057656800" />
  16. 要指定 Visualforce 的版本以及与此电子邮件模板一起使用的 API,请单击版本设置。如果您已从 AppExchange,您还可以指定要与此软件包一起使用的每个托管软件包的版本 电子邮件模板。通常,对所有版本使用默认值来关联电子邮件 包含最新版本的 Visualforce、API 和每个托管包的模板。 要保持特定行为,您可以指定旧版本的 Visualforce,并且 应用程序接口。访问与最新包不同的组件或功能 version,您可以指定托管包的旧版本。
  17. 若要查看模板的详细信息,请单击“保存”。继续 编辑模板时,点击快速保存。您的 Visualforce 标记 必须有效,然后才能保存模板。注意Visualforce 的最大大小 电子邮件模板为 1 MB。您无法使用 Visualforce 电子邮件发送群发电子邮件 模板。{!Receiving_User。field_name}和 {!Sending_User。field_name}合并字段 仅适用于群发电子邮件和列表电子邮件,在 Visualforce 电子邮件中不可用 模板。

以下示例显示了如何定义一个 Visualforce 电子邮件模板,该模板 显示与联系人关联的所有案例。该示例使用标签遍历所有 与联系人相关的案例,并将其纳入 模板:

<apex:repeat>

<messaging:emailTemplate recipientType="Contact"
	relatedToType="Account"
	subject="Case report for Account: {!relatedTo.name}"
	language="{!recipient.language__c}"
	replyTo="support@acme.com">

	<messaging:htmlEmailBody>
		<html>
			<body>

			<p>Dear {!recipient.name},</p>
			<p>Below is a list of cases related to {!relatedTo.name}.</p>
			<table border="0" >
				<tr>
					<th>Case Number</th><th>Origin</th>
					<th>Creator Email</th><th>Status</th>
				</tr>
				<apex:repeat var="cx" value="{!relatedTo.Cases}">
				<tr>
					<td><a href = 
						"https://yourInstance.salesforce.com/{!cx.id}">{!cx.CaseNumber}
					</a></td>
					<td>{!cx.Origin}</td>
					<td>{!cx.Contact.email}</td>
					<td>{!cx.Status}</td>
				</tr>
				</apex:repeat> 
			</table>
			<p/>
			<center>
				<apex:outputLink value="https://salesforce.com">
					For more detailed information login to Salesforce.com
				</apex:outputLink>
			</center>
			</body>
		</html>
	</messaging:htmlEmailBody>
</messaging:emailTemplate>

请注意有关标记的以下信息:

  • 属性和充当 电子邮件模板的控制器。有了它们,您可以访问相同的 合并可供其他标准控制器使用的字段。该属性表示 电子邮件的收件人。该属性表示要与电子邮件关联的记录。recipientTyperelatedToTyperecipientTyperelatedToType
  • 该组件可以包含 Visualforce 标记和 HTML 的组合。该组件只能包含 Visualforce 标记和纯文本。<messaging:htmlEmailBody><messaging:plainTextEmailBody>
  • 翻译基于 Visualforce 电子邮件模板 在收件人或相关对象的语言上,使用标记的属性(有效值:Salesforce 支持的语言键,用于 例如,“en-US”)。language 属性接受来自 电子邮件模板和属性。创建自定义语言 用于合并字段的字段。<messaging:emailTemplate>languagerecipientTyperelatedToType翻译 需要 Workbench 来翻译电子邮件模板。该示例使用合并字段获取联系人的属性 接收电子邮件。language

在 Visualforce 电子邮件模板中使用自定义样式表

默认情况下,Visualforce 电子邮件模板始终使用其他 Salesforce 组件的标准外观。 但是,您可以通过定义 自己的样式表。

与其他 Visualforce 页面不同,Visualforce 电子邮件模板不能使用引用的页面样式或静态资源。尽管 CSS 似乎在 电子邮件模板预览窗格,它对收件人来说看起来不一样 您的电子邮件。您必须在标签中使用 CSS 定义样式。<style>以下示例将电子邮件的字体更改为 Courier,向表格添加边框,然后 更改表格的颜色 行:

<messaging:emailTemplate recipientType="Contact"
	relatedToType="Account"
	subject="Case report for Account: {!relatedTo.name}"
	replyTo="support@acme.com">

	<messaging:htmlEmailBody>
		<html>
			<style type="text/css">
			body {font-family: Courier; size: 12pt;}

			table {
			border-width: 5px;
			border-spacing: 5px;
			border-style: dashed;
			border-color: #FF0000;
			background-color: #FFFFFF;
		}

		td {
			border-width: 1px;
			padding: 4px;
			border-style: solid;
			border-color: #000000;
			background-color: #FFEECC;
		}

		th { 
			color: #000000;
			border-width: 1px ;
			padding: 4px ;
			border-style: solid ;
			border-color: #000000;
			background-color: #FFFFF0;
		}
		</style>
		<body>
			<p>Dear {!recipient.name},</p>
			<table border="0" >
				<tr>
					<th>Case Number</th><th>Origin</th>
					<th>Creator Email</th><th>Status</th>
				</tr>
				<apex:repeat var="cx" value="{!relatedTo.Cases}">
				<tr>
					<td><a href = 
						"https://MyDomainName.my.salesforce.com/{!cx.id}">{!cx.CaseNumber}
					</a></td>
					<td>{!cx.Origin}</td>
					<td>{!cx.Contact.email}</td>
					<td>{!cx.Status}</td>
				</tr>
				</apex:repeat> 
			</table>
		</body>
		</html>
	</messaging:htmlEmailBody>
</messaging:emailTemplate>

呈现的 Visualforce 电子邮件模板示例

在自定义组件中定义 Visualforce 样式表

虽然您无法在 Visualforce 电子邮件模板中引用外部样式表,但您可以将样式定义放在自定义样式中 可以在其他地方引用的组件。例如 您可以修改前面的示例以放置样式信息 在名为 EmailStyle 的组件中:

<apex:component access="global">
		<style type="text/css">
		body {font-family: Courier; size: 12pt;}

		table {
			border-width: 5px;
			border-spacing: 5px;
			border-style: dashed;
			border-color: #FF0000;
			background-color: #FFFFFF;
		}

		td {
			border-width: 1px;
			padding: 4px;
			border-style: solid;
			border-color: #000000;
			background-color: #FFEECC;
		}

		th { 
			color: #000000;
			border-width: 1px ;
			padding: 4px ;
			border-style: solid ;
			border-color: #000000;
			background-color: #FFFFF0;
		}
		</style>
</apex:component>

然后,在 Visualforce 电子邮件模板中,您可以仅引用该组件:

<messaging:htmlEmailBody>
	<html>
	<c:EmailStyle />
		<body>
			<p>Dear {!recipient.name},</p>
		...
		</body>
	</html>
</messaging:htmlEmailBody>

注意

Visualforce 电子邮件模板中使用的任何标签的访问级别都必须为 。<apex:component>global

添加附件

您可以向 Visualforce 电子邮件模板添加附件。每 附件必须封装在单个组件中。其中的代码可以是 HTML 和 Visualforce 标记。<messaging:attachment><messaging:attachment>前面的示例演示了如何通过以下方式创建 Visualforce 电子邮件模板 遍历某些数据并将其显示给电子邮件收件人。此示例显示 如何修改该标记以将数据显示为 附件:

<messaging:emailTemplate recipientType="Contact"
	relatedToType="Account"
	subject="Case report for Account: {!relatedTo.name}"
	replyTo="support@acme.com">

	<messaging:htmlEmailBody>
		<html>
			<body>
			<p>Dear {!recipient.name},</p>
			<p>Attached is a list of cases related to {!relatedTo.name}.</p>
			<center>
				<apex:outputLink value="https://salesforce.com">
					For more detailed information login to Salesforce.com
				</apex:outputLink>
			</center>
			</body>
		</html>
	</messaging:htmlEmailBody>

	<messaging:attachment>
		<apex:repeat var="cx" value="{!relatedTo.Cases}">
			Case Number: {!cx.CaseNumber}
			Origin: {!cx.Origin}
			Creator Email: {!cx.Contact.email}
			Case Number: {!cx.Status}
		</apex:repeat> 
	</messaging:attachment>
</messaging:emailTemplate>

此标记在电子邮件中呈现为附加的数据文件,没有任何格式。您可以 使用以下选项之一以更具可读性的格式显示数据:

  • 更改文件名
  • 更改 renderAs 属性
  • 添加样式和图像

更改文件名

该标签具有 属性,用于定义 附加文件的名称。虽然定义一个容易的 可识别的名称,它不是必需的。如果您将其保留为未定义,Salesforce 会生成一个名称 给你的。<messaging:attachment>filename不带扩展名的文件名默认为文本文件。您可以渲染附加的 文件作为 CSV格式:

<messaging:attachment filename="cases.csv">
	<apex:repeat var="cx" value="{!relatedTo.Cases}">
		{!cx.CaseNumber}
		{!cx.Origin}
		{!cx.Contact.email}
		{!cx.Status}
	</apex:repeat> 
</messaging:attachment>

您还可以将数据呈现为 HTML 格式 文件:

<messaging:attachment filename="cases.html">
	<html>
		<body>
		<table border="0" >
			<tr>
				<th>Case Number</th><th>Origin</th>
				<th>Creator Email</th><th>Status</th>
			</tr>
			<apex:repeat var="cx" value="{!relatedTo.Cases}">
			<tr>
				<td><a href = 
					"https://MyDomainName.my.salesforce.com/{!cx.id}">{!cx.CaseNumber}
				</a></td>
				<td>{!cx.Origin}</td>
				<td>{!cx.Contact.email}</td>
				<td>{!cx.Status}</td>
			</tr>
			</apex:repeat> 
		</table>
		</body>
	</html>
</messaging:attachment>

虽然您只能为每个组件定义一个文件名,但可以将多个文件附加到 一封电子邮件。<messaging:attachment>

更改 renderAs 属性

与其他 Visualforce 页面类似,在组件上将 renderAs 属性设置为 PDF 会将附件呈现为 PDF。 为 例:

<messaging:attachment>

<messaging:attachment renderAs="PDF" filename="cases.pdf">
	<html>
		<body>
		<p>You can display your {!relatedTo.name} cases as a PDF:</p>
			<table border="0" >
			<tr>
				<th>Case Number</th><th>Origin</th>
				<th>Creator Email</th><th>Status</th>
			</tr>
			<apex:repeat var="cx" value="{!relatedTo.Cases}">
			<tr>
				<td><a href = 
					"https://MyDomainName.my.salesforce.com/{!cx.id}">{!cx.CaseNumber}
				</a></td>
				<td>{!cx.Origin}</td>
				<td>{!cx.Contact.email}</td>
				<td>{!cx.Status}</td>
			</tr>
			</apex:repeat> 
		</table>
		</body>
	</html>
</messaging:attachment>

Visualforce PDF 渲染的局限性 服务包括以下内容。

  • PDF 是唯一受支持的渲染服务。
  • PDF 渲染服务渲染 PDF 版本 1.4 和 CSS 版本,最高可达 2.1.
  • 渲染 作为 PDF 文件的 Visualforce 页面适用于设计和优化的页面 用于打印。
  • 呈现的 Visualforce 页面 PDF文件要么显示在浏览器中,要么被下载,这取决于 浏览器的设置。具体行为取决于浏览器、版本、 和用户设置,并且不受 Visualforce 的控制。
  • PDF 呈现服务在页面上呈现标记和数据,但它可能会 不呈现富文本区域字段内容中包含的格式 添加到页面。
  • 没有断点的长文本行(如空格或短划线)不能 由 PDF 渲染服务包装。这种情况最常发生 具有长 URL、注册表项等。当这些线比 页面,它们将页面内容的宽度增加到 PDF 边缘之外 页。然后,内容从页面的一侧“流出”并被切断。
  • 不要使用不容易格式化以用于打印或表单的标准组件 元素,例如输入或按钮,或任何需要 JavaScript 的组件 格式化。
  • PDF 呈现不支持 JavaScript 呈现的内容。
  • Salesforce 移动应用程序中的页面不支持 PDF 呈现。
  • 页面上使用的字体必须在 Visualforce PDF 渲染中可用 服务。不支持 Web 字体。
  • 如果 PDF 文件无法显示页面的所有文本,尤其是多字节 字符,例如日语或重音国际字符,请调整您的 CSS 使用支持它们的字体。为 例:<apex:page showHeader="false" applyBodyTag="false" renderAs="pdf"> <head> <style> body { font-family: 'Arial Unicode MS'; } </style> </head> <body> これはサンプルページです。<br/> This is a sample page: API version 28.0 </body> </apex:page>“Arial Unicode MS”是唯一支持的字体 包含多字节字符的扩展字符集。
  • 如果您使用内联 CSS 样式,请将 API 版本设置为 28.0 或更高版本。还设置了, 并向页面添加静态、有效和标签,如上文所示 例。<apex:page applyBodyTag=”false”><head><body>
  • 创建 PDF 文件时的最大响应大小必须小于 15 MB,然后才能呈现为 PDF 文件。此限制是以下标准限制 所有 Visualforce 请求。
  • 生成的 PDF 文件的最大文件大小为60兆字节.
  • 生成的 PDF 中包含的所有图像的最大总大小为30兆字节.
  • PDF 呈现不支持以 data: URI 方案格式编码的图像。
  • PDF 渲染不支持 WebP 图像或 SVG 标记。
  • 以下组件在呈现为 PDF 时不支持双字节字体。
    • <apex:pageBlock>
    • <apex:sectionHeader>
    不建议在呈现为 PDF 的页面中使用这些组件。
  • 如果 或 没有 rendered,将页面呈现为 PDF 失败。要解决此问题,请将 table 组件的属性,如果其子组件均不为 呈现。<apex:dataTable><apex:pageBlockTable><apex:column>renderedfalse<apex:column>

添加样式和图像

附件还可以使用样式表来更改数据的呈现方式。风格 与附件的关联方式与在 Visualforce 电子邮件中的关联方式相同 模板,可以作为内联代码,也可以使用自定义组件。

呈现为 PDF 的附件可以通过 $Resource 全局变量引用静态资源。这 使您能够引用 PDF 正文中的图像或样式表。例如,以下附件在 PDF格式:

<messaging:attachment renderAs="PDF" filename="cases.pdf">
		<html>
			<body>
			<img src = "{!$Resource.logo}" />
			...
			</body>
		</html>
	</messaging:attachment>

此附件引用已另存为静态的样式表 资源:

<messaging:attachment renderAs="PDF">
		<html>
		<link rel='stylesheet' type='text/css' href='{!$Resource.EMAILCSS}' />
			<body>
			...
			</body>
		</html>
	</messaging:attachment>

警告

在远程服务器上引用静态资源可能会增加 呈现 PDF 附件所需的时间。您不能引用遥控器 在 Apex 触发器中创建 PDF 附件时的资源;这样做将导致 异常。

在 Visualforce 电子邮件模板中使用自定义控制器

Visualforce 电子邮件模板可以利用自定义控制器进行高度渲染 定制内容。为此,请在使用该自定义控制器的 Visualforce 电子邮件模板中包含自定义组件。例如,假设您要显示所有帐户的列表 以电子邮件模板中的“Smith”一词开头。 为此,请首先编写一个使用 SOSL 调用的自定义控制器 返回以“Smith”开头的帐户列表:

public class findSmithAccounts {
	private final List<Account> accounts;

	public findSmithAccounts() {
		accounts = [select Name from Account where Name LIKE 'Smith_%'];
	}

	public List<Account> getSmithAccounts() {
		return accounts;
	}
}

接下来,创建一个名为使用此控制器的自定义组件:

smithAccounts

<apex:component controller="findSmithAccounts" access="global">
	<apex:dataTable value="{!SmithAccounts}" var="s_account">
		<apex:column>
			<apex:facet name="header">Account Name</apex:facet>
			{!s_account.Name}
		</apex:column>
	</apex:dataTable>
</apex:component>

提示

请记住,Visualforce 电子邮件模板中使用的所有自定义组件的级别必须为 .accessglobal最后,创建一个包含组件的 Visualforce 电子邮件模板:

smithAccounts

<messaging:emailTemplate subject="Embedding Apex Code" recipientType="Contact" relatedToType="Opportunity">
	<messaging:htmlEmailBody>
		<p>As you requested, here's a list of all our Smith accounts:</p>
		<c:smithAccounts/>
		<p>Hope this helps with the {!relatedToType}.</p>
	</messaging:htmlEmailBody>
</messaging:emailTemplate>

请注意,尽管该属性是组件所必需的,但它对此示例没有任何影响。它具有 仅值 以表明它可以采用与 自定义组件中使用的对象。relatedToTypeemailTemplate“Opportunity”

注意

如果您的电子邮件模板使用 标准控制器。如果组织范围的用户默认 object 设置为 Private,您需要访问用户信息,例如 作为 Visualforce 电子邮件模板中的名称和电子邮件地址,您可以使用自定义组件或自定义控制器 替换为“不共享”关键字。

为 有关用户对象共享的信息,请参阅 Salesforce Online 中的用户共享概述 帮助。

动态 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。你会看到一个 类似于以下内容的序列:

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

动态 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