Visualforce 远程对象

JavaScript 远程处理是一种常用、功能强大且高效的 Web 应用程序构建方法 使用 Visualforce,特别是用于创建用于 Salesforce 移动应用程序或工作的页面 使用 JavaScript 库,例如 jQuery 或 AngularJS。Visualforce 远程 对象是启用 直接从 JavaScript 对 sObject 执行基本 DML 操作。远程 对象消除了一些复杂性 通过减少对 Apex 控制器或扩展中方法的需求,从 JavaScript 远程处理。

@RemoteAction

幕后,远程 对象控制器处理共享规则、字段级安全性和其他数据可访问性 关注。使用远程的页面 对象受所有标准 Visualforce 限制的约束,但与 JavaScript 远程处理、远程处理一样 对象调用不计算在内 达到 API 请求限制。使用 Visualforce Remote 对象包括在同一页面上实现两个单独的功能。

  1. 访问定义,使用 Visualforce 和 Remote 编写 对象组件。这些组件会生成一组 JavaScript 代理对象,您可以 在步骤 2 中使用。
  2. 数据访问函数,用 JavaScript 编写。这些函数使用代理对象 由访问定义提供,以执行创建、检索、更新和 对数据执行删除操作。

然后,您的页面使用数据访问函数来响应用户交互,例如 表单提交或控制更改,或执行定期操作以响应计时器,或 你可以用 JavaScript 编写的大多数内容。

远程的简单示例 对象

这个简短的示例演示了您需要实现的两个功能 使用远程对象。此 Visualforce 页面检索一个 列出 10 条仓库记录,并在页面上显示它们以响应用户 单击〖检索仓库〗按钮。

<apex:page>
    
    <!-- Remote Objects definition to set accessible sObjects and fields -->
    <apex:remoteObjects >
        <apex:remoteObjectModel name="Warehouse__c" jsShorthand="Warehouse" 
            fields="Name,Id">
            <apex:remoteObjectField name="Phone__c" jsShorthand="Phone"/>
        </apex:remoteObjectModel>
    </apex:remoteObjects>

    <!-- JavaScript to make Remote Objects calls -->
    <script>
        var fetchWarehouses = function(){
            // Create a new Remote Object
            var wh = new SObjectModel.Warehouse();
            
            // Use the Remote Object to query for 10 warehouse records
            wh.retrieve({ limit: 10 }, function(err, records, event){
                if(err) {
                    alert(err.message);
                }
                else {
                    var ul = document.getElementById("warehousesList");
                    records.forEach(function(record) {
                        // Build the text for a warehouse line item
                        var whText = record.get("Name");
                        whText += " -- ";
                        whText += record.get("Phone");
                        
                        // Add the line item to the warehouses list
                        var li = document.createElement("li");
                        li.appendChild(document.createTextNode(whText));
                        ul.appendChild(li);
                    });
                }
            });
        };
    </script>

    <h1>Retrieve Warehouses via Remote Objects</h1>

    <p>Warehouses:</p>

    <ul id="warehousesList">
    </ul>
    <button onclick="fetchWarehouses()">Retrieve Warehouses</button>

</apex:page>

通知 此页面有些不寻常 – 没有控制器或控制器扩展。 所有数据访问均由远程处理 对象组件。此示例的第一部分是远程 对象组件,用于指定要在 页。

<apex:remoteObjects >
    <apex:remoteObjectModel name="Warehouse__c" jsShorthand="Warehouse" fields="Name,Id">
        <apex:remoteObjectField name="Phone__c" jsShorthand="Phone"/>
    </apex:remoteObjectModel>
</apex:remoteObjects>

这些 组件生成 JavaScript 模型类,访问中的每个 sObject 一个 规范,用于直接从 JavaScript 进行数据访问调用 法典。请注意该属性的使用,该属性将完整的 Salesforce API 名称映射到 在 JavaScript 代码中使用的更简单、更短的名称。如果您打算打包和 分发代码,设置为 必不可少,因为它消除了在 打包的代码。使用速记可以完成所有工作。

jsShorthandjsShorthand此示例的第二部分是一个 JavaScript 函数,该函数使用以下模型 由访问定义组件生成,用于检索一组要显示的记录 在 页。

<!-- JavaScript to make Remote Objects calls -->
<script>
    var fetchWarehouses = function(){
        // Create a new Remote Object
        var wh = new SObjectModel.Warehouse();
        
        // Use the Remote Object to query for 10 warehouse records
        wh.retrieve({ limit: 10 }, function(err, records, event){
            if(err) {
                alert(err.message);
            }
            else {
                var ul = document.getElementById("warehousesList");
                records.forEach(function(record) {
                    // Build the text for a warehouse line item
                    var whText = record.get("Name");
                    whText += " -- ";
                    whText += record.get("Phone");
                    
                    // Add the line item to the warehouses list
                    var li = document.createElement("li");
                    li.appendChild(document.createTextNode(whText));
                    ul.appendChild(li);
                });
            }
        });
    };
</script>

这 函数的第一行从模型创建一个 Warehouse 对象。请注意, 创建它的调用使用 for sObject 而不是对象的完整 API 名称。遵循此最佳实践 将 JavaScript 代码与组织命名空间的细节分离, sObject 和字段名称等,并使代码更加简洁明了。

jsShorthand

第二行使用新的 Warehouse 对象 来执行对 Warehouse 记录的查询。该调用提供两个 参数:一个简单的查询说明符和一个用于处理结果的匿名函数。这 函数是标准的 JavaScript。它循环访问结果并创建列表项以 追加到页面上的仓库列表。wh页面正文是静态的 HTML。

<h1>Retrieve Warehouses via Remote Objects</h1>

<p>Warehouses:</p>

<ul id="warehousesList">
</ul>
<button onclick="fetchWarehouses()">Retrieve Warehouses</button>

你 代码将结果添加到列表中。 加载页面时,列表为空。单击该按钮将触发 JavaScript 之前定义的函数,用于执行查询并添加结果。

warehousesList

使用远程 JavaScript 中的对象

远程生成的 JavaScript 模型 对象组件提供了一个 JavaScript API,用于为应用创建读取和保存的函数 值返回给 Salesforce。使用组件创建的基本模型进行实例化 相应 sObject 的特定模型。然后使用特定模型执行操作 在他们的 sObject 上,例如检索、创建、更新和删除。

<apex:remoteObjects>远程的基本型号 对象由组件创建。基本模型为 Remote 提供了一个伪命名空间 使用它创建的对象。默认情况下,基本模型是 命名的,但您可以使用该属性来设置名称。使用不同的底座 对相关远程进行分组的模型 沿功能线或包装线的对象。为 例:

<apex:remoteObjects>SObjectModeljsNamespace

<apex:remoteObjects jsNamespace="MyCorpModels">
    <apex:remoteObjectModel name="Contact" fields="FirstName,LastName"/>
</apex:remoteObjects>
<apex:remoteObjects jsNamespace="TrackerModels">
    <apex:remoteObjectModel name="Shipment__c" fields="Id,TrackNum__c"/>
</apex:remoteObjects>

特定型号

您通常不会自己创建基础模型,而是使用生成的 基础模型作为创建特定模型的工厂。例如,使用上面的 声明,在 JavaScript 中实例化一个 Contact 模型,例如 这:

var ct = new MyCorpModels.Contact();

注意 这是一个 JavaScript 模型 联系人对象,而不是特定的联系人记录。

ct

ct表示一个特定的对象,并提供 页面的 JavaScript 和 Salesforce 服务。 可用于执行基本的“CRUD” 对 Contact 对象执行的操作(创建、读取、更新和删除)在 数据库。Contactct在以下各节中,示例基于以下远程 对象声明,它使用所有三个 Remote 对象组件,并演示如何添加自定义字段、Notes__c、 带有“简写”名称,以便在 JavaScript 中访问它 自然的。

<apex:remoteObjects jsNamespace="RemoteObjectModel">
    <apex:remoteObjectModel name="Contact" fields="Id,FirstName,LastName,Phone">
        <apex:remoteObjectField name="Notes__c" jsShorthand="Notes"/>
    </apex:remoteObjectModel>
</apex:remoteObjects>

这 声明使您能够访问联系人记录上的五个字段。

实例化模型和访问字段

实例化设置了或未设置字段值的模型,具体取决于您的意图。 通常,当您想要将更改写入数据库时,您将设置字段 并在您只是阅读时省略字段。字段值是通过传入来设置的 一个 JSON 字符串,其中包含要在新模型上设置的字段的值。若要创建未设置字段的模型,请使用空参数创建该模型 列表。

var ct = new RemoteObjectModel.Contact();

要实例化设置了字段的模型,通常要创建新记录,请传入 包含字段名称和值对的对象。为 例:

var ct = new RemoteObjectModel.Contact({ 
    FirstName: "Aldo",
    LastName: "Michaels", 
    Phone: "(415) 555-1212"
});

远程 对象模型使用基本 和 方法来检索和设置字段值。 为 例:

get()set()

var ct = new RemoteObjectModel.Contact({ FirstName: "Aldo" });
ct.get('FirstName');  // 'Aldo'
ct.get('Phone'); // <undefined>
ct.set('FirstName', 'Benedict');
ct.set('Phone', '(415) 555-1212');

有 使用属性列表中的属性列表设置字段值之间没有功能差异 构造函数并使用 设置字段值。

set()

使用 Remote 创建记录 对象

通过调用 Remote 创建记录 对象模型实例。

create()

create()接受两个 参数,两者都是可选的。

RemoteObjectModel.create({field_values}, callback_function)

块 使您能够在一个语句中定义和创建记录。设置字段 值,就像使用 JSON 字符串创建模型时一样。为 例如,以下两个调用是等效的。

field_valuescreate()

var ctDetails = { FirstName: 'Marc', LastName: 'Benioff' };

// Call create() on an existing Contact model, with no arguments
var ct = new RemoteObjectModel.Contact(ctDetails);
ct.create();

// Call create() on an empty Contact model, passing in field values
var ct = new RemoteObjectModel.Contact();
ct.create(ctDetails);

create()不 直接返回结果。回调函数使您能够处理 异步服务器响应。

注意

所有服务器操作 使用远程 对象是异步执行的。任何依赖于 正在完成的请求(包括处理返回的结果)必须 放置在回调函数中。你的回调函数 最多可以接受三个参数。

function callback(Error error, Array results, Object event) { // ... }

查看远程 对象回调函数,了解有关编写 Remote 的详细信息 对象回调函数。该字段已设置为 远程对象作为成功调用的一部分。您可以访问此 字段。

Idcreate()

var ctDetails = { FirstName: 'Marc', LastName: 'Benioff' };
var ct = new RemoteObjectModel.Contact();
ct.create(ctDetails, function(err) {
    if(err) { 
        console.log(err);
        alert(err.message);
    }
    else {
        // this is the contact
        console.log(ct.log());     // Dump contact to log
        console.log(ct.get('Id')); // Id is set when create completes
    }
});

注意该函数的使用;它相当于 Remote 对象。

log()toString()

使用远程检索记录 对象

通过调用远程来检索记录 对象模型实例。

retrieve()

retrieve()需要两个参数,一个用于查询 条件,一个用于回调 处理器。

RemoteObjectModel.retrieve({criteria}, callback_function)

criteria可以是远程对象查询对象或返回对象的函数。以下两个调用是 等效。

var ct = new RemoteObjectModel();

// Empty callback functions for simplicity
ct.retrieve({where: {FirstName: {eq: 'Marc' }}}, function() {}); // query object

ct.retrieve(function(){
	return({where: {FirstName: {eq: 'Marc' }}});
}, function() {}); // function returning query object

请参阅远程的格式和选项 对象查询条件,用于查询对象的说明。

retrieve()不返回结果 径直。回调函数使您能够处理服务器响应 异步。

注意

所有服务器操作 使用远程 对象是异步执行的。任何依赖于 正在完成的请求(包括处理返回的结果)必须 放置在回调函数中。你的回调函数 最多可以接受三个参数。

function callback(Error error, Array results, Object event) { // ... }

查看远程 对象回调函数,了解有关编写 Remote 的详细信息 对象回调函数。

若要使用日期检索记录,请将 JavaScript date 对象传入查询。

var myDate = new Date('2017-01-20');
ct.retrieve({where: {CloseDate: {eq: myDate}}}, function() {});

使用远程更新记录 对象

通过调用远程来更新记录 对象模型实例。

update()

update()接受三个 参数,都是可选的,并且可以在 同时,具体取决于您提供的参数。

RemoteObjectModel.update([record_ids], {field_values}, callback_function)

record_ids是一个字符串数组,其中字符串是 要更新的记录数。如果这 参数,则在 使用远程对象实例。更新记录的最简单方法是调用 本身。

IdIdupdate()

ctDetails = {FirstName: "Marc", LastName: "Benioff"};
ct = new RemoteObjectModel.Contact(ctDetails);
ct.create();

// Later, in response to a page event...
ct.set('Phone', '555-1212');
ct.update();

更常见的是,您可能需要更新记录以响应表单提交。更新 记录可以像读取一些表单值一样简单,包括记录的 ,并将这些值传递给 。为 例:

Idupdate()

var record = new RemoteObjectModel.Contact();
record.update($j('#contactId').val(),
{
    FirstName: $j('#fName').val(),
    LastName: $j('#lName').val(),
    Phone: $j('#phone').val(),
    Notes: $j('#notes').val() 
});

可靠的代码包括用于处理错误的回调。以下 代码的完成方式与上一个示例相同,但已更改为使用 事件处理程序和回调函数。

// Handle the Save button
function updateContact(e){
    e.preventDefault();

    var record = new RemoteObjectModel.Contact({
        Id: $jQuery('#contactId').val(),
        FirstName: $jQuery('#fName').val(),
        LastName: $jQuery('#lName').val(),
        Phone: $jQuery('#phone').val(),
        Notes: $jQuery('#notes').val() 
    });
    record.update(updateCallback);
}

// Callback to handle DML Remote Objects calls
function updateCallback(err, ids){
    if (err) { 
        displayError(err); 
    } else {
        // Reload the contacts with current list
        getAllContacts();
        $jQuery.mobile.changePage('#listpage', {changeHash: true});
    }
}

您可以同时更新多条记录,只要更新 要执行的是统一的,即每条记录都相同。为 例如,您可能需要从 列表,将状态字段更改为“已存档”或 当前时间戳。若要更新一个请求中的记录,请传递数组 的 s 到 .要更新的字段 可以设置为远程对象模型本身的一部分,但它是 将它们直接传递给 更安全,如下所示:

Idupdate()update()

var ct = new RemoteObjectModel.Contact();
ct.update(
    ['003xxxxxxxxxxxxxxx', '003xxxxxxxxxxxxxxx'], 
    { FirstName: "George", LastName: "Foreman" },
    function(err, ids) {
        if (err) { 
            displayError(err); 
        } else {
            // Reload the contacts with current list
            getAllContacts();
            $jQuery('#status').html(ids.length + ' record(s) updated.');
            $jQuery.mobile.changePage('#listpage', {changeHash: true});
        }
});

注意

当您以这种方式更新多条记录时,所有 的记录在同一服务器端事务中更新。

使用远程更新插入记录 对象

通过调用远程来保存记录 对象模型实例。

upsert()

upsert()是一种便利 函数,如果记录存在,则更新记录,如果不存在,则创建记录。 在幕后委托给 或 .用于为 不受记录影响的页面或应用程序 来自新的输入表单或编辑记录页面。upsert()create()update()upsert()

upsert()接受两个参数, 两者都是可选的。

RemoteObjectModel.upsert({field_values}, callback_function)

块 使您能够设置值并将记录保存在一个语句中。 像创建模型时一样,使用 JSON 字符串设置字段值。 例如,以下两个调用是等效的。

field_valuesupsert()

// Call upsert() on a Contact model, with no arguments
// ct is a RemoteObjectModel.Contact that already has data
ct.set('Phone', '(415) 777-1212');
ct.upsert();

// Call upsert() on a Contact model, passing in field values
// ct is a RemoteObjectModel.Contact that already has data
ct.upsert({Phone: '(415) 777-1212'});

在前面的示例中,不清楚联系人是否 存在于数据库中,或者如果它是新联系人,则 来自输入表单。 处理细节。如果在联系人上设置了字段,则 联系方式将更新。如果没有,则创建一个新联系人。upsert()IdId

upsert()不 直接返回结果。回调函数使您能够处理 异步服务器响应。

注意

所有服务器操作 使用远程 对象是异步执行的。任何依赖于 正在完成的请求(包括处理返回的结果)必须 放置在回调函数中。你的回调函数 最多可以接受三个参数。

function callback(Error error, Array results, Object event) { // ... }

查看远程 对象回调函数,了解有关编写 Remote 的详细信息 对象回调函数。

使用 Remote 删除记录 对象

通过调用远程删除记录 对象模型实例。

del()

del()接受两个参数, 两者都是可选的,并且可以删除一条或多条记录,具体取决于 您提供的参数。

注意

为什么代替 ? 是保留字 在 JavaScript 中。del()delete()delete

RemoteObjectModel.del([record_ids], callback_function)

record_ids是一个字符串数组,其中字符串是 要删除的记录的 S。如果这 参数,则在 使用远程对象实例。删除记录的最简单方法是调用 本身。

IdIddel()

ctDetails = {FirstName: "Tobe", LastName: "Ornottobe"};
ct = new RemoteObjectModel.Contact(ctDetails);
ct.create();

// After some thought, and the async operation completes...
// It's not to be; delete the contact
ct.del();

更常见的是,您可能需要删除记录以响应 按钮单击。删除记录就像从页面中获取记录然后传递一样简单 的 到 .例如:

IdIddel()

var id = $jQuery('#contactId').val();
var ct = new RemoteObjectModel.Contact();
ct.del(id);

可靠的代码包括用于处理错误的回调。以下 代码的完成方式与上一个示例相同,但已更改为使用 事件处理程序和回调函数。

// Handle the delete button click
function deleteContact(e){
    e.preventDefault();
    var ct = new RemoteObjectModel.Contact();
    ct.del($jQuery('#contactId').val(), updateCallback);
}

// Callback to handle DML Remote Objects calls
function updateCallback(err, ids){
    if (err) { 
        displayError(err); 
    } else {
        // Reload the contacts with current list
        getAllContacts();
        $jQuery.mobile.changePage('#listpage', {changeHash: true});
    }
}

要删除一个请求中的多条记录,例如,选中 列表中的项目 – 将 s 数组传递给 。

Iddel()

var ct = new RemoteObjectModel.Contact();
ct.del(['003xxxxxxxxxxxxxxx', '003xxxxxxxxxxxxxxx'], function(err, ids) {
    if (err) { 
        displayError(err); 
    } else {
        // Reload the contacts with current list
        getAllContacts();
        $jQuery('#status').html(ids.length + ' record(s) deleted.');
        $jQuery.mobile.changePage('#listpage', {changeHash: true});
    }
});

注意

当您以这种方式删除多条记录时,所有 的记录将在同一服务器端事务中删除。

远程的格式和选项 对象查询条件

远程 对象使用对象来指定操作条件。使用此对象 指定查询的位置、限制和偏移条件。

retrieve()查询对象的结构化格式使 Visualforce 能够验证 标准,减少运行时错误的可能性。格式为 简单。

<apex:remoteObjectsjsNamespace="RemoteObjectModel">    
    <apex:remoteObjectModel name="Contact" fields="FirstName,LastName"/>  
</apex:remoteObjects>

<script>
var ct = new RemoteObjectModel.Contact();
ct.retrieve( 
    { where: { 
        FirstName: {eq: 'Marc'}, 
        LastName: {eq: 'Benioff'} 
      }, 
      orderby: [ {LastName: 'ASC'}, {FirstName: 'ASC'} ],
      limit: 1 },  

    function(err, records) { 
        if (err) { 
            alert(err); 
        } else { 
            console.log(records.length); 
            console.log(records[0]); 
        } 
    } 
);
</script>

这 查询条件:找到名为 Marc Benioff 的联系人,并将查询限制为单个 结果。

哪里条件

where条件使您能够筛选 检索操作的结果,与 SOQL 查询中的条件大致相同。可用的运算符 条件是:

WHEREwhere

  • eq:等于
  • ne:不等于
  • lt:小于
  • lte:小于或等于
  • gt:大于
  • gte:大于或等于
  • like:字符串匹配。与 SOQL,使用“%”作为通配符。
  • in:in,用于查找与一组 固定值。提供 值作为数组,例如 [‘Benioff’, ‘Jobs’, ‘盖茨’]。
  • nin:not in,用于查找与 一组固定值。提供 值作为数组,例如 [‘Benioff’, ‘Jobs’, ‘盖茨’]。
  • and:逻辑 AND,用于 组合条件
  • or:逻辑 OR,用于组合 条件

在对象中,添加字段名称和 条件对以创建复杂的条件。默认情况下,多个条件是 视为 AND 条件。您可以使用 和 创建其他条件条件。 为 例:

whereandor

{ 
where: 
    { 
    or: 
        {
        FirstName: { like: "M%" }, 
        Phone: { like: '(415)%' }
        }
    }
}

使用 和 的组合根据日期范围筛选结果。例如:ltegte

<apex:remoteObjects jsNamespace="RemoteObjectModel">
    <apex:remoteObjectModel name="Account" fields="Id,Name,CreatedDate"/>
</apex:remoteObjects>

<script>
    var account_created_from_date = new Date('2017-01-01');
    var account_created_to_date = new Date('2018-01-01');
    var clauses = {       
        'where': {
            'CreatedDate': { 'lte': account_created_to_date },
            'and': {
                'CreatedDate': { 'gte': account_created_from_date },
                'Id': { 'ne': '' }
            }
        }   
    };

    var ct = new RemoteObjectModel.Account();
    ct.retrieve(
        clauses,
        function(err, records) { 
            if (err) { 
                console.log(err); 
            } else { 
                console.log(records.length); 
                console.log(records[0]); 
            } 
        } 
    );
</script>

订购者条件

orderby使您能够 为结果设置排序顺序。您最多可以对三个字段进行排序。指定条件 作为包含名称/值对的 JavaScript 对象数组。这 要排序的字段是名称,排序说明是值。 排序说明使您能够按升序或降序排序,并 对 NULL 值进行排序。例如:

orderby

orderby: [ {Phone: "DESC NULLS LAST"} , {FirstName: "ASC"} ]

限制和偏移条件

limit并使您能够检索特定的 一次记录数,并翻阅一组扩展的 结果。offset

用于 指定在一批结果中要返回的记录数。默认 值为 20。最大值为 100。limit

用于指定要跳过的记录数 在将记录添加到返回结果之前的总体结果集。最小值为 1。 最大偏移量为 2,000 行。请求 大于 2,000 的偏移量会导致错误。offsetNUMBER_OUTSIDE_VALID_RANGE

远程 对象回调函数

远程 对象以异步方式将所有请求发送到 Salesforce 服务。代码处理响应 到远程 您提供的回调函数中的对象操作。回调函数处理更新 包含操作结果和返回的错误的页面。

回调函数是标准 JavaScript 中用于处理事件和异步操作的技术。远程 对象使用此模式来处理其异步操作的响应。当你 调用远程 对象操作,您可以提供操作的参数和回调(可选) 功能。调用操作后,JavaScript 代码将继续不间断地运行。 当远程操作完成并返回结果时,回调函数 被调用并接收操作的结果。远程 可以编写对象回调函数以接收最多三个参数。

function callback(Error error, Array results, Object event) { // ... }
名字类型描述
错误JavaScript Error 对象标准 JavaScript Error 对象。如果操作成功,则为 。用于检索 失败的原因。errornullerror.message
结果JavaScript 数组一个数组,包含操作的结果。如果 操作是一个, 结果是相应 Remote 的实例 对象。否则,数组包含表示受影响 记录。retrieve()Id
事件JavaScript 对象一个 JavaScript 对象,该对象提供传输 Remote 的 JavaScript 远程处理事件的详细信息 对象操作。

大多数回调函数会检查错误,然后使用 结果。通常使用该对象 仅在调试和复杂的错误管理方面。

event

下面是一个简单的回调函数,用于处理操作的结果。

retrieve()

function getAllContacts() {
    $j.mobile.showPageLoadingMsg();
    
    var c = new RemoteObjectModel.Contact();
    c.retrieve({ limit: 100 }, function (err, records) { 
        // Handle errors
        if (err) { 
            displayError(err); 
        } else {
            // Add the results to the page
            var list = $j(Config.Selectors.list).empty();
            $j.each(records, function() {
                var newLink = $j('<a>'+this.get('FirstName')+' '+this.get('LastName')+'</a>');
                newLink.appendTo(list).wrap('<li></li>');
            });
            
            $j.mobile.hidePageLoadingMsg();
            list.listview('refresh');
        }
    });
}

在 此示例调用匿名函数并将其传递为 回调。回调函数检查错误,然后使用 jQuery 循环访问结果记录数组,并将它们添加到页面中。一些细节 省略以专注于回调结构。请参阅使用远程的示例 带有 jQuery Mobile 的对象,用于获取完整的页面源代码。

getAllContacts()retrieve()

覆盖默认远程对象操作

覆盖默认的远程 使用您自己的 Apex 代码进行对象操作,以扩展或自定义 Remote 的行为 对象。

Remote 幕后花絮 对象、基本操作—, , ,和 —使用 Remote 对象控制器,相当于普通 Visualforce 页面的标准控制器。您可以 覆盖远程 用于扩展或替换此控制器的内置行为的对象操作。重写 远程的 对象操作是用 Apex 编写的,并通过将它们添加到页面的 Remote 中来生效 对象定义。create()retrieve()update()del()

注意

无法重写该操作。 它只是一个便利功能,在幕后它委托给 either 或 .重写其中任一方法时,重写的方法 被自动用作 适当。upsert()create()update()upsert()

远程 方法重写的对象访问定义

覆盖远程使用远程方法对对象进行操作,将操作的属性设置为方法 替换默认方法。例如,下面介绍如何使用远程覆盖联系人的操作 方法。

create()

<apex:remoteObjectModel name="Contact" fields="FirstName,LastName,Phone" 
    create="{!$RemoteAction.RemoteObjectContactOverride.create}"/>

该属性采用 视觉力 引用要用作内置操作重写的方法的表达式。@RemoteActioncreate()这 表达式采用 的形式,为 全局处理您的位置 组织命名空间,就像 JavaScript 远程处理一样。请注意, 包含该方法的类 需要设置为页面的控制器或控制器扩展 页。$RemoteAction.OverrideClassName.overrideMethodName$RemoteAction@RemoteAction

使用此声明,每当页面的 JavaScript 代码调用联系人远程对象的函数时, 而不是使用远程对象控制器,将调用您的远程方法。create()

远程 对象重写方法

远程 对象重写方法作为 Apex 类中的方法编写,您可以将其作为 控制器或控制器扩展。@RemoteAction重写的方法签名 方法 是:

@RemoteAction
public static Map<String,Object> methodName(String type, Map<String,Object> fields)

type 参数是正在执行操作的 sObject 类型, 字段映射是包含值的集合 在重写方法之前在远程对象上设置的 叫。

返回值是表示 Remote 结果的映射 对象操作。此映射通常包括调用结果、状态和 要作为自定义方法的一部分提供的任何自定义数据。这 构造有效返回映射的最简单方法是使用 .这是标准 为远程提供内置功能的控制器 对象,您可以通过以下方式将数据操作语言 (DML) 操作委托给它 传递方法的参数。例如,这里有一个方法,它只执行 的内置版本:

RemoteObjectControllercreate()create()

@RemoteAction
public static Map<String, Object> create(String type, Map<String, Object> fields) {
    Map<String, Object> result = RemoteObjectController.create(type, fields);
    return result;
}

这 方法实际上是一个空操作;也就是说,此方法执行完全相同的操作 内置版本本来可以做到的,不多也不少。您的覆盖方法 可以执行您需要的任何其他 Apex,包括日志记录、额外的 DML、 其他方法调用,依此类推。有关远程的更完整示例 对象重写方法以及使用它的页面,请参阅在远程中使用远程方法重写的示例 对象。

重要

标准控制器自动处理 共享 Remote 的规则、所有权和其他安全问题 对象。相比之下,自定义控制器或控制器扩展中的方法在 系统模式,允许对组织中的所有数据进行完全访问。 此行为与使用 自定义控制器或控制器扩展。当您编写控制器代码时,您 需要自己处理访问权限和其他问题。RemoteObjectController

最佳做法是使用 你的关键词 控制器或控制器扩展类,并尽可能多地委托给 .with sharingRemoteObjectController

在 Remote 中使用 Remote 方法重写的示例 对象

此示例代码演示如何为 Remote 创建远程方法重写 对象操作。该示例提供了一个排序的联系人列表和一个用于输入 新联系人。新的联系人操作将覆盖内置的远程 对象操作。该示例还演示了 混合远程具有多个 Web 开发库的对象,用于呈现适合移动设备的用户 接口。

create()

此示例使用 jQuery、Bootstrap 和 Mustache 工具包,从 外部内容分发网络 (CDN)。这是 Visualforce 页面,其中包含 大胆。

<apex:page showHeader="false" standardStylesheets="false" docType="html-5.0" 
    title="Contacts—RemoteObjects Style" controller="RemoteObjectContactOverride">

    <!-- Include in some mobile web libraries -->
    <apex:stylesheet value="//netdna.bootstrapcdn.com/bootswatch/3.1.1/superhero/bootstrap.min.css"/>
    <apex:includeScript value="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"/>
    <apex:includeScript value="//cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"/>

    <!-- Set up Remote Objects, with an override for create() method -->
    <apex:remoteObjects jsNamespace="$M">
        <apex:remoteObjectModel name="Contact" fields="FirstName,LastName,Phone" 
            create="{!$RemoteAction.RemoteObjectContactOverride.create}"/>
    </apex:remoteObjects>

    <!-- Page markup -->
    <div class="container">
        <div class="row">
            <div class="col-md-2"></div>
            <div class="col-md-8">
                <table id="myTable" 
                    class="table table-bordered table-striped table-condensed">
                    <colgroup>
                        <col class="col-md-3" />
                        <col class="col-md-3" />
                        <col class="col-md-3" />
                    </colgroup>
                    <caption>
                        Contact Data Order ([ {LastName: 'ASC'}, {FirstName: 'DESC'} ]) 
                        <button id="bRefresh" class="btn btn-success btn-sm" 
                            type="button">Refresh</button>
                    </caption>
                    <caption id="msgBox" class="alert alert-danger hidden"></caption>
                    <thead>
                        <tr><td>FirstName</td><td>LastName</td><td>Phone</td></tr>
                    </thead>
                    <tbody></tbody>
                    <tfoot>
                        <tr>
                        <td><input type="text" name="FirstName" id="iFirstName" 
                            placeholder="John" class="form-control" /></td>
                        <td><input type="text" name="LastName" id="iLastName" 
                            placeholder="Doe" class="form-control" /></td>
                        <td>
                            <div class="input-group">
                              <input type="text" name="Phone" id="iPhone" 
                                placeholder="(123) 456-7890" class="form-control" />
                              <span class="input-group-btn">
                                <button id="bAdd" class="btn btn-primary" 
                                    type="button">Save</button>
                              </span>
                            </div>
                        </td>
                        </tr>
                    </tfoot>
                </table>
                <div class="panel panel-default">
                  <div class="panel-heading">Log</div>
                  <div class="panel-body" id="log">
                  </div>
                </div>            
            </div>
            <div class="col-md-2"></div>
        </div>
    </div>

    <!-- Results template (table rows of Contacts) -->
    <script id="tmpl" type="x-tmpl-mustache">
        <tr><td>{{FirstName}}</td><td>{{LastName}}</td><td>{{Phone}}</td></tr>
    </script>

    <!-- Page functionality -->
    <script>
        var table = $('#myTable tbody');
        var template = $('#tmpl').html();
        Mustache.parse(template);

        // Retrieve all contacts and add to results table on page
        var fetchContacts = function() {
            (new $M.Contact()).retrieve({
                orderby: [ {LastName: 'ASC'}, {FirstName: 'DESC'} ],
            }, function(err, records) {
                if (!err) {
                    // Add some status messages to the log panel
                    $('#log')
                    .append('<p>Fetched contact records.</p>')
                    .append('<p>Records Size: '+ records.length + '!</p>');

                    // Update the table of contacts with fresh results
                    table.empty();
                    records.forEach(function(rec) {
                        table.append(Mustache.render(template, rec._props));                    
                    });                
                } else {
                    $('#msgBox').text(err.message).removeClass('hidden');            
                }
            });
        };
        
        var addContact = function() {
            // Create a new Remote Object from form values
            (new $M.Contact({
                FirstName: $('#iFirstName').val(),
                LastName: $('#iLastName').val(),
                Phone: $('#iPhone').val()
            })).create(function(err, record, event) {
                // New record created...
                if (!err) {
                    // Reset the New Record form fields, for the next create
                    $('input').each(function() {
                        $(this).val('');
                    });
                    
                    // Add some status messages to the log panel
                    $('#log')
                    .append('<p>Contact created!</p>')
                    // Custom data added to event.result by override function
                    .append('<p>Got custom data: ' + event.result.custom + '</p>'); 

                    // Redraw the results list with current contacts
                    fetchContacts();
                } else {
                    $('#msgBox').text(err.message).removeClass('hidden');            
                }
            });
        };

        // Bind application functions to UI events
        $('#bRefresh').click(fetchContacts);
        $('#bAdd').click(addContact);
        
        // Initial load of the contacts list
        fetchContacts();
    </script>
</apex:page>

前面示例中的关键代码行位于 Remote 对象访问定义。将单个属性添加到联系人远程对象定义 设置 覆盖:

create="{!$RemoteAction.RemoteObjectContactOverride.create}"

该属性采用 视觉力 引用要用作内置操作重写的方法的表达式。

@RemoteActioncreate()在本例中,引用的方法位于 Apex 类中,该类是页面的 控制器。重写方法的代码很简单。

public with sharing class RemoteObjectContactOverride {
     
    @RemoteAction
    public static Map<String, Object> create(String type, Map<String, Object> fields) {
        System.debug(LoggingLevel.INFO, 'Before calling create on: ' + type);
        
        // Invoke the standard create action
        // For when you want mostly-normal behavior, with a little something different
        Map<String, Object> result = RemoteObjectController.create(type, fields);

        System.debug(LoggingLevel.INFO, 'After calling create on: ' + type);
        System.debug(LoggingLevel.INFO, 'Result: ' + result);
        
        // Here's the little something different, adding extra data to the result
        Map<String, Object> customResult = 
            new Map<String, Object> {'custom' => 'my custom data' };
        customResult.putAll(result);
        
        return customResult;
    }
}

此方法记录调用,然后 使用标准调用来执行创建。它 执行相同的数据操作语言 (DML) 命令以创建记录 内置版本会,因为它使用的是内置版本。后 执行 create,该方法会执行更多的日志记录。最后,它增加了一些额外的内容 数据复制到 JavaScript 回调函数将接收的返回有效负载 Visualforce 页面。@RemoteActionRemoteObjectController.create()

它添加了有趣的额外数据,并使覆盖 内置方法很有用。前面的控制器添加的额外数据 是微不足道的,仅用于说明目的。实际覆盖可以包括 更复杂的逻辑 – 计算的结果、其他方法调用等。 需要了解的重要一点是,新的自定义替代方法可以执行 幕后的附加内容,并可以返回内置版本的额外数据 不能。

使用远程的示例 使用 jQuery Mobile 的对象

Visualforce 远程 Objects 旨在与 JavaScript 框架很好地“融合”。这个扩展但简单的例子 演示如何使用远程 使用 jQuery Mobile 查看联系人列表以及添加、编辑和删除的对象 他们。

此示例使用 Salesforce Mobile Pack 中的 jQuery Mobile,并且基于 在移动随附的示例代码上 打包用于 jQuery。远程 对象和 jQuery Mobile 可以很容易地为 电话。

具有远程对象和 jQuery Mobile 的简单联系人编辑器

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

	<!-- Include jQuery and jQuery Mobile from the Mobile Pack -->
    <apex:stylesheet value="{!URLFOR($Resource.MobilePack_jQuery, 
         'jquery.mobile-1.3.0.min.css')}"/>
    <apex:includeScript value="{!URLFOR($Resource.MobilePack_jQuery, 
         'jquery-1.9.1.min.js')}"/>
    <apex:includeScript value="{!URLFOR($Resource.MobilePack_jQuery, 
         'jquery.mobile-1.3.0.min.js')}"/>

    <!-- Remote Objects declaration -->
    <apex:remoteObjects jsNamespace="RemoteObjectModel">
        <apex:remoteObjectModel name="Contact" fields="Id,FirstName,LastName,Phone">
            <!-- Notes is a custom field added to the Contact object -->
            <apex:remoteObjectField name="Notes__c" jsShorthand="Notes"/>
        </apex:remoteObjectModel>
    </apex:remoteObjects>

    <head>
        <title>Contacts</title>
        <meta name="viewport" 
           content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        
        <script type="text/javascript">
            var $j = jQuery.noConflict(); 

            // Config object with commonly used data
            // This keeps some hard-coded HTML IDs out of the code
            var Config = {
                Selectors: {
                    list: '#cList',
                    detailFields: "#fName #lName #phone #notes #error #contactId".split(" ")
                },
                Data: {
                    contact: 'contact'
                }
            };
            
            // Get all contacts, and display them in a list 
            function getAllContacts() {
                $j.mobile.showPageLoadingMsg();
                
                var c = new RemoteObjectModel.Contact();
                // Use the 'limit' operator to increase the default limit of 20
                c.retrieve({ limit: 100 }, function (err, records) { 
                    // Handle any errors
                    if (err) { 
                        displayError(err); 
                    } else {
                        // Empty the current list
                        var list = $j(Config.Selectors.list).empty();
                        // Now add results records to list
                        $j.each(records, function() {
                            var newLink = $j('<a></a>', {
                                text: this.get('FirstName') + ' ' + this.get('LastName')
                            });
                            newLink.data(Config.Data.contact, this.get('Id'));
                            newLink.appendTo(list).wrap('<li></li>');
                        });
                        
                        $j.mobile.hidePageLoadingMsg();
                        list.listview('refresh');
                    }
                });
            }

            // Handle the Save button that appears on both
            // the Edit Contact and New Contact pages
            function addUpdateContact(e){
                e.preventDefault();

                var record = new RemoteObjectModel.Contact({
                    FirstName: $j('#fName').val(),
                    LastName: $j('#lName').val(),
                    Phone: $j('#phone').val(),
                    Notes: $j('#notes').val() 
                    // Note use of shortcut 'Notes' in place of Notes__c
                });

                var cId = $j('#contactId').val();
                if( !cId ) { // new record
                    record.create(updateCallback);
                } else { // update existing
                    record.set('Id', cId);
                    record.update(updateCallback);
                }
            }
            
            // Handle the delete button
            function deleteContact(e){
                e.preventDefault();
                var ct = new RemoteObjectModel.Contact();
                ct.del($j('#contactId').val(), updateCallback);
            }
            
            // Callback to handle DML Remote Objects calls
            function updateCallback(err, ids){
                if (err) { 
                    displayError(err); 
                } else {
                    // Reload the contacts with current list
                    getAllContacts();
                    $j.mobile.changePage('#listpage', {changeHash: true});
                }
            }

            // Utility function to log and display any errors
            function displayError(e){
                console && console.log(e);
                $j('#error').html(e.message);
            }
        
            // Attach functions to the buttons that trigger them
            function regBtnClickHandlers() {
                $j('#add').click(function(e) {
                    e.preventDefault();
                    $j.mobile.showPageLoadingMsg();
                    
                    // empty all the clic handlers                    
                    $j.each(Config.Selectors.detailFields, function(i, field) {
                        $j(field).val('');
                    });
                    
                    $j.mobile.changePage('#detailpage', {changeHash: true});
                    $j.mobile.hidePageLoadingMsg();
                });
        
                $j('#save').click(function(e) {
                   addUpdateContact(e);
                });
        
                $j('#delete').click(function(e) {
                   deleteContact(e);
                });
            }

            // Shows the contact detail view, 
            // including filling in form fields with current data
            function showDetailView(contact) {
                $j('#contactId').val(contact.get('Id'));
                $j('#fName').val(contact.get('FirstName'));
                $j('#lName').val(contact.get('LastName'));
                $j('#phone').val(contact.get('Phone'));
                $j('#notes').val(contact.get('Notes'));
                $j('#error').html('');
                $j.mobile.changePage('#detailpage', {changeHash: true});
            }

            // Register click handler for list view clicks
            // Note: One click handler handles the whole list	
            function regListViewClickHandler() {
                $j(Config.Selectors.list).on('click', 'li', function(e) {

                    // show loading message
                    $j.mobile.showPageLoadingMsg();

                    // get the contact data for item clicked
                    var id = $j(e.target).data(Config.Data.contact);

                    // retrieve latest details for this contact
                    var c = new RemoteObjectModel.Contact();
                    c.retrieve({ 
                        where: { Id: { eq: id } } 
                    }, function(err, records) { 
                        if(err) { 
                            displayError(err); 
                        } else {
                            showDetailView(records[0]);
                        }

                        // hide the loading message in either case
                        $j.mobile.hidePageLoadingMsg();
                    });
                });
            }

            // And, finally, run the page
            $j(document).ready(function() {
                regBtnClickHandlers();
                regListViewClickHandler();
                getAllContacts();
            });

        </script>    
    </head>

    <!-- HTML and jQuery Mobile markup for the list and detail screens -->
    <body>    
    
        <!-- This div is the list "page" -->
        <div data-role="page" data-theme="b" id="listpage">                
            <div data-role="header" data-position="fixed">
                <h2>Contacts</h2>
                <a href='#' id="add" class='ui-btn-right' data-icon='add' 
                   data-theme="b">Add</a>
            </div>
            <div data-role="content" id="contactList">            
                <ul id="cList" data-filter="true" data-inset="true" 
                    data-role="listview" data-theme="c" data-dividertheme="b">
                </ul>
            </div>
        </div>
                
        <!-- This div is the detail "page" -->
        <div data-role="page" data-theme="b" id="detailpage">
            <div data-role="header" data-position="fixed">
                <a href='#listpage' id="back2ContactList" class='ui-btn-left' 
                   data-icon='arrow-l' data-direction="reverse" 
                   data-transition="flip">Back</a>
                <h1>Contact Details</h1>
            </div>
            <div data-role="content">
                <div data-role="fieldcontain">
                    <label for="fName">First Name:</label>
                    <input name="fName" id="fName" />
                </div>
                <div data-role="fieldcontain">
                    <label for="lName">Last Name:</label>
                    <input name="lName" id="lName" />
                </div>
                <div data-role="fieldcontain">
                    <label for="phone">Phone:</label>
                    <input name="phone" id="phone"/>
                </div>
                <div data-role="fieldcontain">
                    <label for="notes">Notes:</label>
                    <textarea name="notes" id="notes"/>
                </div>

                <h2 style="color:red" id="error"></h2>

                <input type="hidden" id="contactId" />
                <button id="save" data-role="button" data-icon="check" 
                    data-inline="true" data-theme="b" class="save">Save</button>
                <button id="delete" data-role="button" data-icon="delete" 
                    data-inline="true" class="destroy">Delete</button>
            </div>
        </div>
    </body>
</apex:page>

请注意,虽然所有四个远程 演示了对象操作,只有三个回调处理程序。

  • getAllContacts()加载联系人列表的调用 并为回调提供匿名函数。回调检查 错误,然后循环访问结果,将它们添加到页面中。retrieve()
  • 同样,调用加载单个 联系详情页面,结果也由 匿名函数。showDetailView()retrieve()
  • addUpdateContact()并处理添加, 更新和删除联系人。这两种方法都作为回调传递 功能。 不使用远程的结果 对象操作。它只检查错误,将它们记录到控制台,然后 调用刷新 页面。deleteContact()updateCallback()updateCallback()getAllContacts()

使用远程的最佳实践 对象

Visualforce 远程Objects 是一个有效的工具,用于快速向 Visualforce 页面添加简单的数据操作。远程 Objects 易于使用,具有不需要 Apex 代码即可实现读取的轻量级组件 以及将数据写入 Salesforce 服务。远程 但是,对象并不总是适合这项工作的工具,因此了解远程操作非常重要 对象的工作原理以及何时使用其他工具,例如 JavaScript 远程处理。

字段级安全性

远程 对象遵循组织的字段级别安全设置。创建时请记住这一点 使用远程的页面 对象。查看页面的用户无法访问的字段显示为空白。操作 如果修改字段数据 (, , 和 ) 在请求中包含不可访问的字段,则它们将失败并显示错误。create()update()upsert()

交易边界

)

相比之下,JavaScript 远程处理事务 边界位于 Apex 方法上。它 在一种方法中轻松创建发票和相关行项目记录,其中自动 Apex 交易可确保所有 记录是一起创建的,或者根本不创建。@RemoteAction

业务逻辑的适当放置和测试

仔细考虑将应用程序的业务逻辑放在何处,尤其是在 这很复杂。当您创建支持创建、编辑和 删除单个对象,如使用 Remote 的示例 使用 jQuery Mobile 的对象,业务逻辑是最小的。将此业务逻辑放在客户端,在 远程 对象和 JavaScript,可以完全合适。当您有更复杂的业务规则和 但是,从客户端层中删除该逻辑可能更有效,并且 在服务器端构建它。在决定将组织的业务逻辑放在何处时,请考虑以下几点。

  • 安全性和一致性:请记住,用户可能会在 在事务中,或者改变页面的 JavaScript 使用 Firebug 和其他 工具。远程 对象强制执行验证规则、触发器、共享规则、字段级安全性等 数据访问限制,但如果您将业务规则放在 JavaScript 而不是 Salesforce 中,这些规则可能会被中断, 更改或绕过。
  • 可测试性:服务器端的业务逻辑可以使用 Salesforce 提供的许多工具进行测试。为 因此,我们鼓励您将复杂的行为放在 Apex 中,并使用 Apex 测试框架 以验证它是否按预期工作。
  • 性能:如果您的处理需要将许多记录作为事务的一部分进行查看,但 不会在浏览器中显示它们,我们建议您避免将该数据发送到客户端,并且 而是在服务器上“本地”处理数据。想想你的页面需要做什么数据 它的工作,并确保您不会不必要地通过网络复制它。

处理复杂性

应用程序需要仔细管理复杂性。简单的联系人管理器或商店定位器 页面管理起来并不复杂,但许多业务流程都有。 远程 对象与 jQuery 和 AngularJS 等 JavaScript 框架搭配得很好,这些框架可以帮助 应用程序用户界面的复杂性。始终考虑将关注点分开 您的应用程序分为多个层,并尽可能使它们离散。这称为 “关注点分离”,这是一种经典的软件模式和最佳实践。

请考虑将数据完整性规则放在触发器和验证规则中。还要考虑 将业务流程规则封装在 Apex 代码中,您可以通过可与 JavaScript 远程处理或 SOAP 或 REST 服务一起使用的方法访问这些规则 您可以在任何地方使用。@RemoteAction

远程的替代品 对象

远程 Objects 是一个有用的工具,用于快速创建具有基本数据操作的页面。当工作那 您的页面需要做的比这更大,考虑到 Salesforce 提供了许多替代方案 闪电平台开发人员。

  • 标准 Visualforce 可用于实现广泛的应用程序功能。 Visualforce 在使用标准控制器时提供了许多自动功能,并且 使用您自己的 Apex 代码支持完全自定义的功能。
  • JavaScript 远程处理也适用于 第三方 JavaScript 框架,使您能够访问 顶点。
  • Salesforce Mobile 允许您使用 声明性工具,而不是代码。

仔细考虑您的页面或应用程序需要做什么,然后选择正确的 工作工具。有时该工具是远程的 对象,有时是别的东西。

远程 对象限制

Visualforce 虽然远程 对象不受某些资源限制的约束,它有自己的限制。远程 对象受以下限制的约束。

  • 远程 对象不是规避 Salesforce 服务限制的方法。远程 对象调用不受 API 限制的约束,但使用远程的 Visualforce 页面不受限制 对象受所有标准 Visualforce 限制的约束。
  • 在单个请求中最多可以检索 100 行。要显示更多行,请提交 使用查询的其他请求 参数。OFFSET
  • 远程 对象不支持字段。您无法检索 或设置 类型为 的对象字段的值。BlobBlob
  • 将属性设置为“远程” 对象组件禁用为那些 Remote 生成 JavaScript 对象。依赖于未呈现的 Remote 的任何页面功能 还应禁用对象。renderedfalse

在 Visualforce Pages 中使用 JavaScript

在 Visualforce 中使用 JavaScript 页面允许您访问各种现有的 JavaScript 功能,例如 JavaScript 库,以及自定义页面功能的其他方法。操作标记(如 和 )支持 Ajax 请求。

<apex:actionFunction><apex:actionSupport>

警告

通过在页面中包含 JavaScript,您可以引入 使用 Visualforce 时没有的跨浏览器和维护问题。在编写任何 JavaScript 之前, 您应该确保没有现有的 Visualforce 组件可以解决您的 问题。在 Visualforce 页面中包含 JavaScript 的最佳方法是将 JavaScript 放在静态资源中,然后调用 它从那里开始。例如

<apex:includeScript value="{!$Resource.MyJavascriptFile}"/>

然后,您可以使用该 JavaScript 文件中定义的函数 在您的页面中使用标签。

<script>

提示

在表达式中使用 JavaScript 时,需要 使用反斜杠 (\) 对引号进行转义。例如

onclick="{!IF(false, 'javascript_call(\"js_string_parameter\")', 'else case')}"

使用 $Component JavaScript 中的参考组件

使用全局变量简化 引用为 Visualforce 组件生成的 DOM ID,并减少对整个页面的一些依赖 结构。

$Component

每个 Visualforce 标签都有一个属性。一个标签的属性可以由另一个标签用来绑定这两个标签 一起。例如,标签的属性可以与标签的属性一起使用。、 和其他面向操作的 和 属性 组件还使用其他组件的属性值。idid<apex:outputLabel>for<apex:inputField>idreRenderstatus<apex:actionFunction><apex:actionSupport>id

除了用于将 Visualforce 组件绑定在一起外,此 ID 还用于构成文档的一部分 呈现页面时组件的对象模型 (DOM) ID。

要在 JavaScript 或其他支持 Web 的语言中引用 Visualforce 组件,您必须 为该组件的属性指定一个值。DOM ID 由组合构成 的属性 组件和属性 包含该元素的所有组件。ididid

组件访问示例

以下示例将 DOM ID 用于标记。该页面包含两个面板:第一个面板 持有一个触发 DOM 事件的复选框,第二个复选框包含一些文本 为响应事件而更改。<apex:outputPanel>

页面顶部包括 JavaScript 包含在 HTML 标记中。它将触发事件的元素作为参数 () 和包含要影响文本的目标面板的 DOM ID()。<script>inputtextid

<apex:page id="thePage">
    <!-- A simple function for changing the font. -->
    <script>
        function changeFont(input, textid) {
            if(input.checked) {
                document.getElementById(textid).style.fontWeight = "bold";
            }
            else {
                document.getElementById(textid).style.fontWeight = "normal";
            }
        }
    </script>

    <!-- This outputPanel calls the function, passing in the
         checkbox itself, and the DOM ID of the target component. -->
    <apex:outputPanel layout="block">
        <label for="checkbox">Click this box to change text font:</label>
        <input id="checkbox" type="checkbox"
            onclick="changeFont(this,'{!$Component.thePanel}');"/>
    </apex:outputPanel>

    <!-- This outputPanel is the target, and contains 
         text that will be changed. -->
    <apex:outputPanel id="thePanel" layout="block">
        Change my font weight!
    </apex:outputPanel>
</apex:page>

该表达式用于获取 组件生成的 HTML 元素的 DOM ID。{!$Component.thePanel}<apex:outputPanel id=”thePanel”>

将 JavaScript 库与 Visualforce 结合使用

您可以在 Visualforce 页面中包含 JavaScript 库,以利用 这些库提供的功能。包含 JavaScript 库的最佳方式是 创建静态资源,然后通过向页面添加组件来包含库。

<apex:includeScript>例如,如果您使用的是 jQuery (https://js.foundation/),请从名为 jquery 的库创建静态资源,然后在类似 这:

<apex:page>
    <apex:includeScript value="{!$Resource.jquery}"/>
</apex:page>

你 然后,可以通过添加 to 调用在页面中使用它 函数。

<script>如果您在 Visualforce 页面中使用 JavaScript 库,并且该库 定义为特殊字符,您需要 修改 JavaScript 以覆盖此用法。例如,使用 jQuery,您可以覆盖 通过使用函数的定义。

$$jQuery.noConflict()

<apex:page >
<apex:includeScript value="{!$Resource.jquery}"/>
<html>
<head>
  <script>
    jQuery.noConflict();
    
    jQuery(document).ready(function() {    
        jQuery("a").click(function() {
          alert("Hello world, part 2!");
        });
    });
  </script>
</head>
...
</apex:page>

注意

  • 支持并鼓励使用第三方 JavaScript 库和框架 由 Salesforce 提供。但是,Salesforce 无法帮助您调试 JavaScript 代码,除非它 特别是与Salesforce功能有关。
  • 不要在使用 Chatter 组件、 、 或 .<apex:enhancedList><knowledge:articleCaseToolbar><knowledge:articleRendererToolbar>

适用于 Apex 的 JavaScript 远程处理 控制器

在 Visualforce 中使用 JavaScript 远程处理来调用 来自 JavaScript 的 Apex 控制器。创建具有复杂、动态行为的页面,但事实并非如此 可以使用标准的 Visualforce AJAX 组件。使用 JavaScript 远程处理实现的功能需要三个元素:

  • 您添加到 Visualforce 页面的远程方法调用,写在 JavaScript的。
  • Apex 控制器类中的远程方法定义。此方法 定义是用 Apex 编写的,但与 正常操作方法。
  • 您添加到或包含在 Visualforce 中的响应处理程序回调函数 页面,用 JavaScript 编写。

什么是 JavaScript Remoting?

JavaScript 远程处理是一种工具,可以 前端开发人员可以使用从 Visualforce 页面直接向 Apex 发出 AJAX 请求 控制器。JavaScript 远程处理允许您 通过将页面与控制器分离来运行异步操作,并在 页面,而无需重新加载整个页面。

此外,JavaScript 远程处理还可以提供帮助 缓解视图状态问题,同时仍在用户查看页面的上下文中执行。 JavaScript 远程处理是最有效的方法 调用控制器并从页面传入数据,因为您可以确保 每次进行调用时仅传递所需的数据。

何时使用 JavaScript 远程处理

JavaScript 远程处理是 针对移动网页和使用第三方 JavaScript 的网页进行了优化 图书馆。它支持动态的交互式页面,感觉比 传统 Visualforce 页面。

JavaScript 远程处理是 标准 Visualforce AJAX 组件和 Visualforce Remote 对象。它 提供了一种更惯用的方式,通过 JavaScript 与 Lightning 平台进行交互。JavaScript 远程处理允许您使用熟悉的 JavaScript 实践和结构,并利用其他 JavaScript 框架和工具 对于前端开发人员来说,套件更轻松。远程处理可创建响应速度更快的理想体验 对于移动页面或您的用例需要最高效率的任何其他页面,以及 性能。由于它是异步的,因此只能加载初始页面和 需要显示页面,然后延迟加载页面上可能未使用的其他数据 马上。您甚至可以使用此方法为用户的页面或视图预加载数据 尚未访问。

虽然 JavaScript 远程处理 可以提供高效、响应迅速且优化的用户体验,但事实并非如此 无限制。开发使用它的页面可能需要额外的时间,而您 需要改变你开发和思考页面流程的方式。因为你 未使用表单,并且没有与请求关联的视图状态,您有 在客户端自行管理页面的状态。另一方面 没有什么可以阻止您将 JavaScript 远程处理与 标准 Visualforce MVC 设计范式。一如既往,把你试图解决的问题放在首位 在确定您的设计时。JavaScript 远程处理是众多远程处理之一 可用的工具。

比较 JavaScript Remoting 和 <apex:actionFunction>

该组件还允许 通过 JavaScript 调用控制器操作方法。

<apex:actionFunction>

一般来说,更容易使用 并且需要更少的代码,而 JavaScript 远程处理提供了更大的灵活性。<apex:actionFunction>以下是两者之间的一些具体区别。

  • 标签:<apex:actionFunction>
    • 允许您指定重新渲染目标
    • 提交表格
    • 不需要你编写任何 JavaScript
  • JavaScript 远程处理:
    • 允许您传递参数
    • 提供回调
    • 需要你编写一些 JavaScript

比较 JavaScript 远程处理和远程处理 对象

JavaScript 远程处理和远程 对象提供类似的功能,两者都是用于创建动态响应式页面的有用工具。 它们有一些重要的区别,在选择哪个之前,您应该考虑这些差异 用。

一般来说,远程 Objects 非常适合只需要执行简单的 Create-Read-Update-Delete 或 “CRUD”,对象访问。JavaScript Remoting 更适合 到访问更高级别服务器操作的页面。远程 Objects 可让您快速启动和运行,而无需太多仪式,而 JavaScript Remoting 适合 更复杂的应用程序,需要一些前期 API 样式的设计工作。视觉力 远程 对象:

  • 使基本的“CRUD”对象访问变得容易
  • 不需要任何 Apex 代码
  • 支持最小的服务器端应用程序逻辑
  • 不提供自动关系遍历;您必须查找相关对象 你自己

JavaScript 远程处理:

  • 需要 JavaScript 和 Apex 代码
  • 支持复杂的服务器端应用程序逻辑
  • 更好地处理复杂的对象关系
  • 更有效地(甚至)使用网络连接

将 JavaScript 远程处理添加到 Visualforce 页面

要在 Visualforce 页面中使用 JavaScript 远程处理,请将请求添加为 JavaScript 函数调用。将 Apex 类作为自定义控制器或控制器扩展添加到 你 页。

<apex:page controller="MyController" extension="MyExtension">

警告

添加控制器或控制器扩展将授予对该 Apex 类中所有方法的访问权限,即使这些方法 页面中不使用方法。任何可以查看该页面的人都可以执行所有方法,并向 控制器。@RemoteAction@RemoteAction然后,将请求添加为 JavaScript 函数调用。一个简单的 JavaScript 远程调用采用以下方法 形式。

[namespace.]MyController.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);
元素描述
namespace控制器类的命名空间。如果出现以下情况,则 namespace 元素是必需的 您的组织定义了一个命名空间,或者该类是否来自已安装的 包。
MyController,MyExtensionApex 控制器或扩展的名称。
method要调用的 Apex 方法的名称。
parameters方法采用的参数的逗号分隔列表。
callbackFunction处理来自 控制器。还可以以内联方式声明匿名函数。 接收方法的状态 调用和结果作为参数。callbackFunction
configuration配置远程呼叫和响应的处理。使用此元素可以 更改远程处理调用的行为,例如是否转义 Apex 方法的响应。

远程方法调用同步执行,但不会等待响应 返回。当响应返回时,回调函数会异步处理它。有关详细信息,请参阅处理远程响应。

配置 JavaScript 远程处理请求

通过在以下情况下为对象提供配置设置来配置远程处理请求 声明远程处理请求。例如,默认配置参数如下所示 这:

{ buffer: true, escape: true, timeout: 30000 }

这些 配置参数没有顺序,您可以省略不想更改的参数 从默认值。JavaScript 远程处理支持以下功能 配置参数:

名字数据类型描述
缓冲区布尔是否将时间上彼此靠近执行的请求分组到单个请求中。 缺省值为 。trueJavaScript 远程处理 优化在时间上彼此接近执行的请求,并将调用分组到 单个请求。这种缓冲提高了整体请求和响应的效率 循环,但有时确保所有请求都执行很有用 独立地。
布尔是否转义 Apex 方法的响应。缺省值为 。true
超时整数请求的超时时间(以毫秒为单位)。默认值为 30,000 (30 秒)。最大值为 120,000 (120 秒,或 2 分钟)。

还可以为页面发出的所有请求配置请求超时,方法是将 使用 Visualforce 远程处理超时 对象:

<script type="text/javascript">

    Visualforce.remoting.timeout = 120000; // Set timeout at page level

    function getRemoteAccount() {
        var accountName = document.getElementById('acctSearch').value;

        // This remoting call will use the page's timeout value
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.AccountRemoter.getAccount}',
            accountName, 
            handleResult
        );
    }

    function handleResult(result, event) { ... }
</script>

通过在 该请求的配置对象,如上所述。

命名空间和 JavaScript 远程处理

您可以使用全局 自动解析远程操作的正确命名空间(如果有)。这使得它 更易于使用命名空间,尤其是对于对方法进行远程处理调用的页面 以包装形式提供。

$RemoteAction若要使用此工具,必须显式调用 JavaScript 远程处理。 执行此操作的模式 是:

Visualforce.remoting.Manager.invokeAction(
    'fully_qualified_remote_action', 
     invocation_parameters
);

完全限定的远程操作是一个字符串,表示 远程操作方法的完整路径,包括命名空间、基类等:. 在表达式中自动使用 解析命名空间,例如 .namespace[.BaseClass][.ContainingClass].ConcreteClass.Method$RemoteAction{!$RemoteAction.MyController.getAccount}调用参数是用于执行 远程方法调用,并且是用于进行标准远程处理调用的相同参数:

  • 要发送到方法的参数, 如果有的话。@RemoteAction
  • 回调函数,用于处理返回的结果。
  • 调用的配置详细信息(如果有)。

例如,您可以定义一个远程调用来检索 帐户喜欢 这:

<script type="text/javascript">
function getRemoteAccount() {
    var accountName = document.getElementById('acctSearch').value;

    Visualforce.remoting.Manager.invokeAction(
        '{!$RemoteAction.MyController.getAccount}', 
        accountName, 
        function(result, event){
            if (event.status) {
                document.getElementById('acctId').innerHTML = result.Id
                document.getElementById('acctName').innerHTML = result.Name;
            } else if (event.type === 'exception') {
                document.getElementById("responseErrors").innerHTML = event.message;
            } else {
                document.getElementById("responseErrors").innerHTML = event.message;
            }
        }, 
        {escape: true}
    );
}
</script>

这 JavaScript 远程处理调用不需要知道命名空间的详细信息,其中 控制器是定义的,无论是在你自己的命名空间中,还是在 已安装的软件包。它还可以处理您的组织没有 命名空间定义。

注意

调用时遇到的错误是 仅在 JavaScript 控制台中报告。例如,如果在多个命名空间中找到匹配的方法,则返回第一个匹配方法,并将警告记录到 JavaScript 控制台。如果未找到匹配的控制器或操作,则调用将失败,并且 错误将记录到 JavaScript 控制台。invokeAction$RemoteAction@RemoteAction

JavaScript 远程处理的 OAuth 2.0 身份验证

您可以使用 OAuth 2.0 对 JavaScript 远程处理请求进行身份验证,而不是要求 标准的用户名和密码登录过程。OAuth 允许跨应用程序和 使用标准无法安全地完成的跨组织集成 认证。

使用 OAuth 的 Visualforce 页面 身份验证在页面级别对其进行配置,并将 OAuth 用于所有 JavaScript 远程处理请求。以外 配置,使用 JavaScript 远程处理是 完全一样。为 JavaScript 远程处理配置 OAuth 从 Visualforce 页面获取 以后 形式:

<script type="text/javascript">

    Visualforce.remoting.oauthAccessToken = <access_token>;

    // ...
</script>

设置后,所有 JavaScript 远程处理请求都使用 OAuth。其余的 的 JavaScript 远程处理代码可以保留 相同。

oauthAccessToken

oauthAccessToken是 OAuth 身份验证令牌 由页面代码获取。获取和更新访问令牌是简单的 OAuth, 加一个。JavaScript 远程处理 OAuth 身份验证请求“VisualForce”范围,因此必须使用此令牌或 包含它的范围,包括“web”或“full”。在 OAuth 请求中设置(或“web”或“full”)。scope=visualforce

有关获取访问令牌以及将 OAuth 与 Lightning 平台结合使用的信息,请参阅《对远程访问应用程序进行身份验证》和《深入了解 OAuth 2.0》 Salesforce 联机帮助中的 Salesforce。

在 Apex 中声明远程方法

您几乎可以将任何 Apex 方法作为 JavaScript 远程处理远程操作调用。为此, 方法需要遵循一些简单的规则。在控制器中,Apex 方法声明前面有 注释喜欢 这:

@RemoteAction

@RemoteAction
global static String getItemId(String objectName) { ... }

Apex 方法必须是 和 或 。

@RemoteActionstaticglobalpublic

您的方法可以采用 Apex 基元、集合、类型化 和泛型 sObjects,以及用户定义的 Apex 类和接口作为参数。通用 sObjects 必须具有 ID 或 sobjectType 值才能标识实际类型。接口参数必须 有一个 apexType 来标识实际类型。

您的方法可以返回 Apex 基元、sObjects、 集合、用户定义的 Apex 类和枚举、 、 、 或 。SaveResultUpsertResultDeleteResultSelectOptionPageReference用于 JavaScript 远程处理的方法必须由名称和编号唯一标识 参数;无法重载。例如,使用上述方法,您不能同时拥有 一种方法。相反 声明具有不同名称的多个方法:

getItemId(Integer productNumber)

  • getItemIdFromName(String objectName)
  • getItemIdFromProductNumber(Integer productNumber)

@RemoteAction方法的范围和可见性

Apex 方法必须是 和 或 。@RemoteActionstaticglobalpublic不要使用全局公开的远程操作来执行敏感操作或公开 非公开数据。 远程操作只能调用 其他方法。您不能使用公共遥控器 组件或作用域中的操作。范围升级会导致编译器错误,或者 对于在运行时解析的引用,则为运行时失败。下表描述了这些限制。

Globalglobalglobalglobal

@RemoteAction范围Visualforce 页面非全局组件全局组件iframe跨包访问
全局远程方法允许允许允许允许允许
公共远程方法允许允许错误错误包必须共享命名空间。方法必须具有注释。@namespaceAccessible

注意

如果 @RemoteAction 方法位于托管包中并由 Visualforce Remoting 使用,则它必须 如果使用用户配置文件或权限集访问权限,则具有全局可见性。什么时候 远程操作通过标记访问,它们通过组件、或标记间接包含。远程方法的作用域是 结转到顶级容器(包含层次结构中的顶级项)中, 必须遵守范围升级规则。

<apex:include>,<apex:composition>

顶级容器
@RemoteAction访问自Visualforce 页面非全局组件全局组件iframe
全局组件允许允许允许允许
非全局组件允许允许仅当非全局组件不包括公共远程方法时才允许。仅当非全局组件不包括公共远程方法时才允许。
<apex:include> <apex:composition>允许在同一命名空间中;如果命名空间不同且包含的命名空间不同,则出错 Page 或其子层次结构包含公共远程方法。不适用不适用错误

远程方法和继承

您可以在 Apex 控制器上调用继承方法的远程操作。什么时候 查找或调用方法时,Visualforce 会检查页面 控制器的继承层次结构,并在控制器的祖先类中查找方法。

@RemoteAction@RemoteAction下面是一个演示此功能的示例。以下 Apex 类构成一个 三层继承 等级制度:

global with sharing class ChildRemoteController 
    extends ParentRemoteController { }
global virtual with sharing class ParentRemoteController 
    extends GrandparentRemoteController { }

global virtual with sharing class GrandparentRemoteController {
    @RemoteAction
    global static String sayHello(String helloTo) {
        return 'Hello ' + helloTo + ' from the Grandparent.';
    }
}

此 Visualforce 页面只需调用 遥控器 行动。

sayHello

<apex:page controller="ChildRemoteController" >
    <script type="text/javascript">
        function sayHello(helloTo) {
            ChildRemoteController.sayHello(helloTo, function(result, event){
                if(event.status) {
                    document.getElementById("result").innerHTML = result;
                }
            });
        }
    </script>

    <button onclick="sayHello('Jude');">Say Hello</button><br/>
    <div id="result">[Results]</div>
    
</apex:page>

这 类中不存在 Remote 方法。相反,它继承自 .

ChildRemoteControllerGrandparentRemoteController

使用接口参数声明远程方法

您可以使用以下命令声明方法 接口参数和返回类型,而不是局限于具体类。这,为了 例如,允许包提供程序打包远程方法和关联的接口,其中 订阅者组织可以从 Visualforce 页面调用,并传入自己的类 实现打包接口。

@RemoteAction这是一个简短的 例:

public class RemoteController {
    public interface MyInterface { String getMyString(); }
    public class MyClass implements MyInterface { 
        private String myString; 
        public String getMyString() { return myString; }
        public void setMyString(String s) { myString = s; }
    }
    
    @RemoteAction
    public static MyInterface setMessage(MyInterface i) {
        MyClass myC = new MyClass();
        myC.setMyString('MyClassified says "' + i.getMyString() + '".');
        return myC;
    }
}

从 JavaScript 远程处理调用发送到声明接口参数的对象必须包含一个值,该值必须是指向 具体类,即 . 例如,要对上述内容进行 JavaScript 远程处理调用 控制器:

@RemoteActionapexTypenamespace[.BaseClass][.ContainingClass].ConcreteClass

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.RemoteController.setMessage}',
    {'apexType':'thenamespace.RemoteController.MyClass', 'myString':'Lumos!'}, 
    handleResult
);

如果类定义位于您的组织内,则可以 简化远程处理调用,并使用默认命名空间:

c

RemoteController.setMessage(
    {'apexType':'c.RemoteController.MyClass', 'myString':'Lumos!'}, 
    handleResult
);

处理远程响应

异步处理对远程方法调用的响应 遥控器 方法回调 功能。您的回调函数接收 这 以下参数:

  • event表示 远程呼叫
  • result远程 Apex 返回的对象 方法。

你 函数可以根据数据更新页面上的信息和用户界面元素 返回。该对象提供允许您执行操作的值 在远程调用成功或失败时。

event

描述
event.statustrue成功时,错误时。false
event.type响应的类型:for a 调用成功,如果远程 方法抛出异常。rpcexception
event.message包含返回的任何错误消息。
event.where这 顶点 叠 trace 是 引用 如果它是由远程生成的 方法。

返回的 Apex 基元数据类型,例如 字符串或数字 – 转换为其 JavaScript 等效项。顶点对象 返回的对象将转换为 JavaScript 对象,而集合将转换为 JavaScript 数组。请记住,JavaScript 区分大小写,因此 、 和 被视为不同的字段。resultidIdID

作为 JavaScript 远程调用的一部分,如果 Apex 方法响应包含对 相同 对象。 这 对象是 不 在返回的 JavaScript 对象中重复。 相反 呈现的 JavaScript 对象包含 一个 参考 到同一个对象。一个示例是 Apex 方法,它返回一个包含相同 对象两次。

了解日期和时间序列化

日期和时间值在通过 Visualforce 传递时被序列化为纪元时间 远程处理。

Date, , 从函数返回的对象被序列化为长整数。DateTimeTimeRemoteAction

序列化日期时间值

[{
    "statusCode": 200,
    "type": "rpc",
    "tid": 8,
    "ref": false,
    "action": "DateTestController",
    "method": "add",
    "result": 1432047600000
}]

调试 JavaScript 远程处理

调试使用 JavaScript 远程处理的页面需要调试 Visualforce、Apex 和 JavaScript的。

重要

使用 JavaScript 远程处理时,在开发过程中保持 JavaScript 控制台处于打开状态。错误和 JavaScript 远程处理遇到的异常将记录到 JavaScript 控制台(如果已启用),否则会以静默方式忽略。

当方法引发异常时 由于编程错误或其他故障,Apex 堆栈跟踪将返回到 对象中的浏览器。检查 堆栈跟踪在 JavaScript 调试器控制台中,或将其用于 响应回调函数。@RemoteActionevent下面是一个回调函数,当有 例外。

<script type="text/javascript">
function getRemoteAccount() {
    var accountName = document.getElementById('acctSearch').value;

    Visualforce.remoting.Manager.invokeAction(
        '{!$RemoteAction.MyController.getAccount}', 
        accountName, 
        function(result, event){
            if (event.status) {
                document.getElementById('acctId').innerHTML = result.Id
                document.getElementById('acctName').innerHTML = result.Name;
            } else if (event.type === 'exception') {
                document.getElementById("responseErrors").innerHTML = 
                    event.message + "<br/>\n<pre>" + event.where + "</pre>";
            } else {
                document.getElementById("responseErrors").innerHTML = event.message;
            }
        }
    );
}
</script>

JavaScript 远程处理限制和 考虑

尽管 JavaScript 远程处理不受某些资源限制的约束,但它还有其他资源限制 限制。

JavaScript 远程处理无法避免 Salesforce 服务限制。JavaScript 远程处理调用不是 受 API 限制的约束,但使用 JavaScript 远程处理的 Visualforce 页面受所有 标准 Visualforce 限制。

默认情况下,远程呼叫响应必须在30 秒,之后调用超时。如果您的请求需要更长的时间才能完成, 配置更长的超时时间,最多 120 秒。

这 请求(包括标头)的最大大小为 4 MB。远程呼叫响应最大大小为

15兆字节.如果你的 JavaScript 远程处理代码超出此限制,您有多种选择。

  • 减小每个请求的响应大小。仅返回以下数据 必填。
  • 将大数据检索分解为返回较小块的请求。
  • 若要减小批处理大小,请更频繁地发出批处理请求。
  • 使用非批处理请求。在远程处理请求配置块中设置。{ buffer: false }

Salesforce 会记录某些 JavaScript 远程处理调用中的错误消息。您可以防止个人 由于未在错误消息中包含客户数据而记录的信息。相反,抓住 异常并记录完整消息。然后向 Visualforce 返回一条用户友好的消息 页。

发出 Visualforce Remoting 请求时,将使用 “会话设置设置”页面中的组织范围超时值。超时未刷新 后续请求。如果不需要超时,则可以使用用户配置文件访问权限或 权限集访问权限。

JavaScript 远程处理 例

下面是一个基本示例,演示如何在 Visualforce 页面中使用 JavaScript 远程处理。首先,创建一个名为 Apex 的控制器:

AccountRemoter

global with sharing class AccountRemoter {

    public String accountName { get; set; }
    public static Account account { get; set; }
    public AccountRemoter() { } // empty constructor
    
    @RemoteAction
    global static Account getAccount(String accountName) {
        account = [SELECT Id, Name, Phone, Type, NumberOfEmployees 
                   FROM Account WHERE Name = :accountName];
        return account;
    }
}

其他 比注释,这看起来 与任何其他控制器定义一样。

@RemoteAction要使用此远程方法,请创建一个如下所示的 Visualforce 页面 这:

<apex:page controller="AccountRemoter">
    <script type="text/javascript">
    function getRemoteAccount() {
        var accountName = document.getElementById('acctSearch').value;

        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.AccountRemoter.getAccount}',
            accountName, 
            function(result, event){
                if (event.status) {
                    // Get DOM IDs for HTML and Visualforce elements like this
                    document.getElementById('remoteAcctId').innerHTML = result.Id
                    document.getElementById(
                        "{!$Component.block.blockSection.secondItem.acctNumEmployees}"
                        ).innerHTML = result.NumberOfEmployees;
                } else if (event.type === 'exception') {
                    document.getElementById("responseErrors").innerHTML = 
                        event.message + "<br/>\n<pre>" + event.where + "</pre>";
                } else {
                    document.getElementById("responseErrors").innerHTML = event.message;
                }
            }, 
            {escape: true}
        );
    }
    </script>

    <input id="acctSearch" type="text"/>
    <button onclick="getRemoteAccount()">Get Account</button>
    <div id="responseErrors"></div>

    <apex:pageBlock id="block">
        <apex:pageBlockSection id="blockSection" columns="2">
            <apex:pageBlockSectionItem id="firstItem">
                <span id="remoteAcctId"/>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem id="secondItem">
                <apex:outputText id="acctNumEmployees"/>
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:page>

通知 关于此标记的以下内容:

  • JavaScript 使用显式远程处理调用,并利用全局来解析正确的 远程操作方法的命名空间。invokeAction$RemoteAction
  • 仅当调用成功时,该变量才可用。错误 示例所示的处理特意简单,并打印错误 分别来自 和 值的消息和堆栈跟踪。我们鼓励你实现更可靠的 方法调用未调用的请求的替代逻辑 成功。event.statustrueevent.messageevent.where
  • 该变量表示 从 Apex 方法返回的对象。resultgetAccount
  • 访问纯 HTML 元素的 DOM ID 很简单,只需使用 项目。
  • Visualforce 的 DOM ID 组件是动态生成的,以确保 ID 是唯一的。代码 以上使用了使用$Component 从 JavaScript 引用组件,通过全局变量访问组件来检索组件的 ID。$Component

常见 Visualforce JavaScript 远程处理 API 验证 错误

了解如何更正对 Visualforce JavaScript Remoting 的常见无效调用 应用程序代码中的 API。

以下错误消息适用于 Apex 控制器验证:

  • Apex 命名空间 “” 中的具体实现 “” 接口 “” 没有权限 要使用。无法实例化 Apex 对象。CLASSNAMESPACEMETHOD_PARAMETER
    • 反序列化对象没有权限 在关联的命名空间中使用。查看 Apex 类删除不兼容的注释或 满足必须满足的缺失安全要求 满意。
  • 命名空间 “” 中的具体实现 “” 没有 实现 Apex 接口 “.” 无法实例化 Apex 对象。CLASSNAMESPACEMETHOD_PARAMETER
    • 确保反序列化的数据类型 Apex 对象与输入参数类型相同 Apex 控制器方法所期望的。

这些错误消息适用于 CSRF 令牌验证:

  • 远程处理请求的授权无效。请稍后再试。
    • 检查CSRF令牌是否成功发行。 如果需要,请请求另一个令牌。
  • 远程处理请求授权已过期。刷新页面,然后重试。
    • CSRF 令牌已过期。请求另一个 令牌,然后对请求使用新令牌。
  • 无法授权远程处理请求。刷新页面并 再试一次。
    • CSRF 令牌期间发生意外故障 验证。查看控制台日志和服务器 日志以识别故障点并确定 如何更正问题。
  • 远程处理请求授权对请求的方法无效。
    • 确认 CSRF 令牌有效。然后 确保 CSRF 令牌用于 与发布验证的方法相同。
  • 您没有执行 您请求的操作。请联系所有者 记录或您的管理员(如果需要访问)。
    • 通过令牌或授权进行访问 不成功,但没有任何信息 关于授权失败的原因。检查 控制台日志和服务器日志,用于提供有关 如何更正问题。
  • 授权远程处理请求时出错。刷新 页面,然后重试。
    • 验证期间发生不明问题。 确认您的访问权限有效,然后检查 控制台日志和服务器日志,用于提供有关 如何更正问题。

已知的 Visualforce Mobile 问题

Salesforce 发布已知问题以增强信任并支持客户成功。

Salesforce 客户支持和工程部门自行决定发布已知问题 根据客户报告的数量、问题的严重性以及 解决方法。如果您遇到的问题未列出,则它可能不符合以下条件 在已知问题网站上发布。并非所有已知的 bug 都会发布;通常 bug 会得到解决 快速或不影响客户。

  • 访问或权限问题 访问和权限问题
    会影响用户在 Salesforce 应用程序中看到的页面和记录。
  • 设备传感器问题 设备传感器问题包括移动设备的摄像头、麦克风和地理位置问题
  • 输入问题
    输入问题会影响用户使用 Salesforce 移动应用程序中的输入字段和选择器输入信息的方式。
  • 加载和性能问题 加载和性能问题
    会影响 Salesforce 移动应用程序的响应速度和加载速度。
  • 导航问题
    这些问题会阻止用户导航到 Salesforce 移动应用程序中的某些页面。
  • 网络问题 网络问题
    会影响 Salesforce 移动应用程序的连接。
  • Salesforce Classic 与 Lightning Experience 问题
    这些问题是由 Salesforce Classic 和 Lightning Experience 之间的切换引起的。
  • 更新记录问题
    这些问题会影响尝试在 Salesforce 移动应用程序中更新记录的用户。
  • 用户界面问题
    这些问题会影响 Salesforce 移动应用程序的用户界面。

访问或权限问题

访问和权限问题会影响用户在 Salesforce 应用程序。

问题溶液
如果在 用户配置文件级别,用户无法访问 Visualforce 内容。而不是 Visualforce 内容, 用户会看到错误消息“您无法查看此页面,因为您没有 权限,或者因为该页面在移动设备上不受支持。本期独有 适用于 iOS 的 Salesforce 和适用于 Android 的 Salesforce。在用户配置文件上禁用高确定性,然后重新登录。在以下位置实现高保证 Salesforce 连接的应用程序级别,而不是用户配置文件级别,以继续强制执行 多重身份验证。
Experience Cloud 站点用户无法在转换操作中访问 Visualforce 覆盖 对于应用程序中的潜在客户。相反,他们会看到错误消息“您也无法查看此页面 因为您没有权限,或者因为该网页在移动设备上不受支持 设备。创建一个单独的 Visualforce 操作,用于使用相同的 Visualforce 转换潜在客户 页。

设备传感器问题

设备传感器问题包括移动设备的摄像头、麦克风和 地理位置。

问题溶液
移动设备的相机在输入字段的子浏览器窗口中不起作用。 相反,用户会看到黑屏。这个问题可能是 Apple 的错误,用于儿童浏览器 窗户。此问题仅适用于 iOS 版 Salesforce。SFSafariViewController在 Safari 移动浏览器中打开 Salesforce,方法是点击 Safari 中的图标 Salesforce 移动应用程序中子浏览器窗口的右下角。

输入问题

输入问题会影响用户如何使用 Salesforce 移动应用程序。

问题溶液
将显示使用 URL 内容源的列表按钮访问的 Visualforce 页面 输入选择器的样式不正确。例如,输入日期字段显示一个日历,其中 白底白枣。此问题仅适用于 Android 版 Salesforce。将列表视图 URL 按钮转换为具有 Visualforce 页面内容的列表视图按钮 源、Visualforce 选项卡或 Visualforce 操作。
如果用户点击输入字段,iOS 本机输入控件将保留在屏幕上 然后,标题后退箭头仍然激活这些控件。输入控件还可以 导航到其他页面时意外重新出现。此问题是 Salesforce 独有的 适用于 iOS。在 Salesforce 移动版中导航之前,请确保输入控件已关闭 应用程序。
在字段中长按后,Visualforce 输入字段冻结或不允许输入。 用户可以长按复制和粘贴、进行选择或更改光标位置。这 问题仅适用于 iOS 版 Salesforce。将以下 JavaScript 行添加到 Visualforce 页面的底部:window.onkeydown=function(){window.focus();}

加载和性能问题

加载和性能问题会影响 Salesforce 移动应用程序的响应速度,以及 加载速度有多快。

问题溶液
如果将 Visualforce 页面或 Lightning 选项卡设置为登录页面,则可能存在页面 加载错误和性能下降。错误消息“我们在加载时陷入了循环 该页面“或”加载此页面需要一段时间。您可以继续等待或重试” 出现。选择一个标准选项卡作为登录页面。
打开多个文件会导致 iOS 版 Salesforce 冻结。在 Safari 中使用 Salesforce 移动 Web。

导航问题

这些问题会阻止用户导航到 Salesforce 移动版中的某些页面 应用程序。

问题溶液
Visualforce 选项卡会在用户离开后加载登录页面。在切换应用或选择其他选项卡之前允许页面完全加载,或者选择 再次打开 Visualforce 页面选项卡。
用户在切换后返回 Canvas 页面时可能会看到空白页面 应用程序。重新加载 Canvas 应用。
如果发布者操作使用该函数对文件记录进行导航调用,则文件预览 窗口可能会在显示文件之前关闭。出现此问题的原因是该方法在导航调用之前触发 文件记录。此问题仅适用于 iOS 版 Salesforce。sforce.one.navigateToSObjectpublisher.close不要使用之前的通话 导航到文件记录。用户必须在以下时间后手动关闭发布者操作窗口 他们已完成对文件记录的处理。publisher.close
当用户强制退出应用时,记录类型选择页可能会显示不正确 从记录类型选择页面的正确版本。本期独有 适用于 iOS 的 Salesforce。导航回对象主页,然后再次点击新建。如果 问题仍然存在,请清除应用程序缓存或注销以重置行为。
使用导航到 iPad 上的对象主页。出现错误后,没有其他导航调用可以处理 页面,直到设备缓存被清除。清除缓存。
使用导航库从 Visualforce 页面按 ID 导航到笔记记录,然后点击取消或 保存会导致循环。如果此导航方法是从 Visualforce 操作调用的 记录详细信息页面,则“取消”、“保存”和“返回”按钮可能会返回到空白记录详细信息页面。sforce.one没有直接的解决方法。强制退出并重新启动应用程序。
视图状态 POST 请求存储在 Salesforce 移动应用程序导航历史记录中。 如果用户提交视图状态表单,导航到另一个 Visualforce 页面,然后单击 后退按钮,将再次处理 POST 请求。此问题会在 适用于 iOS 的 Salesforce,并导致浏览器应用程序中出现错误。

网络问题

网络问题会影响 Salesforce 移动应用程序的连接。

问题溶液
网络连接错误消息“请检查您的网络连接,然后重试” 当网络处于活动状态时,显示在 Visualforce 页面上。本期独有 适用于 iOS 的 Salesforce。关闭组织范围的设置“在 Salesforce 中启用缓存”。

Salesforce Classic 与 Lightning Experience 问题

这些问题是由 Salesforce Classic 和 Lightning 之间的切换引起的 经验。

问题溶液
UI 检查错误地返回,而不是在用户使用 移动设备上的经典 UI。Visualforce 全局和 Apex 类命令会出现此问题。Theme4tTheme3$User.UIThemeDisplayedUserInfo.getUiThemeDisplayed通过验证 JavaScript 对象是否可用来检查用户的当前 UI;此对象在 经典 UI。sforce.one

更新记录问题

这些问题会影响尝试在 Salesforce 移动版中更新记录的用户 应用程序。

问题溶液
用户无法在更新后立即保存 Salesforce 记录。相反 他们会看到错误消息“记录在编辑期间被 [当前用户] 修改 会期。记下您输入的数据,然后重新加载记录并输入您的更新 再次”。避免在立即调用 标准编辑页面。否则,用户必须等待 30 秒,直到缓存期过去, 在他们能够编辑记录之前。
编辑未读线索时会触发冲突检测。错误消息“这 记录在编辑会话期间被(用户更新潜在客户)修改。记下 您输入的数据,然后重新加载记录并再次输入您的更新”。打开记录并使用记录详细信息中的编辑按钮进行更改 页。
当 可选参数在 以下命令:.错误消息“查看此页上的错误。记录类型 ID: 此值对用户无效:可能会显示“[用户名]”。recordTypeIDrecordTypeIDsforce.one.createRecord(​entityName​[, recordTypeId]);修改对 要传入参数的 Visualforce 页面。sforce.one.createRecordrecordTypeId

用户界面问题

这些问题会影响 Salesforce 移动应用程序的用户界面。

问题溶液
Salesforce 移动应用程序标准后退导航按钮仅在点击时才会响应 两次。当编程调用(如 和)首先用于导航回一个页面,然后导航回用户时,会出现此问题 尝试使用标准的“后退”按钮返回另一页。本期独有 适用于 iOS 的 Salesforce。window.history.back()sforce.one.back()如果您的实现有多个页面,请将其设计为仅使用标准背面 按钮或仅编程调用。
当用户从记录详细信息页面中嵌入的 Visualforce 页面单击返回时, 页面不滚动。单击“返回”后等待几秒钟,离开记录并返回,或者 将设备从横向旋转到纵向。
某些 CSS 元素会导致 CancelPost 或 Save 按钮,或 用户界面,变得无响应。删除这些影响滚动的 CSS 元素:overflow-x: hidden;overflow-y: scroll;-webkit-overflow-scrolling: touch;
用户无法在 Salesforce for iOS 中创建和编辑页面的标准记录上滚动。 用户通常只有在链接到“创建”的 Visualforce 页面时才会遇到此问题 或“编辑”页面包含超出设备屏幕的内容。本期独有 适用于 iOS 的 Salesforce。减少 Visualforce 页面上包含“创建”或“编辑”的内容量 链接,以便用户无需在自定义页面上滚动
对象页面布局中嵌入的 Visualforce 页面不遵循用户定义的 高度。此问题仅适用于 iOS 版 Salesforce。从页面中取消选择嵌入式 Visualforce 页面上的显示滚动条选项 布局编辑器。
在 Safari 中滚动 Visualforce 页面时,页面会移动,但不会显示任何内容 新文本。此问题是 Apple 的错误。此问题仅适用于 iOS 版 Salesforce。UIWebView刷新页面。
Experience Cloud 站点用户会看到对象的标准新建按钮,即使页面 被 Visualforce 操作覆盖隐藏,并标记为不适用于移动设备。本期 是 iOS 版 Salesforce 独有的。没有已知的解决方案。在 Safari 中使用基于浏览器的 Salesforce 版本 iOS系统。
使用 Lightning 组件对对象进行视图覆盖会禁用滚动 通过父对象的相关列表访问时的整个页面。本期独有 适用于 iOS 的 Salesforce。没有已知的解决方案。通过对象主页选项卡或其他方式访问记录 手段,例如程序化导航。

在 Salesforce 中使用 Visualforce 的注意事项和限制 移动应用

Visualforce 允许开发人员构建复杂的自定义用户界面,这些用户界面可以 原生托管在闪电网络上。Visualforce 是 Salesforce 久经考验的模型, 使开发人员能够访问数据以及强大的工具和功能。有很多好处 在 Salesforce 移动应用程序中使用 Visualforce,但也有一些限制。

可用性

  • 易于实施,可提高生产率。
  • 以页面为中心的模型自然而然地将大型应用程序拆分为可管理的小页面。

与 Salesforce 平台和其他工具集成

  • 访问 Salesforce 丰富的元数据基础架构。
  • 标准控制器允许直接访问对象,也可以通过关系访问对象,而无需 执行单个查询。
  • Visualforce 页面可以充当 JavaScript 或第三方框架的容器,例如 AngularJS 或 React。

定制

  • 标准选项卡、自定义对象选项卡和列表视图,这些选项卡被 Visualforce 覆盖 页面在 Salesforce 移动应用程序中不受支持。移动用户将看到默认的 Salesforce 对象的页面 相反。

互动

  • 有限的交互性(除了您自己添加的 JavaScript 功能)。
  • 难以打造身临其境的用户体验。

速度

  • 延迟较高,这会降低移动性能。
  • 与计算能力有限的低端或较旧的移动设备不匹配 资源。
  • Visualforce 在 Salesforce 服务器上处理标记标签,从而提高响应速度 时间。

one.app 容器

在 Salesforce Classic 中,Visualforce “拥有”页面, 请求和环境。Visualforce 是应用程序容器。但是在Salesforce中 移动应用程序和 Lightning Experience,Visualforce 在较大的 /lightning 容器内的 iframe 内运行。如果您习惯于在 Salesforce Classic 中进行开发,则使用 one.app 容器需要进行一些调整,主要是范围和安全注意事项。查看更多 信息,请参阅了解 Salesforce 移动应用程序 容器。

针对 Visualforce 页面问题准备支持请求 Salesforce 应用程序

Salesforce 提供资源来帮助开发人员找到问题的答案,并 解决他们的问题。我们建议您先看一下开发者论坛, Salesforce Stack Exchange 和“已知问题”页面,查看您是否可以立即找到 解决您的问题。如果您的问题仍未得到解答,您可以将案例提交至 Salesforce 的支持团队,他们会将您的问题发送给最佳人选来回答 它。

Salesforce 开发人员论坛

Salesforce 开发人员社区的论坛是任何 有关Salesforce平台和工具的问题。在以下社区中提问和回答问题 400 万 Salesforce 开发人员。

Salesforce 堆栈交换

Salesforce Stack Exchange 是面向 Salesforce 管理员的问答网站, 实施专家和开发人员。任何人都可以提出和回答问题。

已知问题

Salesforce 发布已知问题,通过提供以下功能来增强信任和客户成功 对已知 bug 的可见性。Salesforce 客户支持和工程发布已知 基于客户报告数量、问题严重性和 变通办法的可用性。并非所有已知的 bug 都符合发布条件。

提交支持请求

如果您在论坛上找不到问题的答案,请 Stack Exchange 或“已知问题”页面,下一步是提交支持案例。

  1. 在“帮助和培训”门户上登录您的帐户。
  2. “联系支持人员”磁贴下,单击“创建” 案例
  3. 在“帮助查找器”页面上,选择“开发”,然后选择“Apex/Visualforce”。
  4. 在“问题”选项卡上,检查您的问题是否包含在常见问题中 问题和答案。如果没有,请按 Log a New Case 图标 页面底部。

提交案例需要以下信息:

  • 用户名
  • 首选电子邮件地址
  • 电话号码
  • 时区
  • 大体时间
  • 回电日期
  • 业务影响

为了帮助 Salesforce 的专家快速解决您的案例,请总结重现的步骤 “说明”框中的错误。使步骤尽可能清晰具体。提供 演示该问题的最简单、最短的代码示例。通常,开发人员会解决 他们在简化繁殖过程中的问题。

此外,请考虑向 Salesforce 支持团队授予登录访问权限,以帮助他们进行调查 你的情况。要授予登录访问权限,请转到您的姓名/设置/我的个人信息/授予登录访问权限/选择 Salesforce.com 支持

检查支持请求

您可以查看已提交的支持案例的进度。

  1. 在“帮助和培训”门户上登录您的帐户。
  2. 在“我的成功中心”磁贴下,单击“转到”。
  3. 单击左侧导航栏中的“支持案例”。

选择有效的页面布局

设计在 Salesforce 移动版中美观且运行良好的 Visualforce 页面 应用程序,使用适合使用页面的上下文的页面布局。添加的页面 作为主导航选项卡或操作栏中的自定义操作,几乎可以使用完整的 屏幕,并且可以垂直滚动,而 Visusalforce 添加到 对象的页面布局必须适合特定的有限空间。

一般来说,添加到页面布局的 Visualforce 在只读的情况下效果最好。 信息一目了然。放置需要用户交互的功能,例如多字段 表单,在全屏页面上,通过将它们添加为主导航中的选项卡或自定义 操作栏中的操作。

页面布局上的 Visualforce

添加到对象页面布局的 Visualforce 页面将显示在记录中 详细信息页面。您可以控制 Visualforce 元素在 移动记录详细信息屏幕,将字段和其他记录详细信息放在上面,并且 在它下面,通过更改其在对象页面布局上的位置。视觉力 以这种方式添加的页面遵循相同的规则来对字段和其他 元素确实如此。

注意

本页图片来自上一页 Salesforce 移动应用程序,而不是新的 Salesforce 移动应用程序。

  1. 加载记录时会显示记录标头,但可以向上滚动 并由用户离开屏幕。在屏幕上时,它总共有 158 像素高 设备,并占用屏幕的整个宽度。你无法控制 显示记录标头。
  2. 记录控件和详细信息,由 Salesforce 自动生成 移动应用程序。
  3. 添加到对象页面布局的 Visualforce 页面。
  4. 将宽度设置为 100%;元素自动调整大小,减去一些填充 在两边。
  5. 通过设置 页面布局编辑器中项目的高度(以像素为单位)。视觉力 元素正好使用该高度,即使内容较短。在那 情况下,额外的区域是空白的。如果页面内容较高,则 内容被剪裁。最佳做法是,不要设置内联 Visualforce 页面要比您打算的最小设备屏幕高 支持。

虽然您可以将多个内联 Visualforce 页面添加到页面布局中,但它可以快速 成为滚动浏览它们以查看页面其余部分的用户体验挑战。 最佳做法是永远不要在 一排;将 Visualforce 元素与常规页面元素(例如字段)分开。 如果您需要全屏显示页面,请考虑将其移至自定义页面 操作。添加到页面布局的 Visualforce 页面会自动具有普通的 Salesforce 删除了标题和侧边栏。您可能会发现显式地将它们和 在开发页面时,完整的 Salesforce 站点样式表会关闭。 此外,如果您的网页使用 Google Maps API,Google 建议使用 HTML5 文档类型。下面是一个执行所有这些操作的标签 事情:

<apex:page>

<apex:page standardController="Warehouse__c"
    docType="html-5.0" showHeader="false" standardStylesheets="false">

全屏布局

添加到 Salesforce 移动应用程序导航菜单或自定义的 Visualforce 页面 动作到动作栏,几乎可以使用整个屏幕,允许更多 信息,以及更复杂的用户界面。

注意

本页图片来自上一页 Salesforce 移动应用程序,而不是新的 Salesforce 移动应用程序。

  1. 提供对主导航菜单的访问的 Salesforce 标头是42像素高。 标头的内容无法更改。
  2. 设备屏幕的其余部分专用于您的 Visualforce 页面。

在 Salesforce 移动应用程序中显示时,标准 Salesforce 标头和 侧边栏会自动删除。但是,用作自定义操作的 Visualforce 页面 与完整的 Salesforce 站点共享,并将页面添加到 导航可能会共享,也可能不会共享。与完整 Salesforce 站点共享的页面 不应明确删除标准的 Salesforce 标题和侧边栏,除非 删除标题和侧边栏是所有 Visualforce 的标准做法 网站。

用户输入和交互

使用 、 属性和传递 HTML 属性 创建适合移动设备的表单和用户界面,这些表单和用户界面高效且可利用 本机移动浏览器功能。

<apex:input>type

如果没有键盘和鼠标,用户可能很难填写标准 HTML 表单 并在移动设备(尤其是手机)上进行交互。对于 Visualforce 页面,这 不要使用 JavaScript 远程处理来发出请求,请选择 Visualforce 组件 着眼于移动用户的表单输入。您可以对 Visualforce 页面进行的其他更改都不会有 与利用新的 HTML5 和移动浏览器功能相比,对可用性的影响更大 以改进窗体和用户界面控件。

选择高效的输入元素

用于获取用户输入 尽可能。 是一个 HTML5 就绪、适合移动设备的通用输入组件,可适应 表单字段所需的数据。它甚至更灵活,因为它使用该属性允许客户端浏览器 显示适合类型的用户输入小组件,例如日期选择器,或使用 特定于类型的键盘,使在移动设备上输入输入变得更加容易。<apex:input><apex:input><apex:inputField>type

您还可以使用 为与 Salesforce 上的字段对应的值创建 HTML 输入元素 对象。 改编 HTML 生成与基础 sObject 字段的数据类型相对应。通常 这是您想要的,但如果不是,请使用该属性覆盖自动数据 类型检测。但是,请注意,这会生成大量 HTML,并且需要额外的 要加载的资源,这意味着它不是最有效的组件 通过移动无线连接。<apex:inputField><apex:inputField>type<apex:inputField>

使用 type 属性创建 适合移动设备的输入元素

在组件上设置属性,并且 ,如果您使用 IT – 显示特定于数据类型的键盘和其他输入用户界面小部件 在触摸屏上更易于使用。该值将传递到生成的 HTML 元素,用于在 Salesforce 应用程序。type<apex:input><apex:inputField><input>

当用户单步执行表单元素时,该表单元素的输入方法会进行调整 对于预期的数据类型。文本字段显示标准键盘、电子邮件字段 显示带有“@”符号和 分配给键的“.com”,日期字段显示日期选择器,依此类推 上。下面是一个表单示例,说明了这是如何实现的 工程:

<apex:form >
  
    <apex:outputLabel value="Phone" for="phone"/>
    <apex:input id="phone" value="{!fPhone}" type="tel"/><br/>
      
    <apex:outputLabel value="Email" for="email"/>
    <apex:input id="email" value="{!fText}" type="email"/><br/>
      
    <apex:outputLabel value="That Number" for="num"/>
    <apex:input id="num" value="{!fNumber}" type="number"/><br/>
      
    <apex:outputLabel value="The Big Day" for="date"/>
    <apex:input id="date" value="{!fDate}" type="date"/><br/>
      
</apex:form>

如 用户在表单字段中移动,通过点击它们或点击“下一步”按钮,键盘会更改以匹配预期 字段的数据。

这些特定于类型的键盘使使用者更容易填写表单 他们的移动设备。

<apex:input>允许设置以下显式值:

type

  • 日期
  • 日期时间
  • 日期时间-本地
  • 时间
  • 电子邮件
  • 范围
  • 搜索
  • 电话
  • 发短信
  • 网址

您还可以设置为 auto,并且 使用关联的控制器属性或方法的数据类型。

type

HTML 属性,包括新的 HTML5 功能,是 HTML 的标准部分。有关属性的其他详细信息、可以使用它的用途以及用途 与移动开发相关,请参阅 WHATWG 的输入类型属性值和说明列表。 并非所有值都支持 Visualforce 输入 组件。如果要使用 Visualforce 不支持的值,请使用 static HTML 而不是 Visualforce 标记。typetype

使用 HTML5 传递属性进行客户端验证

将您和其他 Visualforce 组件上的直通属性设置为 启用其他 HTML5 功能,例如客户端验证。通过执行基本 验证,可以避免向服务器发送请求,并且 等待响应,当表单上有易于纠正的错误时。<apex:input>传递前缀为 到生成的 HTML,并删除前缀。启用客户端 验证,设置属性 要匹配预期的标记 表单值。这将向 生成的标记,启用 该字段的客户端验证。

html-html-pattern<apex:input>pattern<input>

注意

客户端验证要求 Visualforce 页面 设置为 API 版本 29.0 或更高版本,并将页面设置为 .docTypehtml-5.0验证模式是正则表达式。根据表单输入进行检查 表达式,如果匹配,则认为字段输入有效。如果它 不匹配,则输入被视为无效;错误消息是 ,表单将不会提交到服务器。这是一个 需要来自特定电子邮件地址的字段示例 域:

<apex:input id="email" value="{!fText}" type="email"
    html-placeholder="you@example.com"
    html-pattern="^[a-zA-Z0-9._-]+@example.com$"
    title="Please enter an example.com email address"/>

其他可设置为直通属性的有用 HTML5 属性包括:

  • placeholder(使用属性设置)- 添加 幻影文本添加到字段中,以向用户显示示例输入。html-placeholder
  • title(使用 on 的属性和组件的属性进行设置 without a title attribute) – 添加一条错误消息,以便在以下情况下使用:字段 客户端验证失败。title<apex:input>html-title

有关如何使用属性来增强 HTML 元素可用性的灵感,HTML5 表单简介和新增功能 Attributes 是对 HTML5 中新功能的一个很好的调查。为了进一步 详细信息,尤其是对于移动用户的详细信息,以及客户端表单验证的详细信息, 请参阅客户端表单验证和改善移动设备上的用户体验 WHATWG 的 HTML:生活标准中的设备。<input>

管理导航

Salesforce 移动应用程序使用事件管理导航。导航事件 framework 作为 JavaScript 对象提供,它提供了许多实用程序 使创建“正常工作”的编程导航变得轻而易举的功能。这 优势是更适合移动环境的导航体验。它还使 创建完成后导航,例如在订单完成后重定向到订单页面 成功提交,对 Salesforce 开发人员来说更容易。在 Salesforce 移动应用程序中,Visualforce 页面的程序化导航通常 工作原理如下:

  1. 用户调用 Visualforce 页面,通常从导航菜单或 操作栏中的操作。
  2. Visualforce 页面加载并运行,包括任何自定义控制器或 页面调用的扩展代码。
  3. 用户以某种方式与页面交互:例如,填写一些表单 值。
  4. 用户提交表单,或在页面上执行一些其他操作 提交更改。
  5. 控制器或扩展代码运行,将更改保存到 Salesforce,以及 返回操作的结果。
  6. Visualforce 页面使用 JavaScript 响应处理程序接收结果 操作,成功后,通过将用户重定向到新的 页面,显示其操作的结果。

应用的导航框架可以轻松处理此方案。

另一个常见的用例是简单地将链接或其他用户界面控件添加到 页面,从该 Visualforce 页面移动到应用程序中的另一个页面。此导航 也可以通过应用程序的导航框架轻松管理。

在这些情况下,导航由一个特殊的实用程序 JavaScript 对象 .当出现以下情况时,该对象会自动添加到所有 Visualforce 页面中 它们在 Salesforce 移动应用程序内运行。此对象提供了许多函数 在运行时触发导航事件。若要使用这些函数,可以调用它们 直接从页面的 JavaScript 代码中获取,也可以将调用作为点击处理程序附加到 元素。sforce.onesforce.one下面是一个 JavaScript 函数,用于创建要添加到 Google 的标记 地图。

function setupMarker(){ 

    // Use JavaScript nav function to determine if we are
    // in the Salesforce mobile app and set navigation link appropriately
    var warehouseNavUrl = 
        'sforce.one.navigateToSObject(\'' + warehouse.Id + '\')';

    // Wrap the warehouse details with the link to 
    // navigate to the warehouse details
    var warehouseDetails = 
        '<a href="javascript:' + warehouseNavUrl + '">' + 
        warehouse.Name + '</a><br/>' + 
        warehouse.Street_Address__c + '<br/>' + 
        warehouse.City__c + '<br/>' + 
        warehouse.Phone__c;
   
    // Create a panel that will appear when a marker is clicked
    var infowindow = new google.maps.InfoWindow({ 
        content: warehouseDetails
    });
   
    // ...
}

这 第一行构建一个字符串 ,当用作 JavaScript URL,导航到仓库的详细信息页面。链接已创建 ,并显示在单击标记时显示的信息面板(放在字符串中)。 单击仓库名称将带您进入该仓库的详细信息页面(省略 函数代码的一部分涉及 Google Maps API 调用以创建标记和 将其添加到地图中)。

warehouseNavUrlwarehouseDetails如果您有在 Salesforce 移动应用程序中运行的 JavaScript 代码或 HTML 标记, 请记住以下注意事项:

  • 不要使用 直接操作浏览器 URL。这与应用程序的 导航管理系统。window.location.href
  • 不要在导航 URL 中使用; 您无法在应用程序内打开新窗口。target=”_blank”

Canvas 框架中的导航方法

如果您使用的是 Canvas,则有一种更简单的方法来控制 Canvas 周围的导航 Salesforce 移动应用程序中的应用程序和画布个人应用程序。

您可以使用 Lightning Platform 方法控制应用程序中的导航。Canvas 框架中的这些方法是 驻留在 JavaScript 库中的事件。调用导航方法之一时 从画布代码中,您将一个事件发送到 Salesforce,该事件读取有效负载并指示 用户到指定目标。

将导航方法引用为事件变量,并使用 名称和有效负载。例如:

var event = {name:”s1.createRecord”, payload: {entityName: “Account”, recordTypeId: “00h300000001234”}};

有关使用新方法的更多信息,请参阅用于 Canvas 的 Salesforce 移动应用程序导航方法 Canvas 开发人员指南中的应用。

  • 使用 sforce.one 对象
    进行导航和消息传递 Salesforce Platform 包括用于导航和消息传递的事件机制。这在 Visualforce 中显示为名为 的 JavaScript 对象。它可以在 Salesforce 移动应用程序中显示的任何页面中使用。sforce.one
  • sforce.one 如何处理 API 版本 该对象在新版本
    中经常得到改进。为了保持向后兼容性,提供了特定于版本的行为,你可以在应用中使用特定版本的 。sforce.onesforce.onesforce.one

使用 sforce.one 对象进行导航和消息传递

Salesforce Platform 包括用于导航和消息传递的事件机制。这 在 Visualforce 中显示为名为 的 JavaScript 对象。它可在 Salesforce 移动版中显示的任何页面中使用 应用程序。

sforce.one

该对象提供以下功能 功能。使用对象中的点分表示法引用函数。例如:。sforce.onesforce.onesforce.one.navigateToSObject(recordId, view)

有关这些函数触发的基础事件的更多详细信息,请参阅Lightning Aura组件开发人员指南。

功能描述
back(​[refresh])导航到历史记录中保存的上一个状态。这相当于单击一个 浏览器的“后退”按钮。sforce.one刷新是可选的。默认情况下, 页面不刷新。通过刷新 页面(如果可能)。true
navigateToSObject(​recordId​[, view])导航到 sObject 记录, 由 recordId 指定。这个记录“home”有几个视图, 在 Salesforce 移动应用程序中以幻灯片形式提供,用户可以滑动 之间。view 是可选的,默认为 。视图指定幻灯片 在记录主页中最初显示。detail注意对应的记录 ID 不支持 ContentNote SObjects。可能的值如下所示。detail:记录详细信息幻灯片chatter:Chatter 幻灯片related:相关幻灯片的视图
navigateToURL(​url​[, isredirect])导航到指定的 URL。支持相对根 URL 和绝对 URL。相对 URL 是 相对于闪电网域根目录,并保留导航历史。例如 Visualforce 页面的相对根 URL 以正斜杠为前缀,例如 .支持或不支持的相对 URL。外部 URL(即 Lightning 域之外的 URL)在单独的浏览器中打开 窗。/apex/c__Listen../apex/c__Listenapex/c__Listen注意根据 用户的设备平台、设备设置、Salesforce 版本,以及 要打开的外部 URL 的身份验证要求,单独的 浏览器窗口可能需要身份验证或 重新身份验证。使用相对 URL 导航到 应用内的不同屏幕。使用外部 URL 允许用户访问 不同的网站或应用,他们可以在其中执行不需要保留的操作 在您的应用中。若要返回到应用,请返回由外部打开的单独窗口 当用户使用完其他应用时,必须关闭 URL。新窗口有 与应用不同的历史记录,当窗口 闭。这也意味着用户无法单击“后退”按钮返回到 应用程序;用户必须关闭新窗口。mailto:支持 、 、 和其他 URL 方案 启动外部应用程序并尝试“做正确的事”。但是,支持各不相同 通过移动平台和设备。 并且是可靠的,但我们建议您 在预期范围内测试任何其他 URL 设备。tel:geo:mailto:tel:isRedirect 是可选的,默认为 。将其设置为指示新 URL 应替换 导航历史记录。falsetrue当您从模式导航到 URL 时,例如从 为快速操作启用的组件,模态不会由 违约。要在导航时自动关闭模式,请设置为 。isredirecttrue注意在 or any 的处理程序中使用时要小心。即使 ,命令按钮的默认单击操作是 表单发布。在此方案中,命令按钮执行窗体发布和操作,要求用户 单击“后退”按钮两次以导航到上一页。为了防止 默认的单击操作,将处理程序配置为调用或返回 。navigateToURLonClick<apex:commandButton><button type=”submit”><input type=”submit”>isredirect=truenavigateToURLonClickevent.preventDefault()false注意与 ContentNote SObjects 对应的 URL 不是 支持。
navigateToFeed(​subjectId, type)导航到 指定的类型,范围限定为 subjectId。为 某些 Feed 类型,则 subjectId 是必需的 但被忽略了。对于这些 Feed 类型,请将当前用户的 ID 作为 subjectId。type 是源类型。这 可能的值如下所示。BOOKMARKS:包含保存为书签的所有提要项 由上下文用户提供。传递当前用户的 ID 作为 subjectId。COMPANY:包含除 类型。查看源 项,则用户必须具有对其父级的共享访问权限。 传递当前用户的 ID 作为 subjectId。TrackedChangeFILES:包含包含文件的所有源项 由上下文用户关注的人员或组发布。 传递当前用户的 ID 作为 subjectId。GROUPS:包含所有组中的所有源项 上下文用户拥有或属于其成员。 传递当前用户的 ID 作为 subjectId。NEWS:包含上下文用户关注的人员、用户所属组的所有更新 用户关注的成员,以及用户正在关注的归档和记录。包含所有更新 对于其父级为上下文用户的记录。 传递当前用户的 ID 作为 subjectId。PEOPLE:包含所有人发布的所有提要项 上下文用户跟随。 传递当前用户的 ID 作为 subjectId。RECORD:包含其父项为 指定的记录,可以是组、用户、对象、文件或任何其他 标准或自定义对象。当记录为组时,源还包含 提及该组的 Feed 项目。当记录是用户时,源 仅包含该用户的源项。您可以获取其他用户的记录 饲料。将记录的 ID 作为 subjectId 传递。TO:包含提及上下文用户的所有源项。包含饲料 上下文用户评论的项目和上下文用户创建的源项目 被评论。 传递当前用户的 ID 作为 subjectId。TOPICS:包含包含 指定的主题。将主题的 ID 作为 subjectId 传递。此值为 仅在 Salesforce 移动 Web 版中受支持。主题在 适用于 iOS 的 Salesforce 或适用于 Android 的 Salesforce。
navigateToFeedItemDetail(​feedItemId)导航到特定源 item、feedItemId 和任何关联的注释。
navigateToRelatedList(​relatedListId, parentRecordId)导航到相关列表 parentRecordId。例如,要显示 Warehouse 对象,parentRecordId 为 。Warehouse__c.IdrelatedListId 是要显示的相关列表的 API 名称或 ID。
navigateToList(​listViewId​, listViewName, scope)导航到以下列表视图 由 listViewId 指定,listViewId 是列表视图的 ID 显示。listViewName 设置列表视图的标题。它 不需要与为列表视图保存的实际名称匹配。要使用 保存名称,将 listViewName 设置为 null。将 scope 设置为视图中 sObject 的名称,例如, “帐户”或“MyObject__c”。
createRecord(​entityName​[, recordTypeId][, defaultFieldValues])打开页面以创建记录 对于指定的 entityName,例如,“Account”或 “MyObject__c”。recordTypeId 是可选的,并指定 所创建对象的记录类型。在不提供 recordTypeId 的情况下调用 createRecord 可能会导致 错误。defaultFieldValues 是可选的,如果提供, 在记录创建面板上预填充字段,包括未显示在 面板。用户必须对具有预填充值的字段具有创建访问权限。 保存期间由字段访问限制导致的错误不显示错误 消息。
editRecord(​recordId)打开页面以编辑记录 由 recordId 指定。
showToast({toastParams})显示 Toast。Toast 显示一条消息 在视图顶部的标题下方。该对象设置 Toast 的属性。使用任何属性 可用于 Aura 活动。为 例:toastParamsforce:showToastsforce.one.showToast({ "title": "Success!", "message": "The record was updated successfully." });
publish(​messageChannel,​message)使用 闪电消息服务。请参阅在消息通道上发布。
subscribe(​messageChannel,​function)使用 Lightning 消息服务订阅 messageChannel。当订阅上的消息时,提供的功能运行 消息通道已发布。该函数返回一个可与 一起使用的订阅对象。请参阅订阅和 取消订阅消息频道。subscribe()unsubscribe()
unsubscribe(​subscription)从消息通道中取消订阅对象的订阅。请参阅订阅和 取消订阅消息频道。

使用对象时,请记住以下几点:

sforce.one

  • 调用 可能会导致 如果 URL 引用了对象或 Chatter 的标准页面,则出现“不支持的页面”错误 页面。为避免此错误,请确保 URL 以正斜杠开头 (/_ui 而不是 _ui)。sforce.one.navigateToURL
  • 该方法不 尊重 Visualforce 覆盖标准操作。sforce.one.createRecord
  • 开发人员可以使用该类来 控制 Salesforce 移动应用程序的导航。某些操作及其关联的 URL 尚不完全受支持。 例如,使用操作 克隆或编辑可能无法按预期工作。计划在将来的版本中提供全面支持。pageReferencepageReferencestandard_recordPage

sforce.one 如何处理 API 版本

对象经常改进 在新版本中。为了保持向后兼容性,提供了特定于版本的行为,您可以使用特定的 版本。

sforce.onesforce.onesforce.one

默认情况下,使用与 请求的 Visualforce 页面的 API 版本。例如,如果 Visualforce 页面 API 版本为 30.0,该页面上默认使用的 JavaScript 使用 API 版本 30.0 的 。sforce.onesforce.onesforce.one

这意味着,当 Visualforce 页面更新到新的 API 版本时,该页面 自动使用 的更新版本。在前面的示例中,如果该 Visualforce 页面已更新 到 API 版本 31.0,则使用 API 版本 31.0 的应用功能。sforce.onesforce.onesforce.one如果新 API 版本中的更新行为导致页面功能出现兼容性问题,则 有三个选项来纠正问题。

sforce.one

  • 将 Visualforce 页面的 API 版本恢复到以前的版本。此操作 无需更改代码。
  • 更新页面功能的代码以解决问题。此解决方案是 最好,但它可能需要一些调试,并且肯定需要代码 变化。
  • 使用特定版本的 。 此解决方案通常需要最少的代码更改。sforce.one

注意

sforce.one在 14 年冬季添加 (API 版本 29.0),直到 14 年夏季(API 版本 31.0)才进行版本控制。版本 31.0 之前的所有版本都是 与版本 31.0 相同。您可以为对 Visualforce 有效的任何版本指定一个版本,即从 版本 15.0 设置为当前 API 版本。sforce.onesforce.one

使用特定版本的 sforce.one

要使用特定版本的 ,请使用 函数和 为其提供 API 版本和需要使用特定 的版本。适当的 的版本是自动的 由此调用加载。sforce.onesforce.one.getVersion()sforce.onesforce.one签名为:

sforce.one.getVersion()

sforce.one.getVersion(versionString, callbackFunction);

versionString 是应用程序所需的 API 版本。 它始终是两位数、句点和一位数字,例如“30.0”。无效的调用 版本字符串以静默方式失败。

callbackFunction 是一个 JavaScript 函数,它使用特定的 的版本。 经营 异步,回调函数在完成加载 请求的 版本。你 callback 函数接收单个参数,即指定 API 版本的对象。使用传递的对象 在而不是全局制作 对符合 API 的调用 应用所需的版本。sforce.onesforce.one.getVersion()sforce.onesforce.onesforce.onesforce.one

使用特定版本的 sforce.one 的示例

接下来的示例都将 Create Account 函数添加到以下输入中 按钮:

<input type="button" value="Create Account" onclick="btnCreateAccount()" id="btnCreateAcct"/>

默认为 Visualforce 页面的 API 版本应使用默认版本的应用代码 – 与 Visualforce 页面的 API 相对应的版本 版本 – 不需要请求版本。使用该版本会自动发生, 代码是 简单。

sforce.one

<script>
    function MyApp() {
        this.createAccount = function() {
            sforce.one.navigateToURL("/001/e");
        };
    } 

    var app = new MyApp();

    function btnCreateAccount() {
        app.createAccount();
    }
</script>

应用功能是在对象中创建的,然后事件处理函数在该事件 发生按钮咔嗒声。将应用程序功能与应用程序事件分离 处理是最佳做法,它为您设置了使用特定于版本的版本 之。MyAppsforce.one

使用特定的 sforce.one API 版本 (简单)要使用特定版本的 ,请获取 并保存对对象的版本化实例的引用。然后使用此对象可以 拨打电话。最简单的方法是 将其保存在对象中。在下一个 示例中,对 的版本化实例的引用位于 大胆。

sforce.onesforce.oneMyAppsforce.one

<script>
    function MyApp(sfone) {
        this.createAccount = function() {
            sfone.navigateToURL("/001/e");
        };
    } 
        
    var app30 = null;

    function btnCreateAccount() {
        // Create our app object if not already defined
        if(!app30) {
            // Create app object with versioned sforce.one
            sforce.one.getVersion("30.0", function(sfoneV30) {
                app30 = new MyApp(sfoneV30);
                app30.createAccount();
            });
            return;
        }
        app30.createAccount();        
    }
</script>

在前面的示例中,事件处理函数是从第一个扩展而来的 示例,以包括创建特定于版本的 实例。如果您的应用需要混合多个 versions,您可以创建多个具有适当版本和名称的实例。不过,不止一两个是 管理起来很麻烦。我们建议改用下一种方法。sforce.oneMyApp

使用特定的 sforce.one API 版本 (最佳)组织应用代码的更好方法是在应用初始化中创建特定于版本的实例 代码块,以便您可以保留事件处理 分开。

sforce.one

<script>
    function MyApp(sfone) {
        this.createAccount = function() {
            sfone.navigateToURL("/001/e");
        };
    } 
        
    var app30 = null;

    // Initialize app: get versioned API, wire up clicks
    sforce.one.getVersion("30.0", function(sfoneV30) {
        // Create app object with versioned sforce.one
        app30 = new MyApp(sfoneV30);

        // Wire up button event
        var btn = document.getElementById("btnCreateAcct");
        btn.onclick = btnCreateAccount;
    });

    // Events handling functions
    // Can't be fired until app is defined
    function btnCreateAccount() {
        app30.createAccount();
    }
</script>

在此示例中,应用初始化仅由空格和注释分隔。 但是你可以把它分成几个函数,以便更好地封装。

使用特定的 sforce.one API 版本 (同步)您可以通过在页面上手动包含特定版本的 JavaScript 来触发同步模式。这 库 URL 的格式 是:/sforce/one/sforceOneVersion/api.js。 下面是一个示例:

sforce.onesforce.one

<script src="/sforce/one/30.0/api.js"></script>
<script>
    function MyApp(sfone) {
        this.createAccount = function() {
            sfone.navigateToURL("/001/e");
        };
    } 
        
    var app = null;

    sforce.one.getVersion("30.0", function(sfoneV30) {
        app = new MyApp(sfoneV30);
    });

    // Events handling function
    // Can't be fired until app is defined
    function btnCreateAccount() {
        app.createAccount();
    }
</script>

虽然有些情况需要同步模式,但异步版本是 首选。如果您忘记手动包含正确版本的库,您的代码将包含错误 难以诊断。sforce.one

Salesforce Lightning Design System 简介

Salesforce Lightning Design System (SLDS) 可帮助您构建具有外观的应用程序 和 Lightning Experience 的感觉,无需编写任何一行 CSS。SLDS 是一个 CSS 框架 这使您可以访问我们的开发人员用于创建的图标、调色板和字体 闪电体验。

Lightning Experience UI 核心原则

SLDS 所代表的 Lightning Experience UI 采用四核设计精心打造 原则。我们鼓励您在开发应用程序时牢记这些内容。

  • 清晰度 — 消除歧义。使人们能够看到、理解和行动 信心。
  • 效率 — 简化和优化工作流程。智能预测需求 帮助人们更好、更智能、更快速地工作。
  • 一致性 — 通过应用相同的方法创造熟悉感并加强直觉 解决同一问题。
  • 美丽 — 通过深思熟虑和 优雅的工艺。

SLDS 的优势

SLDS 为您提供了创建符合原则、设计语言和 Lightning Experience 的最佳实践。以下是使 SLDS 如此有用的优点:

  • 在扩展现有功能时,它提供了统一的体验和简化的工作流程 或与外部系统集成。
  • 它不会过度强制执行默认值,例如填充和边距。
  • 它会不断更新。只要您使用的是最新版本的 SLDS,您的页面 与 Lightning Experience 一致。
  • 它包括 CSS 框架中的可访问性。
  • 它适用于其他 CSS 框架,如 Bootstrap。
  • 将 SLDS 应用于 Visualforce 页面 您可以使用 Lightning 设计系统 (SLDS) 构建与 Salesforce 移动应用程序的外观相匹配的 Visualforce 页面
    。要使用 SLDS,需要对代码进行一些调整,并需要记住一些事项。在大多数情况下,使用 SLDS 的 Visualforce 代码可以正常工作。
  • 在 Visualforce
    中使用 SLDS 图标 Lightning 设计系统 (SLDS) 包括 PNG 和 SVG(包括个人和 spritemap)版本的动作、自定义、文档类型、标准和实用程序图标。
  • 使用 SLDS 为 Salesforce 移动应用程序创建 Visualforce 页面 让我们创建一个 Visualforce 页面,该页面显示您最近访问的帐户,并使用 Lightning 设计系统 (SLDS) 进行样式设置,并将其添加到移动导航菜单中。
  • 使用 SLDS
    的响应式页面设计 响应式设计是一种网页设计方法,旨在创建在线用户界面,在各种屏幕尺寸上提供最佳的查看体验,包括轻松阅读和导航。

将 SLDS 应用于 Visualforce 页面

您可以使用 Lightning 设计系统 (SLDS) 构建与 Salesforce 移动应用程序的外观。要使用 SLDS,需要对代码进行一些调整,然后 要记住的事情很少。在大多数情况下,使用 SLDS 的 Visualforce 代码无需 问题。

在 Visualforce Pages 中使用 SLDS

每次使用 SLDS 时,请添加到 页面并将代码包装在范围类中。<apex:slds /><div class=”slds-scope”>…</div>

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

  <!-- Import the Design System style sheet -->
  <apex:slds />

    <!-- REQUIRED SLDS WRAPPER -->
    <div class="slds-scope">

我们讨论的 Visualforce 移动开发中的许多最佳实践都适用于此处 也。Apex 标记,例如 和 尚不支持使用 使用 SLDS。<apex:pageblock><apex:inputField>

SLDS 类命名

SLDS 使用称为 Block-Element-Modifier 语法 (BEM) 的标准类命名约定来 使类名不那么模棱两可。

  • 块表示高级组件(例如,)。car
  • 元素表示组件 () 的后代。car__door
  • 修饰符表示块或元素的特定状态或变体 ()。car__door–red

在 Visualforce 中使用 SLDS 图标

闪电设计系统 (SLDS) 包括 PNG 和 SVG(包括个人和 spritemap) 我们的 action、custom、doctype、standard 和 utility 图标的版本。

要在 Visualforce 页面中使用 SVG 精灵图图标,请将属性添加到标签中。xmlns=”http://www.w3.org/2000/svg” xmlns:xlink=”http://www.w3.org/1999/xlink”<html>

<span class="slds-icon_container slds-icon-standard-account" title="description of icon when needed">
 
  <svg aria-hidden="true" class="slds-icon">
 
    <use xlink:href="{!URLFOR($Asset.SLDS, 'assets/icons/standard-sprite/svg/symbols.svg#account')}"></use>
  </svg>
 
   <span class="slds-assistive-text">Icon Assistive Text</span>

</span>

由于图标是独立的并且具有含义,因此我们将其放置在带有 .slds-icon_container class

图标没有开箱即用的背景颜色。要设置背景颜色,我们应用第二个 类到跨度。要对特定图标使用默认颜色,请构造 图标的特定实用程序类,通过连接 、 Sprite 映射名称和 . 将该类应用于元素。在示例中 我们使用“标准”精灵映射和“帐户”图标,因此该类是 .slds-icon--icon<span>slds-icon-standard-account

在 中,我们有一个带有类的元素。元素反过来 包含一个 <use> 标记,该标记根据图标的属性指定要显示的图标。<span><svg>slds-icon<svg>xlink:href

要设置 xlink:href 路径:

  1. 从图标页面中选择要使用的图标。记下它属于哪个类别 (操作、自定义、文档类型、标准或实用程序)。
  2. 通过连接类别 sprite 来完成 xlink:href 属性 (例如,“standard-sprite”)、/svg/symbols.svg# 和特定图标 在其中(例如,“帐户”)。这为我们提供了路径 assets/icons/standard-sprite/svg/symbols.svg#account。

在标记后,辅助文本位于 span 与类。<svg>slds-assistive-text

使用 Salesforce 移动应用程序创建 Visualforce 页面 SLDS系列

让我们创建一个 Visualforce 页面,该页面显示您最近访问的帐户,并且是 使用闪电设计系统(SLDS)进行样式设计,并将其添加到移动导航中 菜单。

  1. 首先,让我们创建 Visualforce 页面。
  2. 打开开发者控制台,点击“文件”|”新品 |Visualforce 页面。输入 SLDSPage 作为页面名称。
  3. 在编辑器中,将任何标记替换为以下内容。<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0"> <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> <title>SLDS LatestAccounts Visualforce Page in Salesforce Mobile</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- Import the Design System style sheet --> <apex:slds /> </head> <apex:remoteObjects > <apex:remoteObjectModel name="Account" fields="Id,Name,LastModifiedDate"/> </apex:remoteObjects> <body> <!-- REQUIRED SLDS WRAPPER --> <div class="slds-scope"> <!-- PRIMARY CONTENT WRAPPER --> <div class="myapp"> <!-- ACCOUNT LIST TABLE --> <div id="account-list" class="slds-p-vertical--medium"></div> <!-- / ACCOUNT LIST TABLE --> </div> <!-- / PRIMARY CONTENT WRAPPER --> </div> <!-- / REQUIRED SLDS WRAPPER --> <!-- JAVASCRIPT --> <script> (function() { var outputDiv = document.getElementById('account-list'); var account = new SObjectModel.Account(); var updateOutputDiv = function() { account.retrieve( { orderby: [{ LastModifiedDate: 'DESC' }], limit: 10 }, function(error, records) { if (error) { alert(error.message); } else { // create data table var dataTable = document.createElement('table'); dataTable.className = 'slds-table slds-table--bordered slds-text-heading_small'; // add header row var tableHeader = dataTable.createTHead(); var tableHeaderRow = tableHeader.insertRow(); var tableHeaderRowCell1 = tableHeaderRow.insertCell(0); tableHeaderRowCell1.appendChild(document.createTextNode('Latest Accounts')); tableHeaderRowCell1.setAttribute('scope', 'col'); tableHeaderRowCell1.setAttribute('class', 'slds-text-heading_medium'); // build table body var tableBody = dataTable.appendChild(document.createElement('tbody')) var dataRow, dataRowCell1, recordName, data_id; records.forEach(function(record) { dataRow = tableBody.insertRow(); dataRowCell1 = dataRow.insertCell(0); recordName = document.createTextNode(record.get('Name')); dataRowCell1.appendChild(recordName); }); if (outputDiv.firstChild) { // replace table if it already exists // see later in tutorial outputDiv.replaceChild(dataTable, outputDiv.firstChild); } else { outputDiv.appendChild(dataTable); } } } ); } updateOutputDiv(); })(); </script> <!-- / JAVASCRIPT --> </body> </html> </apex:page>
    • 标签允许 访问 SLDS 样式表。该组件是 将 SLDS 作为静态资源上传并在 Visualforce 中使用它 页面。<apex:slds />
    • 包装器对于任何 SLDS 样式的内容都是必需的。仅限 SLDS 样式 应用于其中包含的元素。<div class=”slds-scope”>
  4. 此页面也适合移动设备使用。让我们将页面添加到 Salesforce 移动菜单。
  5. 为移动应用启用页面。
    1. 在“设置”中,在“快速”中输入 Visualforce 页面 “查找”框,然后选择“Visualforce 页面”。
    2. 单击列表中 SLDSPage Visualforce 页面旁边的编辑
    3. 选择可用于 Lightning Experience、Experience 构建器网站和移动应用程序
    4. 点击保存
  6. 为 Visualforce 页面创建一个选项卡。
    1. 在“设置”中,在“快速查找”框中输入“选项卡”, ,然后选择选项卡
    2. 在“Visualforce 选项卡”部分中,单击“新建”。
    3. 在“Visualforce 页面”下拉列表中,选择“SLDSPage”。
    4. 在选项卡标签字段中,输入 SLDS 页面。请注意,“选项卡名称”字段是自动填充的
    5. 单击“选项卡样式”字段,然后选择“菱形”样式。此样式的图标显示为 Salesforce 移动导航菜单。
    6. 单击“下一步”,然后单击“下一步”, 然后保存
  7. 将选项卡添加到移动导航菜单。
    1. 在“设置”中,在“快速查找”中输入“移动应用” 框中,然后选择 Salesforce 导航
    2. 选择“SLDS 页面”选项卡,然后单击“添加”。SLDS 项将添加到“已选择”(Selected) 的底部 列表。
    3. 点击保存

使用 SLDS 的响应式页面设计

响应式设计是一种网页设计方法,旨在创建在线用户界面 在各种屏幕上提供最佳的观看体验,包括轻松阅读和导航 大小。

响应式用户界面通过使用流畅的、 基于比例的网格、灵活的图像和 CSS3 媒体查询。使用响应式设计,您可以 可以创建看起来很棒且在手机和平板电脑上运行良好的 Visualforce 页面。

标准 Salesforce 应用程序页面使用响应式设计来提供设备优化的布局。这 主要技术是电话的堆叠单列布局,以及并排的两列布局 平板电脑的布局。该页面对于所有设备都是相同的,并适应其屏幕尺寸 显示上。

SLDS电网系统

闪电设计系统 (SLDS) 使用基于 Flexbox 的网格来提供灵活的、 移动优先、与设备无关的脚手架系统。网格系统可让您划分页面 分成行和列,并为不同尺寸的屏幕定义布局变化。网格可以是 嵌套以创建复杂的布局。

网格系统由两部分组成,网格包装器(类)和其中的列(类)。默认情况下,列的大小相对于其内容。slds-gridslds-col

您还可以使用 SLDS 中的大小调整帮助程序手动指定列大小。它们使用一种格式,其中 X 表示分数 总空间 Y。例如,表示为可用空间的 50% 的宽度。使用手动调整大小类 帮助程序,您可以在以下网格中指定列比 – 2、3、4、5、6 和 12.slds-size–X-of-Yslds-size–1-of-2

使用 SLDS 创建响应式设计页面

  1. 打开开发者控制台,点击“文件”|”新品 |Visualforce 页面。输入页面名称。SLDSResponsivePage
  2. 在编辑器中,将任何标记替换为 以后。<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0"> <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> <title>SLDS ResponsiveDesign Visualforce Page in Salesforce Mobile</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- Import the Design System style sheet --> <apex:slds /> </head> <body> <!-- REQUIRED SLDS WRAPPER --> <div class="slds-scope"> <!-- PRIMARY CONTENT WRAPPER --> <!-- RESPONSIVE GRID EXAMPLE --> <div class="myapp"> <div class="slds-grid slds-wrap"> <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--1-of-4"> <div class="slds-box slds-box_x-small slds-text-align_center slds-m-around--x-small">Box 1</div> </div> <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--3-of-4"> <div class="slds-box slds-box_x-small slds-text-align_center slds-m-around--x-small">Box 2</div> </div> </div> </div> <!-- / RESPONSIVE GRID EXAMPLE --> </div> </body> </html> </apex:page>这 代码创建一个两列网格,其中两列分别为:
    • 移动屏幕上的全宽和垂直
    • 大小为 1:1,在小屏幕上并排显示(超过 480 像素)
    • 大小为 3:1,在更大的屏幕上并排显示(超过 768 像素)

在桌面和移动设备上查看此页面,了解响应式设计的实际效果。

使用 Visualforce 页面作为自定义操作

如果您的 Visualforce 页面用作自定义操作,请将其设计为可执行操作 根据标准控制者提供的单个记录,或找到记录并对其采取行动,或 记录检索到的自定义控制器代码。

对对象的自定义操作

添加为自定义的 Visualforce 页面 在对象类型的记录的上下文中调用对对象的操作。这 自定义操作将传递一个特定的记录 ID,即用户正在查看的记录 当用户单击自定义操作时。设计页面以对该特定记录执行操作 类型。

用作对象自定义操作的 Visualforce 页面必须使用标准 该对象的控制器。使用控制器 用于添加自定义代码的扩展,包括可以使用 JavaScript 调用的方法 远程处理。@RemoteAction

您的自定义代码可以做的不仅仅是对 原始记录。例如,“创建快速订单”自定义操作 搜索匹配的商品。然后,它会创建发票和明细项,所有 作为为部件创建订单的一部分。该逻辑发生在 原始客户记录 – 发票与客户记录相关 调用快速订单操作的位置。

当您重定向到内部 URL 时 对于您的组织,操作对话框将在完成后关闭或以编程方式关闭 导航离开。如果将重定向设置为指向外部 URL,则 由于外部 URL 在新浏览器中打开,因此行为可能会有所不同 标签。

自定义全局操作

用作全局的 Visualforce 页面 操作可以在许多不同的位置调用,并且没有特定的记录 与他们相关。他们有完全的行动自由,这意味着这取决于你 来编写代码。

更具体地说,用作全局操作的 Visualforce 页面不能使用任何标准控制器。您必须编写一个自定义控制器来处理该页面。你 代码可能会创建一条或多条记录、修改找到的记录等。

当全局操作时 完成后,用户将被重定向到作为 该动作或返回到他们开始的地方。

Visualforce 页面的性能调整

性能是移动 Visualforce 页面的一个重要方面。Visualforce 有一个 缓存机制,帮助您调整页面的性能。

若要为页面启用缓存,请使用以下语句:

<apex:page cache="true" expires="600">

页面缓存的参数包括:

属性描述
缓存指定浏览器是否应缓存页面的布尔值。如果不是 指定,缺省为 .false
到期指定缓存周期(以秒为单位)的整数值。

有关更多信息,请参阅 Developerforce 上的 Force.com 站点最佳实践

更多资源

以下是一些可帮助您调整 Salesforce 应用程序性能的更多资源:

  • 深入了解 Force.com Query Optimizer(网络研讨会)
  • 最大限度地提高 Force.com SOQL、报告和列表视图的性能(博客文章)
  • Force.com SOQL 最佳实践:空值和公式字段(博客 帖子)

将 Visualforce 添加到 Salesforce AppExchange 应用程序

您可以在自己的应用程序中包含 Visualforce 页面、组件或自定义控制器 为 AppExchange 创建。

与 Apex 类不同,托管包中 Visualforce 页面的内容不是 安装包时隐藏。但是,自定义控制器、控制器扩展和 自定义组件是隐藏的。此外,可以使用该属性将自定义组件限制为仅在命名空间中运行。access

Salesforce 建议您仅使用托管软件包来分发任何 Visualforce 或 Apex 组件。之所以提出此建议,是因为托管包接收一个唯一的命名空间,该命名空间是 自动附加到页面、组件、类、方法、变量、 等等。此命名空间前缀有助于防止安装程序的 组织。使用 Visualforce 页面创建包时,应考虑以下注意事项:

  • 如果组件上的属性是 包含在托管包中设置为 ,是 请注意以下限制:accessglobal
    • 组件上的属性不能是 更改为 。accesspublic
    • 所有必需的子组件 (将 required 属性设置为 true 的属性)必须将属性设置为 global。<apex:attribute>access
    • 如果在必需的子项上设置了该属性,则无法删除该属性,或者 改变。default<apex:attribute>
    • 您无法添加新的必需子组件。<apex:attribute>
    • 如果子组件上的属性设置为 ,则无法将其更改为 。access<apex:attribute>globalpublic
    • 如果子组件上的属性设置为 ,则无法更改该属性。access<apex:attribute>globaltype
  • 安装具有非全局组件的包时,在 安装程序:请参阅“组件不是全局的”,而不是组件的内容。在 此外,该元件不包括在元件引用中。
  • 如果为正在安装软件包的组织启用了高级货币管理, 使用和不能使用的 Visualforce 页面 安装。<apex:inputField><apex:outputField>
  • 作为 Salesforce AppExchange 应用程序的一部分包含的任何 Apex 都必须至少具有 75% 累积测试覆盖率。当您将软件包上传到 AppExchange 时,将运行所有测试 以确保它们运行没有错误。当包 安装。
  • 从版本 16.0 开始,如果您将托管 Apex 类用作 Visualforce 控制器, 此外,还需要将以下方法和属性的访问级别设置为“访问级别”,以供订阅者使用 他们:globalglobal
    • 自定义控制器的构造函数
    • Getter 和 setter 方法,包括用于输入和输出组件的方法
    • 获取和设置属性的特性

提示

如果自定义标签有翻译,请包括 通过显式打包所需的语言,在包中进行翻译。

当包含 Visualforce 页面的软件包安装到 组织,则页面从 vf.force.com、visual.force.com 或 visualforce.com 域提供 而不是 salesforce.com 域。这是为了防止恶意代码 在包中,以免影响您的数据。

管理 Visualforce 页面和组件的软件包版本设置

如果 Visualforce 标记引用已安装的托管软件包,则每个软件包的版本设置 保存 Visualforce 标记引用的托管包以帮助向后兼容。 这可确保随着托管包中的组件在后续包版本中的发展,一个 页面仍绑定到具有特定已知行为的版本。

包版本是一个数字,用于标识包中上载的组件集。这 版本号的格式为 (对于 例如,2.1.3)。在每个主要数字中,主要数字和次要数字增加到选定的值 释放。仅针对补丁版本生成和更新。 发布者可以使用包版本来正常发展其托管包中的元素 通过发布后续软件包版本,而不破坏现有的客户集成,使用 包。majorNumber.minorNumber.patchNumberpatchNumber配置 Visualforce 的包版本设置 页面或自定义组件:

  1. 编辑 Visualforce 页面或组件,然后单击版本 设置
  2. 为 Visualforce 页面或组件。此版本的托管包将继续 如果托管包的更高版本是 已安装,除非您手动更新版本设置。若要将已安装的托管包添加到设置列表,请选择一个包 从可用软件包列表中。仅当有 尚未与 页面或组件。
  3. 点击保存

使用包版本设置时,请注意以下事项:

  • 如果保存引用托管的 Visualforce 页面或自定义组件 包,而不指定托管包的版本,页面或 组件与最新安装的托管软件包版本相关联 默认情况下。
  • 您无法移除 Visualforce 页面,或者 托管包的组件版本设置(如果该包是 由页面或组件引用。使用显示 用于查找托管包位置的依赖项 引用。
  • 包订阅者可以使用包版本来引用已删除的组件。 包中的 Visualforce 页面始终使用其包的最新 API 版本。他们无法访问已删除的组件。

不同软件包中 Visualforce 页面的访问控制器

要从不同软件包中的 Visualforce 页面访问 Apex 控制器,请执行以下操作: 在 自定义控制器类。在第一代封装中,您只能开发一个托管 具有给定命名空间的包。在第二代封装中,您可以开发超过 一个具有相同命名空间的托管(或已解锁)包。默认情况下,Visualforce 页面 安装在包中的包无法从另一个包中的 Apex 类调用公共 Apex 方法 包。即使两个包位于同一命名空间中,也是如此。

@namespaceAccessible

下面是如何在 Apex 代码中包含注释的示例。@namespaceAccessible

@namespaceAccessible
public virtual class NsController {
    private String message;
    @namespaceAccessible
    public NsController() {
        this.message = 'default'; // init to non-blank value
    }
    @namespaceAccessible
    public virtual String getMessage() {
       return this.message;
    }
    @namespaceAccessible
    public virtual void setMessage(String msg) {
        this.message = msg;
    }
}

首先,在控制器上方添加注释,使其从 命名空间。您必须对控制器进行注释,以便任何方法 您添加到注释中也是可见的。然后,对于您希望可见的每个方法 在命名空间中,在方法之前添加注释。仅使用注释 对于您希望在包外部可见但在同一包内的方法 命名空间。@namespaceAccessible

使用 Visualforce 开发 Salesforce 应用程序

开发人员可以使用 Visualforce 向 Salesforce 扩展和添加新功能 移动应用程序。 使用 Visualforce 进行 Salesforce 开发,您可以访问 Salesforce 数据 并创建在 Lightning 平台上运行的集成体验。您可以创建 在桌面和移动设备之间共享的 Visualforce 页面,或 移动应用程序独有。

注意

Lightning 不支持 Visualforce 页面和自定义 iframe 在 iPad Safari 上体验。

通过针对 Salesforce 进行开发,您可以灵活地使用流程和工具 自定义您的应用程序。例如,您可以使用 Salesforce Lightning 设计系统来 创建符合以下原则、设计语言和最佳实践的应用 闪电体验。或者将 JavaScript 工具和第三方框架合并到 创建交互式用户体验。

在本节中,我们将介绍在 Salesforce 移动应用程序和最佳实践,用于创建功能强大、复杂的应用程序。

Salesforce 平台开发流程

用于在 Lightning Experience 和 Salesforce 移动应用程序中进行开发的流程 都是一样的。如果您习惯于针对 Salesforce Classic 进行开发,则 Lightning Experience 和 Salesforce 移动应用程序有一些差异,但其中大部分是 你很熟悉。

为 Salesforce 移动应用程序创建 Visualforce 页面时,请务必进行设置 正确的工具和测试环境。在本节中,我们将介绍最好的 Salesforce 移动平台开发流程的实践。

设置开发系统

Salesforce 提供了几种不同的工具和方法来编写、编辑和查看您的 法典。

选择编辑器

首先设置用于编写代码的工具。开发人员控制台、Salesforce Visual Studio Code 的扩展和安装程序编辑器在针对 Salesforce 应用程序、Lightning Experience 和 Salesforce Classic。唯一的例外是 Visualforce 开发模式页脚,仅在 Salesforce Classic 中可用。

查看 Visualforce 页面

在 Salesforce Classic 中,您可以使用 https:// yourInstance.salesforce.com/apex/PageName URL 查看页面 模式。此方法不适用于在 Lightning Experience 中查看 Salesforce 应用程序页面。 因为您使用直接 URL 访问查看的页面始终显示在 Salesforce Classic 中。

要在 Lightning Experience 中查看您的页面,请转到 https:// yourInstance.salesforce.com/lightning。这 访问特定 Visualforce 页面的最简单方法是为其创建一个选项卡,然后导航 通过应用程序启动器中的“所有项目”部分添加到该选项卡。

对于更长期的方法,请创建一个“开发中”应用,并添加和删除您的 Visualforce 在您工作时会切换到它。

  1. 在“设置”中,在“快速查找”框中输入, ,然后选择应用程序管理器Apps
  2. 单击“新建 Lightning 应用程序”,然后为您的页面创建自定义应用程序 正在开发中。注意请考虑将应用限制为仅系统管理员或配置文件 您已为组织中的开发人员创建。
  3. 在“设置”中,输入“快速查找”框,然后选择“应用菜单”。App Menu
  4. 确保“开发中”应用设置为“在应用中可见” 发射。
  5. 在“设置”中,在“快速查找”框中输入, ,然后选择选项卡Tabs
  6. 单击“Visualforce 选项卡”部分中的“新建”,然后创建自定义 选项卡,以获取当前正在开发的页面。使选项卡仅对开发用户可见 配置文件,然后仅将选项卡添加到“开发中”应用。
  7. 对要添加到“开发中”应用的每个页面重复上述步骤。

您还可以将以下书签添加到浏览器的菜单或工具栏以进行导航 直接进入您的页面。此 JavaScript 触发 Lightning Experience 事件,相当于在 经典 /apex/PageName URL。navigateToURL

javascript:(function(){ 
  var pageName = prompt('Visualforce page name:'); 
  $A.get("e.force:navigateToURL").setParams(
    {"url": "/apex/" + pageName}).fire();})();

开发过程和测试的重要性

在将 Visualforce 页面部署到生产环境之前,对其进行测试非常重要。测试 跨不同环境、设备和用户的页面。

如果您正在开发需要支持各种可能性的功能, 测试计划应考虑在以下方面进行测试的需要:

  • 每个不同的受支持设备。
  • 每个不同的受支持操作系统。
  • 每个不同的受支持浏览器,包括 Salesforce 移动应用程序,它嵌入了 有。
  • 每个不同的受支持用户界面上下文(Lightning Experience、Salesforce Classic、 和 Salesforce 移动应用程序)。

正常使用不支持在模拟器中运行 Salesforce 移动应用程序。我们了解 设备仿真器很方便。但它们不能替代对你的全面测试 组织支持的移动设备上的自定义应用和页面。在开发过程中, 定期在要部署的每个设备和平台上测试应用。

在 Salesforce 移动应用程序中测试 Visualforce 页面

如果您要创建将在 Lightning Experience、Salesforce Classic 中使用的页面、 和 Salesforce 移动应用程序,在您处理页面时查看所有环境中的页面。 要进行全面测试,请使用多个浏览器甚至多个设备来查看您的页面。你会 还希望能够访问至少一个额外的测试用户。

下面是如何设置开发环境的示例。

主要开发环境

在此环境中,您可以在安装程序中对组织进行更改,例如添加 自定义对象和字段,以及编写实际代码的位置(如果您使用 Developer) 安慰。在此环境中,查看页面在 Salesforce Classic 中的设计和行为。

  • 浏览器:
  • 用户:开发人员用户
  • 用户界面设置:Salesforce 经典

Lightning Experience 审查环境

在此环境中,您可以在 Lightning 中检查页面的设计和行为 经验。

  • 浏览器:Safari 或 Firefox
  • 用户:测试用户Your test user
  • 用户界面设置:闪电体验

Salesforce App Review 环境

此环境用于在 Salesforce 移动版中检查页面的设计和行为 应用程序。

  • 设备:iOS 或 Android 手机或平板电脑
  • 浏览器:Salesforce 应用程序
  • 用户:测试用户Your test user
  • 用户界面设置:Lightning Experience 或 Salesforce Classic

了解 Salesforce 移动应用程序容器

在 Salesforce Classic 中,Visualforce “拥有”页面, 请求和环境。Visualforce 是应用程序容器。但是在Salesforce中 移动应用程序和 Lightning Experience,Visualforce 在较大的 /lightning 容器内的 iframe 内运行。

注意

Salesforce 移动应用程序和 Lightning Experience 容器是否相同?是的,也不是。双 Salesforce 移动应用程序和 Lightning Experience 容器是 /lightning 容器的分支,为一个容器编写的代码可在另一个容器中使用。 但是容器的幕后工作方式略有不同。Salesforce 移动版 应用程序在移动设备的移动浏览器中运行,而 Lightning Experience 应用程序在 标准桌面浏览器中的桌面计算机。我们优化了发送到每个上下文的 /lightning 版本,以及 它运行的也足够不同,足以引起注意。简而言之,将它们视为不同的容器 具有几乎相似的功能。

外部容器和内部 iframe

外部 Salesforce 应用程序容器是通过 /lightning URL 访问的单页应用程序。加载 /lightning 页面,启动其代码,然后 应用程序代码接管环境。

Visualforce 页面在 HTML iframe 中运行,这实质上是一个单独的浏览器 主 /lightning 浏览内容中的窗口。

Salesforce 应用程序是父上下文,Visualforce 页面是子上下文。那 意味着 Visualforce 页面在 /lightning 外部容器的约束下工作,同时仍与 iframe。

Visualforce for Salesforce 应用程序代码注意事项

如果可能,请创建无论用户界面如何都能正常运行的 Visualforce 页面 上下文。通常,您为 Salesforce Classic 编写的 Visualforce 代码在 Salesforce 移动应用程序。但是,在某些情况下,需要对 由于容器,适用于移动设备的 Visualforce 页面。

安全注意事项

可能受影响的安全元素包括:

  • 会话维护和续订
  • 认证
  • 跨域请求
  • 嵌入限制

特别是,请注意会话维护,或管理浏览器使用的令牌 为每个请求输入用户名和密码的位置。您经常需要访问 当前会话使用全局变量 $Api.Session_ID。$Api.Session_ID 返回不同的值,具体取决于 请求,并且 Salesforce 移动应用程序和 Visualforce 页面从不同的域提供。 由于 Visualforce iframe 内部的会话 ID 与外部的会话 ID 不同, 在 Salesforce 移动应用程序容器中,这可能会更改您管理会话 ID 的方式。

范围注意事项

以下范围元素可能需要调整:

  • DOM 访问和修改
  • JavaScript 范围、可见性和访问权限
  • JavaScript 全局变量,例如window.location

简而言之,Visualforce 页面中的 JavaScript 代码只能影响 iframe 的浏览器上下文,而不是父上下文。

Salesforce 移动应用程序容器中要避免的功能

Salesforce 移动应用程序容器可防止选定数量的 Visualforce 组件 在 Salesforce 移动应用程序中按预期运行。

  • 避免在 Visualforce 页面上使用 在 Salesforce 移动应用程序容器中。仅当您真正了解 iframe 时才使用此标记 以及它们如何影响 DOM 和 JavaScript。<apex:iframe>
  • 避免使用 或 等元素来访问父浏览器上下文,因为 Visualforce 和 Salesforce 移动应用程序由不同的域提供服务。contentWindowwindow.parent
  • 避免直接设置,因为 Visualforce iframe 无法直接访问 .window.locationwindow.location
  • 避免使用硬编码的 URL 来访问使用静态模式构建的 Salesforce 资源,例如 link = ‘/’ + accountId + ‘/e’。相反,在 Visualforce 标记中,请使用 ,在 JavaScript 中,请使用 .{!URLFOR($Action.Contact.Edit, recordId)}navigateToSObject(recordId)

告诉我更多:Visualforce 页面可以在哪些方面发挥作用 显示在 Salesforce 移动应用程序中

创建 Visualforce 页面时,可以从以下几个位置访问该页面 在用户界面中的位置。

  • 默认导航菜单,称为“仅限移动设备”,在以下情况下可用 您点击Salesforce 移动应用程序导航菜单图标屏幕底部的导航栏
  • 操作栏和操作菜单 – 可从任何页面的顶部使用 支持操作

您还可以引用并链接到 Visualforce 标记使用带有 sforce.one 对象的导航中列出的导航调用。请务必 选择可用于 Lightning Experience、Experience Builder 站点和 多页面流程中所有页面的移动应用程序。

如果引用的页面没有“可用于 Lightning” 体验、Experience Builder 站点和所选的移动应用程序,它不会阻止显示引用页面或父页面。 但是,当用户尝试访问未启用移动设备的页面时,他们会收到一个 “不支持的页面”错误消息。

准则和最佳实践

在 Salesforce 移动应用程序中,Visualforce 页面不会自动适合移动设备。这 标准 Salesforce 标题和侧边栏被禁用,取而代之的是移动控件,并且 JavaScript API 可用于使 Visualforce 页面能够与移动设备连接 导航管理。在其他方面,页面保持原样,尽管可以在 应用程序,以桌面为中心的 Visualforce 页面将感觉以桌面为中心。

幸运的是,让您的应用程序在 Salesforce 移动应用程序中看起来很棒很简单。你 可以修改您的代码,以便您的页面在完整的 Salesforce 站点和 移动应用程序,或者您可以创建特定于移动设备的页面。

注意

Lightning 不支持 Visualforce 页面和自定义 iframe 在 iPad Safari 上体验。在本章中,你将了解如何执行以下操作的最佳实践:

  • 在移动设备和桌面设备之间共享 Visualforce 页面。
  • 从移动设备或桌面设备中排除 Visualforce。
  • 为您的 Visualforce 页面选择最佳架构。
  • 为您的页面选择有效的页面布局。
  • 管理用户输入和导航。
  • 使用 Visualforce 页面作为自定义操作。
  • 调整页面以获得最佳性能。
  • 在移动版和桌面
    版之间共享 Visualforce 页面 修订显示在 Salesforce 移动应用程序和完整 Salesforce 站点中的 Visualforce 页面,以支持这两种环境。这包括用作自定义操作的 Visualforce 页面和添加到标准页面布局的 Visualforce 页面。
  • 从移动版或桌面
    版中排除 Visualforce 页面 要将 Visualforce 页面添加到 Salesforce 移动应用程序或整个 Salesforce 站点,请使用选项卡和导航设置。
  • 创建在移动和桌面
    上工作的 Visualforce 页面 通过编写适应其运行环境的代码,创建在 Salesforce 移动应用程序和整个 Salesforce 站点中都能正常运行的 Visualforce 页面。
  • 在 Salesforce 移动应用程序
    中为 Visualforce 页面选择架构 有几种方法可以设计和构建 Visualforce 页面,每种方法在开发时间、所需的开发人员技能以及您希望自定义功能与 Salesforce 移动应用程序匹配的程度方面都有不同的权衡。
  • 在 Salesforce 移动应用程序
    中优化 Visualforce 页面的性能 Visualforce 旨在为开发人员提供与标准 Salesforce 页面的功能、行为和性能相匹配的能力。如果您的用户遇到延迟、意外行为或其他专门围绕 Visualforce 的问题,您可以采取一些措施来改善他们的体验,还可以改进编码。在 Salesforce 移动应用程序中,遵循优化的最佳实践非常重要。移动设备的计算资源更加有限,用户希望应用程序速度更快、响应迅速。
  • Salesforce 移动应用程序中应避免的 Visualforce 组件和功能
    大多数核心 Visualforce 组件(命名空间中的组件)在 Salesforce 移动应用程序中正常运行。不幸的是,这并不意味着它们针对移动设备进行了优化,或者每个功能都适用于该应用程序。您可以通过遵循一些简单的规则来改善 Visualforce 页面的移动用户体验。apex
  • 已知的 Visualforce Mobile 问题 Salesforce 发布已知问题
    以增强信任并支持客户成功。
  • 在 Salesforce 移动应用程序中使用 Visualforce 的注意事项和限制 Visualforce 允许开发人员构建复杂的自定义用户界面,这些用户界面可以本地托管在 Lightning 平台上。
    Visualforce 是 Salesforce 久经考验的模型,使开发人员能够访问数据以及强大的工具和功能。在 Salesforce 移动应用程序中使用 Visualforce 有很多好处,但也有一些限制。
  • 针对 Salesforce 应用程序中的 Visualforce 页面问题准备支持请求 Salesforce 提供资源来帮助开发人员找到问题的答案并解决问题。
    我们建议您首先查看开发人员论坛、Salesforce Stack Exchange 和已知问题页面,看看您是否可以立即找到问题的解决方案。如果您的问题仍未得到解答,您可以向 Salesforce 的支持团队提交案例,该团队会将您的问题发送给最合适的人来回答。
  • 选择有效的页面布局 通过使用适合页面使用上下文的页面布局
    ,设计在 Salesforce 移动应用程序中看起来不错且运行良好的 Visualforce 页面。添加为主导航选项卡或操作栏中的自定义操作的页面几乎可以使用设备的全屏,并且可以垂直滚动,而添加到对象页面布局的 Visusalforce 必须适合特定的有限空间。
  • 用户输入和交互
    使用 、 属性和传递 HTML 属性创建适合移动设备的表单和用户界面,这些表单和用户界面高效且利用本机移动浏览器功能。<apex:input>type
  • 管理导航
    Salesforce 移动应用程序使用事件管理导航。导航事件框架以 JavaScript 对象的形式提供,该对象提供了许多实用函数,使创建“正常工作”的编程导航变得轻而易举。其优点是导航体验对于移动环境来说更自然。它还使 Salesforce 开发人员可以更轻松地创建完成后导航,例如在成功提交订单后重定向到订单页面。
  • Salesforce Lightning Design System 简介 Salesforce Lightning Design System
    (SLDS) 可帮助您构建具有 Lightning Experience 外观的应用程序,而无需编写任何一行 CSS。SLDS 是一个 CSS 框架,可让您访问我们的开发人员用于创建 Lightning Experience 的图标、调色板和字体。
  • 将 Visualforce 页面用作自定义操作 如果您的 Visualforce 页面用作自定义操作,请将其设计为对标准控制器提供的单个记录执行操作
    ,或查找并执行记录,或记录检索到的自定义控制器代码。
  • Visualforce 页面的性能调整 性能是移动 Visualforce 页面
    的一个重要方面。Visualforce 具有缓存机制,可帮助您调整页面的性能。

在移动设备和桌面设备之间共享 Visualforce 页面

修改 Salesforce 移动应用程序中显示的 Visualforce 页面和完整版 Salesforce 站点支持这两种环境。这包括用作自定义的 Visualforce 页面 操作和 Visualforce 页面已添加到标准页面布局中。

需要在两种环境中工作的 Visualforce 页面包括:

  • 用作自定义操作的页面。自定义操作显示在 Salesforce 的操作栏中 移动应用程序,以及完整 Salesforce 站点的发布者菜单中。
  • 当可用于 Lightning Experience 时,添加到正常页面布局的页面, Experience Builder 站点,并且已为页面启用移动应用程序。
  • 添加到正常页面布局的自定义 Visualforce 按钮或链接。
  • 标准按钮覆盖 Visualforce 页面,用于新建、编辑、查看、删除和 克隆操作。应用中不支持重写标准列表和选项卡控件。按钮 覆盖不会出现在应用程序中,除非可用于 Lightning Experience, Experience Builder 站点,并且已为页面启用移动应用程序。注意被 Visualforce 页面覆盖的标准按钮将从记录中消失 应用程序中的详细信息页面和记录列表(如果可用于 Lightning) 未选择体验、Experience Builder 站点和移动应用程序 覆盖相应按钮的 Visualforce 页面。

注意

Lightning 不支持 Visualforce 页面和自定义 iframe 在 iPad Safari 上体验。

从移动设备或桌面设备中排除 Visualforce 页面

将 Visualforce 页面添加到 Salesforce 移动应用程序或整个 Salesforce 站点,使用选项卡和导航设置。

可配置为仅限桌面或仅限移动设备的 Visualforce 页面包括:

  • 当可用于 Lightning Experience 时,添加到正常页面布局的页面, Experience Builder 站点,并且该页面的移动应用程序已禁用。仅这些 显示在完整的 Salesforce 站点中。
  • Visualforce 选项卡中使用的页面。将选项卡添加到移动导航中与添加选项卡是分开的 到完整的 Salesforce 站点导航。

创建适用于移动设备和 桌面

创建在 Salesforce 移动应用程序和 通过编写适应其运行环境的代码来完整 Salesforce 站点。

Salesforce 移动应用程序提供了一个用于处理各种导航控件的框架 和事件。当 Visualforce 页面在 完整的 Salesforce 站点,因为对象 仅注入到应用程序内的页面上。这意味着,对于共享的页面 Salesforce 移动应用程序和完整的 Salesforce 站点,您需要编写以下代码: 在对象可用时使用它,并且 标准 Visualforce 导航,如果不是。sforcesforce例如,下面是在 JavaScript 远程处理请求之后运行的一些 JavaScript 从创建快速订单的方法成功返回。此代码来自用作 自定义操作,将其添加到 Salesforce 移动应用程序中的操作栏,然后 完整 Salesforce 站点中的发布者菜单。它需要在这两个地方工作。意图 的代码是导航到订单所在帐户的详细信息页面 放置:

@RemoteAction

// Go back to the Account detail page
if( (typeof sforce != 'undefined') && sforce && (!!sforce.one) ) {
    // Salesforce app navigation
    sforce.one.navigateToSObject(aId);
}
else {
    // Set the window's URL using a Visualforce expression
    window.location.href = 
        '{!URLFOR($Action.Account.View, account.Id)}';
}

该语句检查对象是否可用和可用。这只是 如果页面在应用内运行,则为 true。如果可用,则使用移动导航管理系统进行 到帐户的详细信息页面。ifsforcesforce

如果对象不可用,请尝试 使用它导航到任何地方会导致 JavaScript 错误,并且没有导航。所以 相反,该代码使用 Visualforce 表达式设置窗口的 URL,该表达式返回 帐户详细信息页面的 URL。您不想在应用程序中执行此操作,因为 框架将丢失导航事件,但在正常情况下是必需的 视觉力。sforce

注意

最佳做法是将此类常见测试分解到他们自己的帮助程序中 功能。您可以向 JavaScript 静态资源添加如下内容: 然后直接打电话 您的 if 条件。然后,如果检测逻辑发生变化,只需在 一 地方。ForceUI.isSalesforce1()

(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};

    myContext.ForceUI.isSalesforce1 = function() {
        return((typeof sforce != 'undefined') && sforce && (!!sforce.one));
    }
})(this);

在 Salesforce Mobile 中为 Visualforce 页面选择架构 应用程序

有几种方法可以设计和构建 Visualforce 页面,每种方法都有不同的 在开发时间、所需的开发人员技能以及您的彻底程度方面进行权衡 希望您的自定义功能与 Salesforce 移动应用程序相匹配。对页面结构使用以下方法之一:

  • 标准 Visualforce 页面
  • 混合 Visualforce 和 HTML
  • JavaScript 远程处理和静态 HTML
  • 标准 Visualforce 页面 普通 Visualforce 页面
    在移动浏览器上呈现良好,可以按原样使用,与移动优化的网页相比,用户体验略有降低。页面的显示方式与在完整 Salesforce 站点上的显示方式相同,在视觉上与其他 Salesforce 应用程序功能不匹配。
  • 混合 Visualforce 和 HTML 将表单元素和输出文本的 Visualforce 标记与页面结构的静态 HTML
    相结合,以创建更适合移动设备的页面,使其更接近 Salesforce 移动应用程序的视觉设计。对于仅限移动设备的页面,您可以快速转换现有的 Visualforce 页面,但这不适用于同时在 Salesforce 移动应用程序和整个 Salesforce 站点中使用的页面。
  • JavaScript 远程处理和静态 HTML 将 JavaScript 远程处理和静态 HTML
    相结合,以提供最佳的用户体验,并提供与 Salesforce 移动应用程序匹配的最佳性能和用户界面。此体系结构避免了大多数 Visualforce 标记,而是在 JavaScript 中呈现页面元素。此选项需要最多的开发人员专业知识,并且设置时间可能比标准 Visualforce 或混合 Visualforce 和 HTML 要长一些。使用 Salesforce Mobile Pack 快速入门,并使用最新的移动 Web 应用程序技术。

标准 Visualforce 页面

普通的 Visualforce 页面在移动浏览器上呈现良好,可以按原样使用,使用 与移动优化网页相比,用户体验略有降低。页面显示为 它们将出现在完整的 Salesforce 站点上,并且在视觉上与其他 Salesforce 应用程序不匹配 特征。

局限性

用户体验的局限性包括:

  • 点击目标(按钮、链接、表单域等)针对鼠标进行了优化 光标,并且很难用指尖准确击中。
  • 视觉设计保持不变,可能不适合针对移动设备优化的现代视觉效果 Salesforce 移动应用程序的设计。

如果开发时间线过于紧迫,可能会发现这些限制 可以接受。

标准 Visualforce 页面示例

以下代码提供了标准 Visualforce 页面的示例,该页面允许用户 编辑仓库记录。编辑功能由标准控制器提供,用于 对象。

<apex:page standardController="Warehouse__c">

<apex:form>

  <apex:pageBlock title="{! warehouse__c.Name }">

    <apex:pageBlockSection title="Warehouse Details" columns="1">
      <apex:inputField value="{! warehouse__c.Street_Address__c }"/>
      <apex:inputField value="{! warehouse__c.City__c }"/>
      <apex:inputField value="{! warehouse__c.Phone__c }"/>
    </apex:pageBlockSection>
        
    <apex:pageBlockButtons location="bottom">
      <apex:commandButton action="{! quickSave }" value="Save"/>
    </apex:pageBlockButtons>
    
  </apex:pageBlock>

</apex:form>

</apex:page>

这 页面可以在 Salesforce 移动应用程序和完整的 Salesforce 站点中使用。它显示 作为两种上下文中的标准桌面 Visualforce 页面。

混合 Visualforce 和 HTML

将表单元素的 Visualforce 标签和输出文本与页面的静态 HTML 相结合 结构来创建更适合移动设备的页面,这些页面更接近 Salesforce 移动应用程序。对于仅限移动设备的页面,您可以快速转换现有的 Visualforce 页面,但这不适用于同时用于 Salesforce 移动应用程序和完整的 Salesforce 站点。

以这种方式设计的 Visualforce 页面仍然是“标准”的 Visualforce,在 他们使用标准请求-响应周期,标准控制器功能,用于表单字段, POSTBACK 和视图状态等。与创作页面的主要区别 完整的 Salesforce 站点是减少或消除对 Visualforce 标签的使用添加 结构,以支持静态 HTML。也就是说,用 、 、 等替换 、 、 等。<apex:inputField><apex:pageBlock><apex:pageBlockSection><div><p><span>

这种方法还需要创建 CSS 样式表来管理 页面元素,而不是使用内置的、自动应用的样式,当 您使用 Visualforce 组件。虽然这可能需要一些时间,但它可以让您做很多事情 更接近 Salesforce 移动应用程序的视觉设计。这也意味着 以这种方式设计的页面在视觉上与整个 Salesforce 站点匹配。

将此方法应用于 Visualforce 页面

要使用此方法创建要在 Salesforce 移动应用程序中使用的页面,请按照 一般规则很少。

  • 请勿使用以下 Visualforce 标记:
    • <apex:pageBlock>
    • <apex:pageBlockButtons>
    • <apex:pageBlockSection>
    • <apex:pageBlockSectionItem>
    • <apex:pageBlockTable>
  • 将 、 或 和 用于表单。<apex:form><apex:inputField><apex:input><apex:outputLabel>
  • 使用或 Visualforce 对于不可编辑的文本。<apex:outputText>
  • 使用首选的 HTML 来构造页面的结构:、、、、等。<div><span><h1><p>
  • 使用 CSS 样式来应用您喜欢的视觉设计。

优点和局限性

这种方法的优点包括:

  • 相当快的开发时间,并且您使用普通的 Visualforce 开发工具和流程。
  • 重新利用现有页面相当容易。
  • 您可以更紧密地匹配 Salesforce 移动应用程序的外观。

要记住的一些限制:

  • 这种方法使通常的 Visualforce 请求往返,具有更大的 数据有效负载,与使用 JavaScript 远程处理的完全移动优化方法相比。
  • 添加自动替换样式的 CSS 样式是一项额外的工作 添加者 和 相关组件。<apex:pageBlock>

混合 Visualforce 和 HTML 页面的示例

以下代码示例显示了一个混合的 HTML 和 Visualforce 页面,该页面允许用户 编辑仓库记录。编辑功能由标准控制器提供 对于 对象。

<apex:page standardController="Warehouse__c">

<style>
    html, body, p { font-family: sans-serif; }
</style>

<apex:form >

    <h1>{!Warehouse__c.Name}</h1>

    <h2>Warehouse Details</h2>

    <div id="theForm">
        <div>
            <apex:outputLabel for="address" value="Street Address"/>
            <apex:inputField id="address" 
                value="{! warehouse__c.Street_Address__c}"/>
        </div>
        <div>
            <apex:outputLabel for="city" value="City"/>
            <apex:inputField id="city" 
                value="{! warehouse__c.City__c}"/>
        </div>
        <div>
            <apex:outputLabel for="phone" value="Phone"/>
            <apex:inputField id="phone" 
                value="{! warehouse__c.Phone__c}"/>
        </div>
    </div>

    <div id="formControls">
        <apex:commandButton action="{!quickSave}" value="Save"/>
    </div>

</apex:form>

</apex:page>

这 页面可以在 Salesforce 移动应用程序和完整的 Salesforce 站点中使用。它 在整个 Salesforce 站点上显示为标准页面,但没有完整的 表单的 Salesforce 样式。在 Salesforce 移动应用程序中,它大致显示 与 Salesforce 移动应用程序的视觉风格相匹配。使用其他样式,页面 可以近似两个版本的视觉样式。

JavaScript 远程处理和静态 HTML

将 JavaScript 远程处理和静态 HTML 相结合,以提供最佳的用户体验,同时 与 Salesforce 移动应用程序相匹配的最佳性能和用户界面。这 架构避免了大多数 Visualforce 标记,而是在 JavaScript 中呈现页面元素。 此选项需要最多的开发人员专业知识,并且可能需要更长的时间来设置。 比标准 Visualforce 或混合 Visualforce 和 HTML。将 Salesforce Mobile Pack 用于 快速起步,并使用最新的移动 Web 应用程序技术。

重要

在可能的情况下,我们更改了非包容性条款,以符合我们的 平等的公司价值观。我们保留了某些条款,以避免对 客户实施。

以这种方式设计的 Visualforce 页面避开了许多自动、简化的功能 标准 Visualforce,有利于对请求-响应周期进行更多控制, 并使用 JavaScript 而不是页面重新加载来执行页面更新。这可以 大幅提高页面的性能,尤其是在较低的带宽上, 更高延迟的无线网络连接,使移动设备变得如此移动。 缺点是要编写的代码更多,并且您需要 JavaScript 方面的专业知识, JavaScript 远程处理、HTML5、您的移动工具包和 CSS,以及 Apex 和 视觉力。缺点的优点是,您正在使用最新、最 用于移动开发的高级工具,以及您可以构建的页面是最好的,大多数 “管理单元”自定义功能的完整方式,该功能与 应用程序。

您可以使用此方法构建桌面 Visualforce 页面以及 Salesforce 移动应用程序。甚至可以在两者之间共享此类页面 环境,尽管要紧密匹配 完整的 Salesforce 网站外观。最重要的是,你设计的页面可以完全 响应迅速,适应各种设备和外形规格。

将此方法应用于 Visualforce 页面

要使用此方法为 Salesforce 移动应用程序创建页面,请按照以下步骤操作 一般流程:

  1. 将您首选的 Salesforce Mobile Pack(在 Salesforce 上提供)作为静态资源安装到您的组织中。
  2. 将页面的 docType 设置为 。 强烈建议禁用标准样式表和标头。例如:html-5.0<apex:page standardController="Warehouse__c" extensions="WarehouseEditor" showHeader="false" standardStylesheets="false" docType="html-5.0">
  3. 将所选移动工具包中的脚本和样式添加到页面中 Visualforce 资源标记。为 例:<apex:includeScript value="{!URLFOR( $Resource.Mobile_Design_Templates, 'Mobile-Design-Templates-master/common/js/ jQuery2.0.2.min.js' )}"/>
  4. 使用 HTML5 和移动工具包的标签和属性创建页面 骨架。
  5. 将 JavaScript 函数作为处理程序添加到页面以响应用户交互。 使用 JavaScript 远程处理可以 调用 Apex 方法 检索记录、执行 DML 等。@RemoteAction
  6. 添加其他 JavaScript 函数以处理用户操作和页面更新。 通过在 JavaScript 中构造 HTML 元素来执行页面更新,然后 将它们添加或附加到页面骨架中。

JavaScript 远程处理和静态 HTML 页面的示例

以下代码示例显示了一个远程处理 + HTML Visualforce 页面,该页面允许用户 编辑仓库记录。编辑功能由控制器扩展提供 使用响应 JavaScript 远程处理 请求。

@RemoteAction

<apex:page standardController="Warehouse__c" extensions="WarehouseEditor"
    showHeader="false" standardStylesheets="false"
    docType="html-5.0" applyHtmlTag="false" applyBodyTag="false">

    <!-- Include Mobile Toolkit styles and JavaScript -->
    <apex:stylesheet 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/css/app.min.css')}"/>
    <apex:includeScript 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/js/jQuery2.0.2.min.js')}"/>
    <apex:includeScript 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/js/jquery.touchwipe.min.js')}"/>
    <apex:includeScript 
      value="{!URLFOR($Resource.Mobile_Design_Templates,
      'Mobile-Design-Templates-master/common/js/main.min.js')}"/>

<head>
<style>
    html, body, p { font-family: sans-serif; }
    input { display: block; }
</style>

<script>
    $(document).ready(function(){
        // Load the record
        loadWarehouse();
    });

    // Utility; parse out parameter by name from URL query string
    $.urlParam = function(name){
        var results = new RegExp('[\\?&]' + name + '=([^&#]*)')
            .exec(window.location.href);
        return results[1] || 0;
    }

    function loadWarehouse() {
        // Get the record Id from the GET query string
        warehouseId = $.urlParam('id');

        // Call the remote action to retrieve the record data
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.WarehouseEditor.getWarehouse}',
            warehouseId,
            function(result, event){;
                if(event.status){
                    console.log(warehouseId);
                    $('#warehouse_name').text(result.Name);
                    $('#warehouse_address').val(
                      result.Street_Address__c);
                    $('#warehouse_city').val(result.City__c);
                    $('#warehouse_phone').val(result.Phone__c);
                } else if (event.type === 'exception'){
                    console.log(result);
                } else {
                    // unexpected problem...
                }
        });
    }

    function updateWarehouse() {
        // Get the record Id from the GET query string
        warehouseId = $.urlParam('id');

        // Call the remote action to save the record data
        Visualforce.remoting.Manager.invokeAction(
            '{!$RemoteAction.WarehouseEditor.setWarehouse}',
            warehouseId, $('#warehouse_address').val(),
                $('#warehouse_city').val(), 
                $('#warehouse_phone').val(),
            function(result, event){;
                if(event.status){
                    console.log(warehouseId);
                    $('#action_status').text('Record updated.');
                } else if (event.type === 'exception'){
                    console.log(result);
                    $('#action_status').text(
                      'Problem saving record.');
                } else {
                    // unexpected problem...
                }
        });
    }

</script>
</head>

<body>

<div id="detailPage">
    <div class="list-view-header" id="warehouse_name"></div>
    <div id="action_status"></div>

    <section>
        <div class="content">
            <h3>Warehouse Details</h3>
            <div class="form-control-group">
                <div class="form-control form-control-text">
                    <label for="warehouse_address">
                        Street Address</label>
                    <input type="text" id="warehouse_address" />
                </div>
                <div class="form-control form-control-text">
                    <label for="warehouse_city">City</label>
                    <input type="text" id="warehouse_city" />
                </div>
                <div class="form-control form-control-text">
                    <label for="warehouse_phone">Phone</label>
                    <input type="text" id="warehouse_phone" />
                </div>
            </div>
        </div>
    </section>

    <section class="data-capture-buttons one-buttons">
        <div class="content">
            <section class="data-capture-buttons one-buttons">
                <a href="#" id="updateWarehouse"
                    onClick="updateWarehouse();">save</a>
            </section>
        </div>
    </section>
</div> <!-- end detail page -->

</body>

</apex:page>

这 静态 HTML 提供页面的 shell,包括空表单字段。JavaScript的 函数加载记录,填写表单字段,并发送回更新的表单数据 到 Salesforce。

虽然此页面可以在完整的 Salesforce 站点中使用,但它被设计为 Salesforce 应用程序页面,看起来与普通的 Visualforce 页面有很大不同。

JavaScript 远程处理和静态 HTML 控制器的示例

与创建移动页面的其他两种方法不同,远程处理 + HTML 方法不使用标准控制器功能从中检索数据 并将数据保存到 Salesforce。相反,您可以创建控制器扩展或自定义 控制器,用于添加任何方法 您的页面需要。下面是一个简化的控制器扩展,支持 以上 页。

@RemoteAction

global with sharing class WarehouseEditor {

    // Stub controller
    // We're only using RemoteActions, so this never runs
    public WarehouseEditor(ApexPages.StandardController ctl){ }

    @RemoteAction
    global static Warehouse__c getWarehouse(String warehouseId) {

        // Clean up the Id parameter, in case there are spaces
        warehouseId = warehouseId.trim();

        // Simple SOQL query to get the warehouse data we need
        Warehouse__c wh = [
            SELECT Id, Name, Street_Address__c, City__c, Phone__c
            FROM Warehouse__c
            WHERE Id = :warehouseId];

        return(wh);
    }

    @RemoteAction
    global static Boolean setWarehouse(
        String whId, String street, String city, String phone) {

        // Get the warehouse record for the Id
        Warehouse__c wh = WarehouseEditor.getWarehouse(whId);

        // Update fields
        // Note that we're not validating / sanitizing, for simplicity
        wh.Street_Address__c = street.trim();
        wh.City__c = city.trim();
        wh.Phone__c = phone.trim();

        // Save the updated record
        // This should be wrapped in an exception handler
        update wh;

        return true;
    }
}

在 Salesforce 中优化 Visualforce 页面的性能 移动应用

Visualforce 旨在为开发人员提供匹配 标准 Salesforce 页面的功能、行为和性能。如果您的用户体验 延迟、意外行为或其他专门围绕 Visualforce 的问题,有几个 您可以采取的措施不仅可以改善他们的体验,还可以改进编码。 在 Salesforce 移动应用程序中,遵循优化的最佳实践非常重要。移动 设备的计算资源更加有限,用户期望快速、响应迅速 应用。

有关更多指南,请参阅 Visualforce 性能:最佳实践指南。

视觉力

  • 请勿使用 或 ,这会增加页面的视图状态 大小。视图状态是维护 Visualforce 页面状态的加密数据。它已发送 在每个页面请求中来回切换,从而增加请求和响应的大小。大 视图状态会减慢页面的响应时间。<apex:form><apex:inputField>
  • 用于将必要的数据发送到 呈现页面时的浏览器。此过程可缩短页面加载时间。<apex:repeat>
  • 设置为 为 Visualforce 页面启用缓存。<apex:page cache=”true” expires=”600″>

CSS 和 JavaScript

  • 创建单页应用程序 (SPA) 而不是多页应用程序。考虑使用 用于构建 SPA 的 JavaScript 和第三方框架。
  • 使用压缩器缩小 CSS 和 JavaScript 代码。
  • 避免使用影响页面性能的 CSS 技术,例如投影或渐变。
  • 将语句移到 Visualforce 页面。通过在结束标记之前加载脚本,页面可以先下载其他组件并呈现页面 逐步。<script></body>

图像

  • 使用更少和更小的图像。
  • 压缩所有图像。
  • 使用 PNG 或 JPG 图像,而不是 GIF。
  • 使用 CSS 精灵而不是图像。

一般最佳实践

  • 使用延迟加载。延迟加载是一种首先加载页面关键功能的技术,并且 稍后或用户需要信息时剩余数据。
  • 使用无限滚动。无限滚动是一种加载其他页面内容的技术 仅当用户接近内容末尾时。

Salesforce 中应避免的 Visualforce 组件和功能 移动应用

大多数核心 Visualforce 组件(命名空间中的组件)在 Salesforce 中正常运行 移动应用程序。不幸的是,这并不意味着它们针对移动设备进行了优化,或者每个 功能适用于应用程序。您可以改善 Visualforce 的移动用户体验 页面遵循一些简单的规则。

apex

通常,避免使用结构组件(如子组件)以及其他模仿 Salesforce 外观,例如 .如果必须使用这些组件,请将它们设置为 一列,使用 ,而不是默认的两列。<apex:pageBlock><apex:pageBlockTable><apex:pageBlockSection columns=”1″>

避免使用较宽的非包装组件,尤其是 、 、 和 ,它们都不受支持。保持设备宽度 使用 创建表时要注意。<apex:detail><apex:enhancedList><apex:listViews><apex:relatedList><apex:dataTable>

避免使用 .内嵌 编辑是一种用户界面模式,适用于基于鼠标的桌面应用,但 在基于触摸的设备上很难使用,尤其是在屏幕所在的手机上 小。<apex:inlineEditSupport>

对字段使用是可以的 显示为基本输入字段,如文本、电子邮件和电话号码,但避免 将其用于使用输入小组件的字段类型,例如日期和查阅表格字段。<apex:inputField>

不要使用 . Salesforce 移动应用程序中的任何位置都不支持 sControls。<apex:scontrol>

通过设置 on 的 PDF 呈现不支持 中的页面 Salesforce 移动应用程序。renderAs=”PDF”<apex:page>

  • 不支持的 Visualforce 组件 以下是 Salesforce 移动应用程序不支持的 Visualforce 组件列表,这些组件
    不应在与 Salesforce 移动应用程序一起使用的 Visualforce 页面中使用。

不支持的 Visualforce 组件

以下是 Salesforce 移动版不支持的 Visualforce 组件列表 应用程序,并且不应用于将与 Salesforce 移动版一起使用的 Visualforce 页面 应用程序。

  • <analytics:reportChart>
  • <apex:detail>
  • <apex:emailPublisher>
  • <apex:enhancedList>
  • <apex:flash>
  • <apex:inputField>对于使用 用于输入的小部件,而不是基本的表单字段
  • <apex:listViews>
  • <apex:logCallPublisher>
  • <apex:relatedList>
  • <apex:scontrol>
  • <apex:sectionHeader>
  • <apex:selectList>对于选择列表字段
  • <apex:tabPanel>(因此,<apex:tab>)
  • <apex:vote>

警告

嵌入的 Visualforce 页面,即添加到页面的页面 布局 – 包含组件可能会导致 Salesforce 移动应用程序在 iOS 上崩溃。<apex:enhancedList>

命名空间外的标准组件,用于 示例、 、 等在应用中不受支持。apex<liveagent:*><chatter:*>

自定义组件可以在应用程序中的 Visualforce 中使用,只要它们本身不使用 不支持的组件。

使用 Visualforce 进行模板制作

Visualforce 提供了多种策略,用于在 多个 Visualforce 页面。您选择的方法取决于您需要的灵活性 重用模板。模板方法越灵活, 此外,还可以修改使用该方法的任何模板实现。 可以使用以下模板方法,按大多数顺序排列 最不灵活:定义自定义组件类似于封装一件作品的方式 代码,然后在 程序中,您可以将通用设计模式封装在自定义组件中 ,然后在一个或多个 Visualforce 页面中多次重复使用该组件。定义自定义组件是最灵活的模板 方法,因为它们可以包含任何有效的 Visualforce 标签,并且可以不受限制地导入到任何 Visualforce 页面中。但是,不应使用自定义组件来定义可重用的 Visualforce 页面。如果要重复使用整个 Visualforce 页面的内容,请选择其他两种模板方法之一。使用 <apex:composition> 定义模板如果要定义一个基本模板,该模板允许 要随每个实现而更改的模板,请使用组件。 此模板方法最适合需要维护的情况 一个页面的整体结构,但需要单个内容 页面要不同,比如一个网站的新闻文章不同 文章应以相同的页面布局显示。<apex:composition>通过这种技术,您还可以从 控制器返回的 PageReference。使用 <apex:include> 引用现有页面如果要将 Visualforce 页面的全部内容插入到另一个页面中,请使用该组件。这 模板方法最适合想要复制的情况 多个区域中的相同内容,例如显示的反馈表单 在网站的每个页面上。<apex:include>

仅当您想要引用已存在的 Visualforce 页面时,才应使用使用模板。如果只需要复制一组组件, 使用自定义组件。<apex:insert><apex:composition>

使用 <apex:composition> 定义模板

使用定义的所有模板都必须具有一个或多个子标记。标记指示导入模板的页面,该部分需要 一个定义。任何导入模板的 Visualforce 页面都必须用于指定模板每个部分的内容。<apex:composition><apex:insert><apex:insert><apex:composition><apex:define><apex:insert>

您可以创建一个框架模板,该模板允许后续 Visualforce 页面在同一标准结构中实现不同的内容。 为此,请使用该标记创建模板页面。<apex:composition>

下面的示例演示如何使用 、 和 实现框架 模板。<apex:composition><apex:insert><apex:define>首先,创建一个名为 的空页面,该页面使用名为

myFormCompositioncompositionExample

<apex:page controller="compositionExample">

</apex:page>

保存页面后,会出现一个提示,要求您创建 .使用以下命令 用于定义该自定义控制器的代码:

compositionExample

public class compositionExample{

    String name;
    Integer age;
    String meal;
    String color;
    
    Boolean showGreeting = false;
    
    public PageReference save() {
        showGreeting = true;
        return null;
    }
    
    public void setNameField(String nameField) {
        name = nameField;
    }
    
    public String getNameField() {
        return name;
    }
    
    public void setAgeField(Integer ageField) {
        age= ageField;
    }
    
    public Integer getAgeField() {
        return age;
    }
    
    public void setMealField(String mealField) {
        meal= mealField;
    }
    
    public String getMealField() {
        return meal;
    }   
         
    public void setColorField(String colorField) {
        color = colorField;
    }
    
    public String getColorField() {
        return color;
    }       
    
    public Boolean getShowGreeting() {
        return showGreeting;
    }
}

接下来,返回并创建骨架模板:

myFormComposition

<apex:page controller="compositionExample">
    <apex:form >
        <apex:outputLabel value="Enter your name: " for="nameField"/>
        <apex:inputText id="nameField" value="{!nameField}"/>
        <br />
        <apex:insert name="age" />
        <br />
        <apex:insert name="meal" />
        <br />      
        <p>That's everything, right?</p>
        <apex:commandButton action="{!save}" value="Save" id="saveButton"/>
    </apex:form>
</apex:page>

请注意,两个字段需要 和内容。这 这些字段的标记是在调用此合成的页面中定义的 模板。

<apex:insert>agemeal接下来,创建一个名为 的页面,该页面 定义 中的标签:

myFullForm<apex:insert>myFormComposition

<apex:page controller="compositionExample">
    <apex:messages/>
    <apex:composition template="myFormComposition">
    
    <apex:define name="meal">
        <apex:outputLabel value="Enter your favorite meal: " for="mealField"/>
        <apex:inputText id="mealField" value="{!mealField}"/>
    </apex:define>

    <apex:define name="age">
        <apex:outputLabel value="Enter your age: " for="ageField"/>
        <apex:inputText id="ageField" value="{!ageField}"/>
    </apex:define>
    
   <apex:outputLabel value="Enter your favorite color: " for="colorField"/>
   <apex:inputText id="colorField" value="{!colorField}"/>
    
    </apex:composition>
    
    <apex:outputText id="greeting" rendered="{!showGreeting}" value="Hello {!nameField}. 
    You look {!ageField} years old. Would you like some {!colorField} {!mealField}?"/>
</apex:page>

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

  • 保存时,将显示先前定义的标记和保存按钮。myFullForm<apex:inputText>
  • 由于组合页面需要 和 字段,因此定义它们 作为文本输入字段。它们在页面上的显示顺序确实如此 没关系; 指定该字段始终显示在该字段之前。agemealmyFullFormmyFormCompositionagemeal
  • 即使没有,该字段仍会导入 匹配字段。name<apex:define>
  • 即使该字段被忽略,即使 该字段存在控制器代码。这是因为成分 模板不需要任何名为 的字段。colorcolor
  • 和字段 不需要是文本输入。标记中的组件可以是任何有效的 Visualforce 标记。agemeal<apex:define>

要展示如何在标签中使用任何有效的 Visualforce,请执行以下操作: 创建一个新的 Visualforce 页面,该页面名为 并使用以下标记:

<apex:define>myAgelessForm

<apex:page controller="compositionExample">
    <apex:messages/>
    <apex:composition template="myFormComposition">
    
    <apex:define name="meal">
        <apex:outputLabel value="Enter your favorite meal: " for="mealField"/>
        <apex:inputText id="mealField" value="{!mealField}"/>
    </apex:define>

    <apex:define name="age">
        <p>You look great for your age!</p>
    </apex:define>

    </apex:composition>
    
    <apex:outputText id="greeting" rendered="{!showGreeting}" value="Hello {!nameField}. 
    Would you like some delicious {!mealField}?"/>
</apex:page>

请注意,只有合成模板 需要标记才能存在。在此示例中,定义 作为文本。

<apex:define>age

动态模板

动态模板允许您通过 PageReference 分配模板。 模板名称分配给返回的控制器方法 包含要使用的模板的 PageReference。为 示例,创建一个名为“定义框架模板”的页面:

myAppliedTemplate

<apex:page>
    <apex:insert name="name" />
</apex:page>

接下来,创建一个名为 将返回对此页面的引用的方法:

dynamicComposition

public class dynamicComposition {
    public PageReference getmyTemplate() {
        return Page.myAppliedTemplate;
    }
}

最后,创建一个名为实现 此控制器和动态模板:

myDynamicComposition

<apex:page controller="dynamicComposition">
    <apex:composition template="{!myTemplate}">
    <apex:define name="name">
        Hello {!$User.FirstName}, you look quite well.
    </apex:define>
    </apex:composition>
</apex:page>

使用 Visualforce 渲染流程

无法使用 流生成器。但是,在 Visualforce 页面中嵌入流程后,即可使用 Apex 代码 和 Visualforce 标记,用于在运行时配置流程,例如在 Visualforce 页面和流程,或在运行时自定义流程的外观。

是一个应用程序,用于收集、更新、编辑、 并创建 Salesforce 信息。

以下主题演示如何在 Visualforce 中嵌入和配置流程 页。

  • 在 Visualforce 页面中嵌入流程 要自定义流程的外观或增强其功能,请将其嵌入到 Visualforce 页面
    中。如果您的组织为站点和门户启用了流程,请使用 Visualforce 页面将流程传送到您的 Salesforce 站点、门户或 Experience Cloud 站点。
  • 使用 <flow:interview> 的高级示例
    该组件旨在使开发复杂的 Visualforce 交互变得容易。您可以通过创建自定义控制器来访问流中的其他功能。使用自定义控制器,您可以构建一个包含多个组件的页面,这些组件可以相互交互。组织内的任何流都可以由其自己的 Apex 类型单独引用,并且流中的变量可以作为成员变量进行访问。<flow:interview>
  • 从 Visualforce 页面设置流变量值 将流程嵌入 Visualforce 页面
    后,通过组件设置变量、记录变量、集合变量和记录集合变量的初始值。<apex:param>
  • 将流变量值获取到 Visualforce 页面 流变量值可以显示在 Visualforce 页面
    中。将流程嵌入 Visualforce 页面后,您可以使用 Visualforce 标记来获取变量的值或记录变量。若要显示集合变量或记录集合变量的值,可以使用 Visualforce 标记来获取集合中包含的各个值。
  • 控制用户是否可以暂停 Visualforce 页面中的流程 使用组件将流程嵌入到 Visualforce 页面中后,请考虑是否允许用户暂停该页面
    中的流程。将该属性设置为 false 可防止用户暂停。<flow:interview>allowShowPause
  • 自定义用户如何恢复暂停的流程面试 默认情况下,用户可以从其主页上的“暂停面试”组件恢复暂停的面试
    。如果要自定义用户恢复采访的方式和位置,请使用组件上的属性。pausedInterviewId<flow:interview>
  • 在流程中配置 finishLocation 属性 如果未指定,则单击“完成”的用户将开始新的面试,并看到流程的第一个屏幕。
    您可以使用函数、变量或控制器来调整用户在最后一个屏幕上单击“完成”时发生的情况。finishLocationURLFOR$Page
  • 自定义流程的用户界面
    在 Visualforce 页面中嵌入流程后,您可以通过使用 CSS 应用自定义样式来自定义流程在运行时的外观。使用流属性和 CSS 类的组合,您可以自定义流的各个部分,例如按钮位置、按钮样式、背景以及屏幕标签的外观。
  • 在 Visualforce 页面中渲染流的 Lightning 运行时 默认情况下,当您在 Visualforce 页面
    中嵌入流时,该流将在 Classic 运行时中呈现。顾名思义,Classic 运行时的外观和感觉类似于常规的 Visualforce 页面和 Salesforce Classic 桌面体验。如果您希望流程在 Visualforce 页面的 Lightning 运行时中呈现,请将流程 Aura 组件嵌入到 Visualforce 页面。

在 Visualforce Pages 中嵌入流程

要自定义流的外观或增强其功能,请将其嵌入到 Visualforce 页面。如果您的组织为站点和门户启用了流程,请使用 Visualforce 页面,将流程传送到您的 Salesforce 站点、门户或 Experience Cloud 网站。

注意

用户只能运行具有活动状态的流 版本。如果嵌入的流没有活动版本,用户会看到错误 消息。如果嵌入的流包含 Subflow 元素,则引用的流 并且由 Subflow 元素调用必须具有活动版本。要向 Visualforce 页面添加流程,请使用以下组件嵌入该流程:

<flow:interview>

  1. 查找流的 API 名称。
    1. 在“设置”中,输入“快速” “查找”框,然后选择“流”。Flows
    2. 单击要嵌入的流的名称。
  2. 定义新的 Visualforce 页面或打开要编辑的页面。
  3. 添加组件, 在标签之间的某个地方。<flow:interview><apex:page>
  4. 将属性设置为唯一属性 流的名称。例如:name<apex:page> <flow:interview name="flowAPIName"/> </apex:page>注意如果流程是 在托管包中,该属性必须是 采用以下格式:.namenamespace.flowuniquename
  5. 通过为 包含它的 Visualforce 页面。若要运行流,外部用户(例如 在 Experience Cloud 站点上)需要访问 Visualforce 页面。要运行 流程中,内部用户需要访问 Visualforce 页面,并且:
    • “运行流”权限
    • 在其用户上启用了 Flow User 字段 详情页面
    • 如果覆盖默认行为并限制对 已启用的配置文件或权限集被选中 单个流,用户按配置文件访问该流 或权限集

在流程中设置变量值

在这个 例如,我们将构建一个简单的流程,以允许客户支持代理 通过创建案例对调制解调器问题进行故障排除。您可以在启动流通过组件时设置变量的值。对于我们的 示例,设置使用初始值调用的案例编号变量 01212212当流加载时,请使用以下标记:

<apex:param>vaCaseNumber

<apex:page>
    <flow:interview name="ModemTroubleShooting">
        <apex:param name="vaCaseNumber" value="01212212"/>
    </flow:interview>
</apex:page>

您还可以使用标准 Visualforce 控制器设置变量。例如,如果 Visualforce页面正在使用控制器,可以增强页面传入标准中的数据 控制器。

standardCase

<apex:page standardController="Case" tabStyle="Case" >
    <flow:interview name="ModemTroubleShooting">
        <apex:param name="vaCaseNumber" value="{!Case.CaseNumber}"/>
    </flow:interview>
</apex:page>

有关设置变量值的更多示例,请参见从 Visualforce 页面设置流变量值。为 有关从流程中获取变量值以在 Visualforce 中显示的信息 页面上,请参阅将流变量值获取到 Visualforce 页面。

设置 finishLocation 属性

基于调制解调器故障排除示例,我们还将设置属性以将用户重定向到 Salesforce 主页 页面,当他们单击末尾的“完成”按钮时 流。

finishLocation

<apex:page standardController="Case" tabStyle="Case" >
    <flow:interview name="ModemTroubleShooting" finishLocation="{!URLFOR('/home/home.jsp')}">
        <apex:param name="vaCaseNumber" value="{!case.CaseNumber}"/>
    </flow:interview>
</apex:page>

有关设置的更多示例,请参阅在流中配置 finishLocation 属性。finishLocation

使用 <flow:interview> 的高级示例

组件设计 以便轻松开发复杂的 Visualforce 交互。您可以 通过创建自定义控制器来访问流程中的其他功能。带定制 控制器,您可以构建一个包含多个组件的页面,这些组件可以与每个组件进行交互 其他。组织内的任何流都可以由其自己的 Apex 单独引用 类型,并且流中的变量可以作为成员变量进行访问。

<flow:interview>

注意

你 只能设置允许输入访问的变量,并且只能获取 允许输出访问。对于不允许输入或输出访问的变量,尝试 get 变量被忽略,并且 Visualforce 页面、其组件或 Apex 类的编译可能会失败。<apex:page>在下一个示例中,API 名称为“ModemTroubleShooting”的流是 引用为 。标记演示了如何显示 不同部分的流变量的值 页:

Flow.Interview.ModemTroubleShooting

<apex:page Controller="ModemTroubleShootingCustomSimple" tabStyle="Case">
    <flow:interview name="ModemTroubleShooting" interview="{!myflow}"/>
    <apex:outputText value="Default Case Prioriy: {!casePriority}"/>
</apex:page>

注意

如果流程是 在托管包中,该属性必须是 采用以下格式:.namenamespace.flowuniquename上述标记的控制器如下所示 这:

public class ModemTroubleShootingCustomSimple {

    // You don't need to explicitly instantiate the Flow object;
    // the class constructor is invoked automatically

    public Flow.Interview.ModemTroubleShooting myflow { get; set; }
    public String casePriority;
    public String getCasePriority() {
        // Access flow variables as simple member variables with get/set methods
        if(myflow == null) return 'High';
        else return myflow.vaCasePriority;
    }
}

如果您使用的是自定义控制器,您还可以设置 流构造函数中流开头的变量。传入 使用构造函数的变量是可选的,如果是 使用标签来设置值。<apex:param>下面是一个自定义控制器的示例,该控制器在 一个 构造 函数。

public class ModemTroubleShootingCustomSetVariables {
    public Flow.Interview.ModemTroubleShooting myflow { get; set; }
 
    public ModemTroubleShootingCustomSetVariables() {
        Map<String, Object> myMap = new Map<String, Object>();
        myMap.put('vaCaseNumber','123456');
        myflow = new Flow.Interview.ModemTroubleShooting(myMap);
    }
 
    public String caseNumber { set; }
    public String getCaseNumber() {
        return myflow.vaCaseNumber;
    }
}

您可以使用类中的方法访问 流变量。变量可能位于 Visualforce 页面中嵌入的流程中,也可能位于 由 Subflow 元素调用的单独流。返回的变量值来了 无论面试当前从哪个流程运行。如果指定的变量 在该流中找不到,该方法将返回 。此方法在运行时检查变量是否存在 只是,而不是在编译时。getVariableValueFlow.Interviewnull此示例使用该方法获取痕迹导航 (导航)来自流的信息。如果该流包含子流元素,并且 每个引用的流还包含一个变量,您可以为用户提供痕迹导航,而不管哪个流 面试是 运行。

getVariableValuevaBreadCrumb

public class SampleController {

   //Instance of the flow
   public Flow.Interview.Flow_Template_Gallery myFlow {get; set;}

   public String getBreadCrumb() {
      String aBreadCrumb;
      if (myFlow==null) { return 'Home';}
      else aBreadCrumb = (String) myFlow.getVariableValue('vaBreadCrumb');

      return(aBreadCrumb==null ? 'Home': aBreadCrumb);

   }
}

下表显示了 流量和顶点。

顶点
发短信字符串
十进制
货币十进制
日期日期、日期时间
布尔布尔
使用指定对象进行记录指定对象的 API 名称,例如 Account 或 箱

由于针对 Apex 代码编写测试是一种很好的做法,因此以下是 编写测试类的简单示例:

ModemTroubleShootingCustomSetVariables

@isTest
private class ModemTroubleShootingCustomSetVariablesTest {

    static testmethod void ModemTroubleShootingCustomSetVariablestests() {
        PageReference pageRef = Page.ModemTroubleShootingSetVariables;
        Test.setCurrentPage(pageRef);
        ModemTroubleShootingCustomSetVariables mytestController = 
            new ModemTroubleShootingCustomSetVariables();
        System.assertEquals(mytestController.getcaseNumber(), '01212212');
    }
}

设置 reRender 属性

通过使用该属性,组件将重新呈现 在不刷新整个页面的情况:reRender<flow:interview />

<apex:page Controller="ModemTroubleShootingCustomSimple" tabStyle="Case">
    <flow:interview name="ModemTroubleShooting" interview="{!myflow}" 
     reRender="casePrioritySection"/>
    <apex:outputText id="casePrioritySection" 
     value="Default Case Prioriy: {!casePriority}"/>
</apex:page>

警告

如果未设置属性,则当您单击按钮导航到其他按钮时 屏幕中,整个 Visualforce 页面都会刷新, 不仅仅是组件。reRender<flow:interview>

从 Visualforce 页面设置流变量值

将流程嵌入 Visualforce 页面后,设置变量的初始值。 通过组件记录变量、集合变量和记录集合变量。

<apex:param>

注意

您只能在面试开始时设置变量。标签仅评估一次, 当流量 推出。<apex:param>

你 只能设置允许输入访问的变量。如果引用的变量 不允许输入访问,则忽略设置变量的尝试。 Visualforce 页面、其组件或 Apex 的编译可能会失败 类。<apex:page>

下表列出了设置流变量 record 的方法 变量,并使用 Visualforce 记录集合变量值。

方法变量记录变量集合变量记录集合变量
没有 控制器复选标记
与标准 控制器复选标记复选标记
使用标准列表 控制器复选标记
使用自定义 Apex 控制器复选标记复选标记复选标记复选标记
接受采访 地图复选标记复选标记复选标记复选标记

设置不带 控制器

此示例将 myVariable 设置为面试开始时的值。01010101

<apex:page>
    <flow:interview name="flowname">
        <apex:param name="myVariable" value="01010101"/>
    </flow:interview>
</apex:page>

使用标准设置变量值 控制器

您可以使用标准 Visualforce 控制器通过从记录传入数据来设置变量。此示例集 面试时 myVariable 到 Visualforce 表达式的初始值 开始。{!account}

<apex:page standardController="Account" tabStyle="Account">
    <flow:interview name="flowname">
        <apex:param name="myVariable" value="{!account}"/>
    </flow:interview>
</apex:page>

设置记录集合变量值 使用标准列表控制器

由于记录集合变量表示值数组,因此必须使用 标准列表控制器或自定义 Apex 控制器。此示例将 myCollection 设置为面试开始时的值。{!accounts}

<apex:page standardController="Account" tabStyle="Account" recordSetVar="accounts">
    <flow:interview name="flowname">
        <apex:param name="myCollection" value="{!accounts}"/>
    </flow:interview>
</apex:page>

使用自定义 Apex 设置变量值 控制器

要对 Visualforce 页面进行比标准控制器允许的更精细的控制,请编写 设置变量值的自定义 Apex 控制器,然后引用该变量值 控制器。此示例使用 Apex 将 myVariable 设置为特定帐户的 ID,当 面试开始。

public class MyCustomController {
    public Account apexVar {get; set;}

    public MyCustomController() {
        apexVar = [
            SELECT Id, Name FROM Account
            WHERE Name = 'Acme' LIMIT 1];
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname">
        <apex:param name="myVariable" value="{!apexVar}"/>
    </flow:interview>
</apex:page>

此示例使用 Apex 将记录集合变量 myAccount 设置为 Name 为 的每条记录的 Id 和 Name 字段值。Acme

public class MyCustomController {
    public Account[] myAccount { 
        get {
            return [ 
                SELECT Id, Name FROM account 
                WHERE Name = 'Acme'
                ORDER BY Id
            ] ;
        }
        set {
            myAccount = value;
        }
    }
    public MyCustomController () {
    }
}
<apex:page id="p" controller="MyCustomController">
    <flow:interview id="i" name="flowname">
        <apex:param name="accountColl" value="{!myAccount}"/>
    </flow:interview>
</apex:page>

在面试中设置变量值 地图

此示例使用面试映射在面试开始时将 accVar 的值设置为特定客户的 Id。

public class MyCustomController {
    public Flow.Interview.TestFlow myflow { get; set; }

     public MyCustomController() {
        Map<String, Object> myMap = new Map<String, Object>();
        myMap.put('accVar', [SELECT Id FROM Account 
                             WHERE Name = 'Acme' LIMIT 1]);
        myflow = new Flow.Interview.ModemTroubleShooting(myMap);
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname" interview="{!myflow}"/>
</apex:page>

下面是一个类似的示例,该示例在面试开始时将 accVar 的值设置为新帐户。

public class MyCustomController {
    public Flow.Interview.TestFlow myflow { get; set; }

     public MyCustomController() {
        Map<String, List<Object>> myMap = new Map<String, List<Object>>();
        myMap.put('accVar', new Account(name = 'Acme'));
        myflow = new Flow.Interview.ModemTroubleShooting(myMap);
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname" interview="{!myflow}"/>
</apex:page>

此示例使用映射将两个值添加到字符串集合变量 (stringCollVar) 和两个值到一个数字集合 变量 (numberCollVar)。

public class MyCustomController {
    public Flow.Interview.flowname MyInterview { get; set; }

    public MyCustomController() {
        String[] value1 = new String[]{'First', 'Second'};
        Double[] value2 = new Double[]{999.123456789, 666.123456789};
        Map<String, Object> myMap = new Map<String, Object>();
        myMap.put('stringCollVar', value1);
        myMap.put('numberCollVar', value2);
        MyInterview = new Flow.Interview.flowname(myMap);
    }
}
<apex:page controller="MyCustomController">
    <flow:interview name="flowname" interview="{!MyInterview}" />
</apex:page>

将流变量值获取到 Visualforce 页面

流变量值可以显示在 Visualforce 页面中。一旦你 将您的流程嵌入到 Visualforce 页面中,您可以使用 Visualforce 标记 获取变量的值或记录变量。显示集合的值 变量或记录集合变量,您可以使用 Visualforce 标记来获取 集合中包含的单个值。

注意

你 只能获取允许输出访问的变量。如果引用不允许输出访问的变量, 获取变量的尝试将被忽略。Visualforce 页面、其组件或 顶点类。<apex:page>下面的示例使用 Apex 类从 flow,然后在 Visualforce 中显示它 页。

public class FlowController {
    public Flow.Interview.flowname myflow { get; set; }
    public Case apexCaseVar;
    public Case getApexCaseVar() {
        return myflow.caseVar;
    }
}
<apex:page controller="FlowController" tabStyle="Case">
    <flow:interview name="flowname" interview="{!myflow}"/>
    <apex:outputText value="Default Case Priority: {!apexCaseVar.Priority}"/>
</apex:page>

这 示例使用 Apex 类获取存储在字符串集合中的值 变量 (emailsCollVar)。然后,它使用 Visualforce 页面运行 流程面试。Visualforce 页面将循环访问流程的集合变量,并显示 中的每一项 收集。

public class FlowController {
    public Flow.Interview.flowname myflow { get; set; }

    public List<String> getVarValue() {
        if (myflow == null) { 
            return null; 
        }
        else {
            return (List<String>)myflow.emailsCollVar;
        }
    }
}
<apex:page controller="FlowController">
    <flow:interview name="flowname" interview="{!myflow}" />
        <apex:repeat value="{!varValue}" var="item">
        <apex:outputText value="{!item}"/><br/>
        </apex:repeat>
</apex:page>

这 以下示例使用 Apex 类将流程设置为,然后使用 Visualforce 页面运行 流程面试。Visualforce 页面使用数据表循环访问流程的记录集合 变量,并显示 收集。{!myflow}

public class MyCustomController {
   public Flow.Interview.flowname myflow { get; set; }
}
<apex:page controller="MyCustomController" tabStyle="Account">
   <flow:interview name="flowname" interview="{!myflow}" reRender="nameSection" />
    <!-- The data table iterates over the variable set in the "value" attribute and 
         sets that variable to the value for the "var" attribute, so that instead of 
         referencing {!myflow.collectionVariable} in each column, you can simply refer 
         to "account".-->
    <apex:dataTable value="{!myflow.collectionVariable}" var="account" 
        rowClasses="odd,even" border="1" cellpadding="4" id="nameSection">
        <!-- Add a column for each value that you want to display.-->
        <apex:column >
            <apex:facet name="header">Name</apex:facet>
            <apex:outputlink value="/{!account['Id']}">
                {!account['Name']}
            </apex:outputlink>
        </apex:column>
        <apex:column >
            <apex:facet name="header">Rating</apex:facet>
            <apex:outputText value="{!account['Rating']}"/>
        </apex:column>
        <apex:column >
            <apex:facet name="header">Billing City</apex:facet>
            <apex:outputText value="{!account['BillingCity']}"/>
        </apex:column>
        <apex:column >
            <apex:facet name="header">Employees</apex:facet>
            <apex:outputText value="{!account['NumberOfEmployees']}"/>
        </apex:column>
    </apex:dataTable>
</apex:page>

取决于 在流中记录集合变量的内容上,内容如下 该数据表如下所示。Example of a data table that displays the contents of a record collection variable

控制用户是否可以暂停来自 Visualforce 的流程 页

使用组件在 Visualforce 页面中嵌入流程后,请考虑是否要让用户暂停流程 那一页。将属性设置为 false 阻止用户暂停。

<flow:interview>allowShowPause是否显示“暂停”按钮取决于三个设置。

  • 组织的流程自动化设置必须启用允许用户暂停流程。
  • 为此,不能是假的。默认值为 真。<flow:interview>allowShowPause
  • 每个屏幕都必须配置为显示“暂停”按钮。

在 Visualforce 页面中,您嵌入了一个包含三个屏幕的流程。屏幕 1 是 配置为显示“暂停”按钮。屏幕 2 和 3 配置为 不显示“暂停”按钮。

允许用户暂停流(过程自动化设置)allowShowPause(视觉力 组件)结果暂停按钮
启用true或未设置暂停按钮仅出现在第一个屏幕上
启用false此 Visualforce 页面中的任何屏幕均未显示暂停按钮
未启用true或未设置暂停按钮不会出现在任何屏幕上

此示例将 MyUniqueFlow 流嵌入到 Visualforce 页面中,并且不允许“暂停”按钮 出现。

<apex:page>   
   <flow:interview name="MyUniqueFlow" allowShowPause="false" />
</apex:page>

自定义用户恢复暂停流程访谈的方式

默认情况下,用户可以从“暂停的面试”中恢复暂停的面试 组件。如果要自定义用户恢复其恢复的方式和位置 interviews,请使用组件上的属性。

pausedInterviewId<flow:interview>以下示例显示了如何恢复面试或开始新的面试 一个 – 从页面布局上的按钮。当用户单击“调查”时 客户从联系人记录中,Visualforce 页面执行以下两项操作之一: 取决于用户是否对“调查客户”流程进行了任何暂停的访谈。

  • 如果用户这样做,它将恢复第一个。
  • 如果用户不这样做,它会启动一个新的。

创建 Visualforce 和 Apex 控制器

因为 Visualforce 页面将是 在特定于联系人的按钮中引用,它必须使用该标准控制器。使用 控制器扩展,以使用 Apex 向页面添加更多逻辑,这是页面获取的位置 要恢复的面试的 ID。

<apex:page
   standardController="Contact" extensions="MyControllerExtension_SurveyCustomers">
   <flow:interview name="Survey_Customers" pausedInterviewId="{!pausedId}"/>
</apex:page>

此 Apex 控制器扩展执行 SOQL 查询以获取暂停访谈的列表。如果 查询中不返回任何内容,返回 null 值,并且 Visualforce 页面将开始新的面试。如果至少一个 interview 从查询中返回,则 Visualforce 页面将恢复 那个列表。getPausedId()

public class MyControllerExtension_SurveyCustomers {
    
    // Empty constructor, to allow use as a controller extension
    public MyControllerExtension_SurveyCustomers(
        ApexPages.StandardController stdController) { }
    
    // Flow support methods
    public String getInterviews() { return null; }
    
    public String showList { get; set; }
    
    public String getPausedId() {
        String currentUser = UserInfo.getUserId();
        List<FlowInterview> interviews = 
            [SELECT Id FROM FlowInterview WHERE CreatedById = :currentUser AND InterviewLabel LIKE '%Survey Customers%'];

        if (interviews == null || interviews.isEmpty()) {
            return null; // early out
        }
        
        // Return the ID for the first interview in the list
        return interviews.get(0).Id;
    }
}

参考 Visualforce 页面 从页面布局

为了实际暴露这个 Visualforce 页面,使其可从“联系人”页面布局中使用。

提示

如果将 Visualforce 页面直接嵌入到页面中 布局中,每次用户访问联系人时,他们都会自动恢复他们的第一个联系人 暂停采访——可能是无意的。用户最好将 有意识地选择开始或恢复面试,所以让我们使用自定义按钮。首先,为链接到 Visualforce 页面的 Contact 对象创建一个自定义按钮。使用这些字段 值来创建按钮。

价值
标签调查客户
显示类型“详情页面”按钮
内容源Visualforce 页面
内容YourVisualforcePage

最后,将按钮添加到“联系人”页面布局中。

在流中配置 finishLocation 属性

如果未指定,则用户 谁单击完成开始新的面试并看到 流。您可以确定当用户在最终阶段单击“完成”时会发生什么情况 使用函数、变量或控制器进行筛选。

finishLocationURLFOR$Page以下各节显示了配置组件属性的方法。

<flow:interview>finishLocation

  • 使用 URLFOR 函数设置 finishLocation
  • 使用 $Page 变量设置 finishLocation
  • 使用 Controller 设置 finishLocation

使用 URLFOR 函数设置 finishLocation

注意

  • 您无法将流用户重定向到符合以下条件的 URL Salesforce 组织外部。
  • 不要在同一流中调用方法和属性。 表示 Visualforce 页面登录流程结束。如果在同一流中,则在流中执行 启动,为用户提供对会话的完全访问权限。Auth.SessionManagement.finishLoginFlowfinishLocationAuth.SessionManagement.finishLoginFlowfinishLocationfinishLocation

将用户路由到 相对 URL 或特定记录或详细信息页面,使用其 ID 使用该函数。URLFOR此示例将用户路由到 Salesforce 主页 页。

<apex:page>
    <flow:interview name="MyUniqueFlow" finishLocation="{!URLFOR('/home/home.jsp')}"/>
</apex:page>

此示例将用户引导至详细信息页面 ID 为 001D000000IpE9X。

<apex:page>
    <flow:interview name="MyUniqueFlow" finishLocation="{!URLFOR('/001D000000IpE9X')}"/>
</apex:page>

有关 的详细信息,请参阅函数。URLFOR

使用 $Page 变量设置 finishLocation

要在不使用 的情况下将用户路由到另一个 Visualforce 页面,请设置为目标的名称 页面的格式为 .

URLFORfinishLocation{!$Page.pageName}

<apex:page>
    <flow:interview name="MyUniqueFlow" finishLocation="{!$Page.MyUniquePage}"/>
</apex:page>

有关 的详细信息,请参阅全局变量。$Page

使用 Controller 设置 finishLocation

您可以使用自定义控制器通过多种方式进行设置。finishLocation此示例控制器配置了一个 流动的完成行为在三种不同的 方式。

public class myFlowController {
    
    public PageReference getPageA() {
        return new PageReference('/300');
    }
    
    public String getPageB() {
        return '/300';
    }
    
    public String getPageC() {
        return '/apex/my_finish_page';
    }
}

下面是一个 Visualforce 示例 引用控制器并将流完成行为设置为第一个的页面 选择。

<apex:page controller="myFlowController">
    <h1>Congratulations!</h1> This is your new page.
    <flow:interview name="flowname" finishLocation="{!pageA}"/>
</apex:page>

如果使用标准控制器显示 在与流程相同的页面上进行记录,单击“完成”的用户将开始新的流程访谈。他们看到流的第一个屏幕,但没有 记录,因为查询字符串 参数不会保留在页面 URL 中。如果需要,请配置 将用户路由回 记录。idfinishLocation

自定义流的用户界面

在 Visualforce 页面中嵌入流程后,您可以通过应用 使用 CSS 的自定义样式。结合使用流属性和 CSS 类,您可以自定义流程的各个部分,例如 作为按钮位置、按钮样式、背景以及外观和 屏幕标签的感觉。

Flow 按钮属性

使用这些属性可以更改“下一步”、“上一个”、“完成”、“暂停”和“不暂停”按钮在 你的流程。

属性描述
buttonLocation定义导航按钮在流中的位置 用户界面。可用值为:topbottomboth例如:<apex:page> <flow:interview name="MyFlow" buttonLocation="bottom"/> </apex:page>注意如果未指定,则该值默认为 。buttonLocationboth
buttonStyle将样式作为一组分配给流导航按钮。能 仅用于内联样式,不用于 CSS 类。例如:<apex:page> <flow:interview name="MyFlow" buttonStyle="color:#050; background-color:#fed; border:1px solid;"/> </apex:page>

特定于流的 CSS 类

您可以覆盖这些预定义的流样式 具有您自己的 CSS 样式的类。

Flow Style 类适用于…
FlowContainer(流程容器)包含 流。<div>
FlowPageBlockBtns(流页块Btns)元素 包含流导航按钮。<apex:pageBlockButtons>注意防止流的 CSS 样式 导航按钮不被其他位置应用的按钮样式覆盖 系统中,我们建议您在每次应用 CSS 时指定此流样式类 流导航按钮的样式。例如,输入 ,而不是 。.FlowPreviousBtn {}.FlowPageBlockBtns .FlowPreviousBtn {}
FlowCancelBtn不暂停”按钮。
FlowPauseBtn“暂停”按钮。
流程上一页Btn“上一步”按钮。
流下一个BTN下一步”按钮。
FlowFinishBtn完成”按钮。
流文本文本字段标签。
FlowTextArea文本区域字段标签。
流量编号数字字段标签。
流日期日期字段标签。
流货币货币字段标签。
FlowPassword(流密码)密码字段标签。
FlowRadio(英语:FlowRadio单选按钮字段标签。
FlowDropdown选择列表标签。

在 Visualforce 页面中渲染流程的 Lightning 运行时

默认情况下,当您在 Visualforce 页面中嵌入流程时,该流程将以 Classic 格式呈现 运行。顾名思义,Classic 运行时的外观和感觉都像常规的 Visualforce 页面 以及 Salesforce Classic 桌面体验。如果您希望流程在 Lightning 中渲染 运行时,将流 Aura 组件嵌入到 Visualforce 页面。

  1. 创建一个声明对组件的依赖关系的 Lightning 应用程序。lightning:flow
  2. 添加 Lightning 组件 使用组件将 Visualforce JavaScript 库添加到您的 Visualforce 页面。<apex:includeLightning/>
  3. 在 Visualforce 页面中,引用依赖项应用。
  4. 编写一个 JavaScript 函数,该函数使用 在页面上创建组件。$Lightning.createComponent()

<aura:application access="global" extends="ltng:outApp" >
    <aura:dependency resource="lightning:flow"/>
</aura:application>
<apex:page >
   <html>
      <head>
         <apex:includeLightning />
      </head>
      <body class="slds-scope">
         <div id="flowContainer" />
         <script>
            var statusChange = function (event) {
               if(event.getParam("status") === "FINISHED") {
                  // Control what happens when the interview finishes
 
                  var outputVariables = event.getParam("outputVariables");
                  var key;
                  for(key in outputVariables) {
                     if(outputVariables[key].name === "myOutput") {
                        // Do something with an output variable
                     }
                  }
               }
            };
            $Lightning.use("c:lightningOutApp", function() {
               // Create the flow component and set the onstatuschange attribute
               $Lightning.createComponent("lightning:flow", {"onstatuschange":statusChange},
                  "flowContainer",
                  function (component) {
                     // Set the input variables
                     var inputVariables = [
                        {
                           name : 'myInput',
                           type : 'String',
                           value : "Hello, world"
                        }
                     ];
                     
                     // Start an interview in the flowContainer div, and 
                     // initializes the input variables.
                     component.startFlow("myFlowName", inputVariables);
                  }
               );
            });
         </script>
      </body>
   </html>
</apex:page>

使用 Visualforce 创建地图

地图传达的信息比单纯的信息更清晰 位置数据。Visualforce 映射 组件使创建使用第三方地图服务的地图变得简单。Visualforce 地图是交互式的, 基于 JavaScript 的地图,包括缩放、平移和基于 Salesforce 或其他数据的标记。创建独立版 地图页面、可插入页面布局的地图,甚至是 Salesforce 的移动地图 应用程序。

Visualforce 提供了一组相关的映射 组件。该组件定义地图 画布,包括大小、类型、中心点和初始缩放级别。子组件定义要放置的标记 在地图上按地址或地理位置(纬度和经度)。您可以使用该组件添加可自定义的 单击或点击标记时显示的信息面板。<apex:map><apex:mapMarker><apex:mapInfoWindow>

注意

Visualforce 映射组件不是 在 Developer Edition 组织中可用。

您在 Visualforce 标记中定义的地图会生成 JavaScript 代码 以呈现到页面上。此 JavaScript 连接到地图服务,并通过以下方式构建地图 获取地图图块并放置标记。如果要映射的项目没有纬度和 经度,Visualforce 地图可以进行地理编码 他们的地址。地图渲染后,用户可以通过平移和 缩放,就像他们习惯于其他地图站点一样。效果就好像你自己写的一样 自定义 JavaScript 与第三方映射服务进行交互,但实际上不需要 写出来。您可以在 Visualforce 中定义地图并免费获取地图 JavaScript。

重要

Visualforce 映射 组件将 JavaScript 添加到页面,并使用第三方 JavaScript 代码绘制地图。

  • Visualforce 添加的 JavaScript 使用 行业标准最佳实践,避免与在同一 JavaScript 上执行的其他 JavaScript 发生冲突 页。如果您自己的 JavaScript 没有使用最佳实践,它可能会与 映射代码。
  • 需要地理编码的地址 – 即不包含纬度和 经度 – 发送到第三方服务进行地理编码。这些地址未关联 ,除了您在 Visualforce 标记中提供的数据外,不会发送其他数据。但是,如果您的 组织要求严格控制在 Salesforce 外部共享的数据,不要使用地理编码 Visualforce地图的功能。
  • 创建基本地图 没有标记的基本地图
    只需要一个组件。此组件定义地图的基本画布,包括其尺寸、位置和初始缩放级别。<apex:map>
  • 向地图添加位置标记 您可以使用该组件向地图
    添加标记以表示特定位置。您可以包括指针悬停在标记上时显示的文本。<apex:mapMarker>
  • 使用自定义标记图标
    Visualforce 地图标记图标功能强大,但很普通。要区分标记并向地图添加细节或样式,请使用自定义地图标记图标。
  • 将信息窗口添加到标记
    信息窗口允许您在地图上显示额外的详细信息。当用户单击或点击标记时,将显示信息窗口。
  • 在 Apex 中构建地图数据的示例 在 Apex
    中构建位置数据以执行自定义查询、搜索附近位置、筛选或转换结果,或者在无法使用 Visualforce 标准控制器返回的结果时执行。

创建基本地图

没有标记的基本地图只需要一个组件。此组件定义地图的基本画布,包括其 尺寸、位置和初始缩放级别。

<apex:map>该属性定义围绕该点的点 地图居中。您可以在以下几个方面提供值 格式。

centercenter

  • 一个表示地址的字符串。例如,“1 Market Street, San Francisco, CA”。这 对地址进行地理编码以确定其纬度和经度。
  • 一个字符串,表示具有指定位置的 JSON对象和属性 坐标。例如,“{latitude: 37.794, longitude: -122.395}”。latitudelongitude
  • 类型为 的 Apex 映射对象 with 和 键指定位置坐标。Map<String, Double>latitudelongitude

如果没有子标记,则该属性是必需的。

<apex:map><apex:mapMarker>center这张简单的街道地图显示了 Salesforce 旧金山周围的社区 总部。

<apex:page >

    <h1>Salesforce in San Francisco</h1>
  
    <!-- Display the address on a map -->
    <apex:map width="600px" height="400px" mapType="roadmap" zoomLevel="16"
        center="One Market Street, San Francisco, CA">
    </apex:map>
    
</apex:page>

这 代码生成以下映射。

请注意此示例中的以下内容。

  • 映射的地址没有标记。该组件本身不显示地图标记,即使对于中心点也是如此。要显示最多 100 个标记,添加子组件。<apex:map><apex:mapMarker>
  • 地图的位置值以 街道地址,而不是地理位置。地图服务查找纬度和经度 地址。此过程称为地理编码。您最多可以将 10 个地理编码地址作为属性或作为随组件添加的标记包含在地图中。centercenter<apex:mapMarker>
  • 值是“路线图”,一条标准街道 地图。其他选项是“卫星”和“混合”。mapType

向地图添加位置标记

您可以使用该组件向地图添加标记以表示特定位置。您可以包含以下文本 当指针悬停在标记上时显示。

<apex:mapMarker>

若要在地图上放置标记,请添加一个组件作为关联 .您可以使用属性指定标记的位置。(可选)使用该属性在指针悬停时显示文本 标记。<apex:mapMarker><apex:map>positiontitle您最多可以加到

100地图上的标记。 使用迭代组件添加 集合或列表中的多个标记。

<apex:repeat>

注意

Visualforce 地图可以是 资源密集型,可能导致移动浏览器和 Salesforce 内存问题 应用程序。具有许多标记的地图或用作自定义标记的大图像可以进一步增加 内存消耗。如果您计划在使用的页面中部署 Visualforce 地图 在移动设备环境中,请务必彻底测试这些网页。该属性定义地图上的点 放置标记。您可以提供值 有几种格式。

positionposition

  • 一个表示地址的字符串。例如,“1 Market Street, San Francisco, CA”。这 对地址进行地理编码以确定其纬度和经度。
  • 一个字符串,表示具有指定位置的 JSON对象和属性 坐标。例如,“{latitude: 37.794, longitude: -122.395}”。latitudelongitude
  • 类型为 的 Apex 映射对象 with 和 键指定位置坐标。Map<String, Double>latitudelongitude

注意

您最多可以拥有10每个地图的地理编码地址查找。对组件属性和组件属性的查找都计入此限制。自 显示更多标记,提供 不需要地理编码。超过地理编码限制的位置将被跳过。center<apex:map>position<apex:mapMarker>position下面是一个页面,其中显示了 帐户,以帐户的 地址。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/NearbyContacts?id=001D000000JRBet -->
  
  <apex:pageBlock >
    <apex:pageBlockSection title="Contacts For {! Account.Name }">
    
     <apex:dataList value="{! Account.Contacts }" var="contact">
       <apex:outputText value="{! contact.Name }" />
     </apex:dataList> 
    
  <apex:map width="600px" height="400px" mapType="roadmap"
    center="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}">

    <apex:repeat value="{! Account.Contacts }" var="contact">
    <apex:mapMarker title="{! contact.Name }"
       position="{!contact.MailingStreet},{!contact.MailingCity},{!contact.MailingState}"
    />
    </apex:repeat>

  </apex:map>

    </apex:pageBlockSection>
  </apex:pageBlock>

</apex:page>

这 代码生成以下映射。

请注意此示例中的以下内容。

  • 和属性作为 Visualforce 表达式传递,该表达式 连接地址元素以提供可进行地理编码的地址字符串。centerposition
  • 由于此页面对地址使用地理编码,因此仅显示前 9 个地址 接触。属性使用一个地理编码查找作为 允许 10 个。(在图中,该帐户只有三个联系人。center<apex:map>

使用自定义标记图标

Visualforce 地图标记图标 功能齐全,但朴素无比。要区分标记并向地图添加细节或样式,请使用 自定义地图标记图标。要自定义标记的图标,请将属性设置为绝对或完全限定的 URL 要使用的图形。您可以引用 Web 上的任何图像,例如,如果您的图形是 分布在 CDN 中。您还可以使用存储在静态资源中的图形。如果您使用图像 从静态资源中,使用 URLFOR() 函数获取图像 URL。为 例:

icon

<apex:mapMarker title="{! Account.Name }" 
    position="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}"
    icon="{! URLFOR($Resource.MapMarkers, 'moderntower.png') }" />

使用常见的图形格式,例如 PNG、GIF 或 JPEG格式。首选标记大小为 32 × 32 像素。其他尺寸是缩放的,但不会 始终产生理想的结果。

注意

Visualforce 地图可以是 资源密集型,可能导致移动浏览器和 Salesforce 内存问题 应用程序。具有许多标记的地图或用作自定义标记的大图像可以进一步增加 内存消耗。如果您计划在使用的页面中部署 Visualforce 地图 在移动设备环境中,请务必彻底测试这些网页。此完整页面演示了如何使用自定义标记来指示帐户的位置,以及 帐户的标准标记 接触。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/AccountContacts?id=001D000000JRBet -->
  
  <apex:pageBlock >
    <apex:pageBlockSection title="Contacts For {! Account.Name }">
    
      <apex:dataList value="{! Account.Contacts }" var="contact">
        <apex:outputText value="{! contact.Name }" />
      </apex:dataList> 
      
      <apex:map width="600px" height="400px" mapType="roadmap"
  center="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}">

      <!-- Add a CUSTOM map marker for the account itself -->
      <apex:mapMarker title="{! Account.Name }" 
  position="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}"
       icon="{! URLFOR($Resource.MapMarkers, 'moderntower.png') }"/>
      
      <!-- Add STANDARD markers for the account's contacts -->
      <apex:repeat value="{! Account.Contacts }" var="ct">
        <apex:mapMarker title="{! ct.Name }" 
          position="{! ct.MailingStreet },{! ct.MailingCity },{! ct.MailingState }">
        </apex:mapMarker>
      </apex:repeat>

      </apex:map>
    
    </apex:pageBlockSection>
  </apex:pageBlock>
   
</apex:page>

这 代码生成以下映射。

要对迭代中添加的标记使用不同的图标,例如 ,请使用与迭代相关的表达式 变量来定义 URL。一种简单的方法是在 记录。另一种方法是在自定义公式字段中提供图标名称。<apex:repeat>这是上一个块,带有 假定联系人对象具有名为“ContactType__c”的自定义字段的变体,并且 每种触点类型都有一个相应的名称 图标。

<apex:repeat>

<!-- Add CUSTOM markers for the account's contacts -->
    <apex:repeat value="{! Account.Contacts }" var="ct">
        <apex:mapMarker title="{! ct.Name }" 
          position="{! ct.MailingStreet },{! ct.MailingCity },{! ct.MailingState }"
          icon="{! URLFOR($Resource.MapMarkers, ct.ContactType__c + '.png') }">
        </apex:mapMarker>
    </apex:repeat>

如果 您使用字段来提供图标 URL 的关键部分,确保它始终提供 可用值。例如,通过将其设为必填字段,或确保公式字段 提供合理的默认值。

将信息窗口添加到标记

信息窗口允许您在地图上显示额外的详细信息。当用户 单击或点击标记。

地图标记属性允许您显示 当用户将鼠标悬停在标记上时,少量信息。要显示更多信息或 可以更好地控制其格式,使用信息窗口代替属性或添加属性。titletitle

例如,您可以显示联系人地址的完整详细信息,格式为最佳 显示。您可以添加可点击的电话链接,甚至可以显示对象的个人资料照片 有一个。

要将信息窗口添加到地图标记,请将组件添加为 关联的 .组件的主体显示在 用户单击或点击标记时的信息窗口,可以是 Visualforce 标记、HTML 和 CSS,或者 纯文本。<apex:mapInfoWindow><apex:mapMarker><apex:mapInfoWindow>此完整页面使用 Visualforce 标记 信息内容 窗。

<apex:page standardController="Account">

  <!-- This page must be accessed with an Account Id in the URL. For example: 
       https://<salesforceInstance>/apex/AccountContactsCustomMarker?id=001D000000JRBet -->
  
  <apex:pageBlock >
    <apex:pageBlockSection title="Contacts For {! Account.Name }">
    
      <apex:dataList value="{! Account.Contacts }" var="contact">
        <apex:outputText value="{! contact.Name }" />
      </apex:dataList> 
      
      <apex:map width="600px" height="400px" mapType="roadmap"
  center="{!Account.BillingStreet},{!Account.BillingCity},{!Account.BillingState}">

      <!-- Add markers for account contacts -->
      <apex:repeat value="{! Account.Contacts }" var="ct">
        <apex:mapMarker title="{! ct.Name }" 
          position="{! ct.MailingStreet },{! ct.MailingCity },{! ct.MailingState }">

          <!-- Add info window with contact details -->
          <apex:mapInfoWindow >
            <apex:outputPanel layout="block" style="font-weight: bold;">
              <apex:outputText>{! ct.Name }</apex:outputText>
            </apex:outputPanel>

            <apex:outputPanel layout="block">
              <apex:outputText>{! ct.MailingStreet }</apex:outputText>
            </apex:outputPanel>               

            <apex:outputPanel layout="block">
              <apex:outputText>{! ct.MailingCity }, {! ct.MailingState }</apex:outputText>
            </apex:outputPanel>               

            <apex:outputPanel layout="block">
              <apex:outputLink value="{! 'tel://' + ct.Phone }">
                  <apex:outputText>{! ct.Phone }</apex:outputText>
              </apex:outputLink>
            </apex:outputPanel>               
          </apex:mapInfoWindow>

        </apex:mapMarker>
      </apex:repeat>

      </apex:map>
    
    </apex:pageBlockSection>
  </apex:pageBlock>
   
</apex:page>

这 代码生成以下映射。

默认情况下,一次只显示一个信息窗口。当您单击另一个标记时,第一个标记 “信息”窗口将关闭,而“新建信息”窗口将打开。要同时显示多个信息窗口, 设置为 在包含组件上。

showOnlyActiveInfoWindowfalse<apex:map>

注意

仔细考虑显示多个的效果 信息窗口,因为它可以创建杂乱的地图。

在 Apex 中构建地图数据的示例

在 Apex 中构建您的位置数据以执行自定义查询,搜索附近 位置、过滤或转换结果,或者当您无法使用 Visualforce 标准返回的结果时 控制器。

Apex 代码使您可以完全控制返回并用于 地图和标记。您还可以使用 Apex 返回来自 Salesforce 外部的结果。此页面最多显示 10 个离用户最近的仓库 位置。

<apex:page controller="FindNearbyController" docType="html-5.0" >

    <!-- JavaScript to get the user's current location, and pre-fill
         the currentPosition form field. -->
    <script type="text/javascript">
        // Get location, fill in search field
    	function setUserLocation() {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(function(loc){
                    var latlon = loc.coords.latitude + "," + loc.coords.longitude;
                    var el = document.querySelector("input.currentPosition");
                    el.value = latlon;
                });
            }
        }
        // Only set the user location once the page is ready
    	var readyStateCheckInterval = setInterval(function() {
    		if (document.readyState === "interactive") {
        		clearInterval(readyStateCheckInterval);
        		setUserLocation();
    		}
		}, 10);
    </script>
    
    <apex:pageBlock >
        <!-- Form field to send currentPosition in request. You can make it
             an <apex:inputHidden> field to hide it. -->
        <apex:pageBlockSection >
            <apex:form >
                <apex:outputLabel for="currentPosition">Find Nearby</apex:outputLabel> 
                <apex:input size="30" 
                     html-placeholder="Attempting to obtain your position..."
                     id="currentPosition" styleClass="currentPosition" 
                     value="{!currentPosition}" />
                <apex:commandButton action="{!findNearby}" value="Go!"/>
            </apex:form>
        </apex:pageBlockSection>
        
        <!-- Map of the results -->
        <apex:pageBlockSection rendered="{!resultsAvailable}" title="Locations">
            <apex:map width="600px" height="400px">
                <apex:repeat value="{!locations}" var="pos">
                    <apex:mapMarker position="{!pos}"/>
                </apex:repeat>
            </apex:map>
        </apex:pageBlockSection>

    </apex:pageBlock>

</apex:page>

这 代码生成以下映射。

此页面有三个重要部分。

  • 开头的 JavaScript 块说明了如何访问 浏览器的内置功能,可以询问用户的当前位置。此代码 更新可见的表单字段。但是,您可以轻松使用隐藏的表单字段 而是避免显示原始纬度和经度及其不太可能的级别 的精度。
  • 第一个包含一个简短的表单,用于在 POSTBACK 中提交用户的位置 请求。出于说明目的,它是可见的,需要单击,但这是 不需要。<apex:pageBlockSection>
  • 在第二种情况下,地图本身很简单,只需要 五行代码。所有的复杂性都在表达式中,它访问 Apex 中的属性 控制器。<apex:pageBlockSection>{!locations}请注意属性的用法,该属性采用表达式的值。这 expression 是另一个 Apex 属性,将它与该属性一起使用会隐藏 map 部分 当位置无法放置在地图上时。rendered{!resultsAvailable}rendered

这是支持以前的 Apex 控制器 页。

public with sharing class FindNearbyController {

    public List<Map<String,Double>> locations { get; private set; }

    public String currentPosition { 
        get {
            if (String.isBlank(currentPosition)) {
                currentPosition = '37.77493,-122.419416'; // San Francisco
            }
            return currentPosition;
        }
        set; 
    }
    
    public Boolean resultsAvailable {
        get {
            if(locations == Null) {
                return false;
            }
            return true;
        }
    }
    
    public PageReference findNearby() {
        String lat, lon;

        // FRAGILE: You'll want a better lat/long parsing routine
        // Format: "<latitude>,<longitude>" (must have comma, but only one comma)
        List<String> latlon = currentPosition.split(',');
        lat = latlon[0].trim();
        lon = latlon[1].trim();

        // SOQL query to get the nearest warehouses
        String queryString =
           'SELECT Id, Name, Location__longitude__s, Location__latitude__s ' +
           'FROM Warehouse__c ' +
           'WHERE DISTANCE(Location__c, GEOLOCATION('+lat+','+lon+'), \'mi\') < 20 ' +
           'ORDER BY DISTANCE(Location__c, GEOLOCATION('+lat+','+lon+'), \'mi\') ' +
           'LIMIT 10';

        // Run the query
        List <Warehouse__c> warehouses = database.Query(queryString);
        
        if(0 < warehouses.size()) {
            // Convert to locations that can be mapped
            locations = new List<Map<String,Double>>();
            for (Warehouse__c wh : warehouses) {
                locations.add(
                    new Map<String,Double>{
                        'latitude' => wh.Location__latitude__s, 
                        'longitude' => wh.Location__longitude__s
                    }
                );
            }
        }
        else {
            System.debug('No results. Query: ' + queryString);
        }
                
        return null;
    }
}

花几分钟时间了解有关此控制器的更多信息,以及它如何与 Visualforce 页面配合使用。

  • 该属性是元素的列表。此列表以可直接使用的格式保存位置数据 按组件。locationsMap<String,Double><apex:mapMarker>
  • 该属性捕获 从页面表单提交的位置信息。此属性 此外,如果表单提交为空,则有效的默认值为 提供。(更健壮的实现将对表单进行更多错误检查 输入。currentPosition
  • 该属性,在 前面对 Visualforce 标记的描述。resultsAvailable
  • 操作方法被调用 当 Go! 被按下。这 方法完成所有工作,执行自定义 SOQL 查询并处理 结果到属性中 格式。findNearby<apex:commandButton>locations

如果要使用 的属性来提供额外的 信息(例如,仓库名称),您有多种选择。如果你的 方法返回 sObjects,您可以在 Visualforce 标记中引用相应的字段。如果你是 直接创建新对象,就像我们在这里一样,你可以创建一个内部类 将位置地图对象与标题字符串组合在一起。然后,返回 内部类对象到页面。title<apex:mapMarker>

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

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