<!--htmlEmailEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>49.0</apiVersion>
<!--isExposed can be true or false-->
<isExposed>true</isExposed>
</LightningComponentBundle>
// volumeEditor.js
import { LightningElement, api } from "lwc";
export default class VolumeEditor extends LightningElement {
_inputVariables = [];
@api
get inputVariables() {
return this._inputVariables;
}
// Set a field with the data that was stored from the flow.
// This data includes the public volume property of the custom volume
// component.
set inputVariables(variables) {
this._inputVariables = variables || [];
}
// Get the value of the volume input variable.
get volume() {
const param = this.inputVariables.find(({ name }) => name === "volume");
return param && param.value;
}
@api
validate() {
const volumeCmp = this.template.querySelector("lightning-slider");
const validity = [];
if (this.volume < 0 || this.volume > 100) {
volumeCmp.setCustomValidity("The slider range is between 0 and 100.");
validity.push({
key: "Slider Range",
errorString: "The slider range is between 0 and 100.",
});
} else {
volumeCmp.setCustomValidity("");
}
volumeCmp.reportValidity();
return validity;
}
handleChange(event) {
if (event && event.detail) {
const newValue = event.detail.value;
const valueChangedEvent = new CustomEvent("configuration_editor_input_value_changed", {
bubbles: true,
cancelable: false,
composed: true,
detail: {
name: "volume",
newValue,
newValueDataType: "Number",
},
});
this.dispatchEvent(valueChangedEvent);
}
}
}
<!--volumeEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>49.0</apiVersion>
<!--isExposed can be true or false-->
<isExposed>true</isExposed>
</LightningComponentBundle>
// SelectRecordAction.cls
public with sharing class SelectRecordAction {
@InvocableMethod(configurationEditor='c-select-record-editor')
public static List <Results> selectRecord(List<Requests> requestList) {
List<SObject> inputCollection = requestList[0].inputCollection;
//Store the first input record for output
SObject outputMember = inputCollection[0];
Results response = new Results();
response.outputMember = outputMember;
List<Results> responseWrapper= new List<Results>();
responseWrapper.add(response);
return responseWrapper;
}
public class Requests {
@InvocableVariable(label='Object for Input' description='Records for Input')
public List<SObject> inputCollection;
}
public class Results {
@InvocableVariable(label='Object for Storing Output' description='Records for Output')
public SObject outputMember;
}
}
<!--selectRecordEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<!--isExposed can be true or false-->
<isExposed>true</isExposed>
</LightningComponentBundle>
<!--displayRecordEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<!--isExposed can be true or false-->
<isExposed>true</isExposed>
</LightningComponentBundle>
<!---createContactActionEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>53.0</apiVersion>
<!--isExposed can be true or false-->
<isExposed>true</isExposed>
</LightningComponentBundle>
自定义属性编辑器 JavaScript 接口
自定义属性编辑器使用这些 JavaScript 函数与 Flow Builder 进行通信。
输入变量
该接口提供有关 FlowScreenField 元数据的数据。inputVariables
_inputVariables = [];
@api
get inputVariables() {
return this._inputVariables;
}
// Set a field with the data that was stored from the flow.
set inputVariables(variables) {
this._inputVariables = variables || [];
}
_elementInfo = {};
@api
get elementInfo() {
return this._elementInfo;
}
// Set a local variable with the data that was stored from flow.
set elementInfo(info) {
this._elementInfo = info || {};
}
// myComponent.js
import { LightningElement } from "lwc";
export default class MyComponent extends LightningElement {
opportunityId;
accountId;
customerLocation;
get flowName() {
if (this.customerLocation === "USA") {
flowname = "Survey_USA";
} else {
flowname = "Survey_General";
}
}
get inputVariables() {
return [
{
// Match with the input variable name declared in the flow.
name: "OpportunityID",
type: "String",
// Initial value to send to the flow input.
value: this.opportunityId,
},
{
// Match with the input variable name declared in the flow.
name: "AccountID",
type: "String",
// Initial value to send to the flow input.
value: this.accountId,
},
];
}
handleStatusChange(event) {
if (event.detail.status === "FINISHED") {
// set behavior after a finished flow interview.
}
}
}
使用注意事项
该组件仅支持该属性的活动流。lightning-flowflow-api-name
该事件返回这些参数。onstatuschange
如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow
参数
类型
描述
活动阶段
对象[]
流中 $Flow.ActiveStages 变量的当前值。在 API 版本 42.0 及更高版本中可用。
当前阶段
对象
流中 $Flow.CurrentStage 变量的当前值。在 API 版本 42.0 及更高版本中可用。
对于您设置的每个变量,请提供变量的名称、类型和值。对于 type (类型),请使用 API 名称作为流数据类型。例如,对于记录变量,请使用 SObject,对于文本变量,请使用 String。
{
name : "varName",
type : "flowDataType",
value : valueToSet
},
{
name : "varName",
type : "flowDataType",
value : [ value1, value2 ]
}, ...
JavaScript 文件中的此方法返回一个数字变量、一个日期集合变量和几个记录变量的值数组。Flow Builder 中的 Record 数据类型与此处的 SObject 相对应。
// myComponent.js
get inputVariables() {
return [
{ name: 'numVar', type: 'Number', value: 30 },
{ name: 'dateColl', type: 'String', value: [ '2016-10-27', '2017-08-01' ] },
// Sets values for fields in the account record (sObject) variable.
// Id uses the value of the accountId property. Rating uses a string.
{ name: 'account', type: 'SObject', value: {
'Id': this.accountId,
'Rating': 'Warm'
}},
// Set the contact record (sObject) variable to the value of the contact property.
// We're assuming the property contains the entire sObject for a contact record.
{ name: 'contact', type: 'SObject', value: this.contact }
]
}
// AccountController.apex
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static Account getAccount() {
return [SELECT Id, Name, LastModifiedDate, FROM Account LIMIT 1];
}
}
// myComponent.js
import { LightningElement, track, wire } from "lwc";
import getAccount from "@salesforce/apex/AccountController.getAccount";
export default class MyComponent extends LightningElement {
@track
inputVariables = [];
@wire(getAccount)
getAccountFromApex({ error, data }) {
if (error) {
console.log("Failed to get account data.");
} else if (data) {
this.inputVariables = [
{
name: "account",
type: "SOjbect",
value: data,
},
];
}
}
}
// myComponent.js
handleStatusChange(event) {
if(event.detail.status === 'FINISHED') {
// Redirect to another page in Salesforce., or
// Redirect to a page outside of Salesforce., or
// Show a toast, or something else
}
}
// InterviewsController.apex
public class InterviewsController {
@AuraEnabled(cacheable=true)
public static String getPausedId() {
// Get the ID of the running user.
String currentUser = UserInfo.getUserId();
// Find all of that user’s paused interviews for the Survey customers flow.
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;
}
}
// myComponent.js
import { LightningElement, wire } from "lwc";
import getPausedId from "@salesforce/apex/InterviewsController.getPausedId";
export default class MyComponent extends LightningElement {
flowName;
pausedInterviewId;
@wire(getPausedId)
getPausedInterviewId({ error, data }) {
if (error) {
// start a new interview since no interview id was returned.
this.flowName = "Survey_customers";
} else if (data) {
// resume the flow with the returned interview id.
this.pausedInterviewId = data;
}
}
}
通知运行时属性更改。Lightning Web 组件使用事件来报告从组件到流程的更改。为了通知流运行时属性更改,组件会触发事件。例如,在满足条件时使用条件可见性呈现屏幕组件时,或者将输出属性的值映射到流变量时,请使用此事件。有关代码示例,请参见。FlowAttributeChangeEventlightning-flow-support
设置屏幕流组件的反应性
屏幕流反应性意味着使屏幕流组件更改影响另一个同一屏幕组件。
在 Flow 构建器中添加和配置自定义 Lightning Web 组件之前,请创建自定义 LWC。
如果您计划构建反应式屏幕组件或使用支持的组件,则需要了解每个组件的注意事项。反应性包括:
确定哪个屏幕组件是源,哪个组件是反应式组件。
了解每组组件的不同交互。
示例:标准流量组件到标准流量组件
使用标准 Flow 组件是实现反应性的最简单方法。
使用标准 Flow 组件,您可以添加组件,为每个组件提供 API 名称和标签(如果需要),标识源和反应式组件,然后保存并运行流。
要使 Name 组件的 First Name 字段响应并显示来自 Text 组件的所有文本输入,请执行以下操作:
在流程中,添加组件和组件,并为每个组件提供标签。TextName
在组件的 First Name’s source 字段中,选择组件作为源。源可以是同一类型的任何组件,在本例中,Text 组件的类型和 Name 组件的 First Name 字段的类型为 String。NameText
保存并运行 Flow,然后在组件的输入中键入 .键入每个字符后,“名字”字段会立即更新。TextThis is Reactivity
示例:标准流组件到自定义 Lightning Web 组件
让您的自定义组件对标准 Flow 组件中的更改做出反应。
要创建自定义组件并将其添加到 Flow Builder 中,请执行以下操作:colorName
添加“文本”组件和“自定义”组件,并为每个组件提供标签。colorName
在 colorName 的属性中,选择组件作为“颜色名称”字段的源。Text
对文本输入所做的任何更改都会自动反映在自定义 Lightning Web 组件 .例如,在文本输入中输入洋红色会导致 LWC 显示具有相同颜色 magenta 样式的颜色名称。colorName
<!-- colorName.html -->
<template>
<p>
Color name has the same color :
<span style={colorStyle}>{color}</span>
</p>
</template>
// colorName.js
import { LightningElement, api } from "lwc";
export default class colorName extends LightningElement {
@api color;
get colorStyle() {
return "color:" + this.color;
}
}
@api textValue;
textValueToRender;
changeCounter = 0;
get textValue() {
return this.textValueToRender;
}
// Due to the nature of the Flow Runtime, this setter method
// is called after the FlowAttributeChangeEvent fired below. It also
// is invoked when reactivity leads to a change to the textValue property.
set textValue(newTextValue) {
this.changeCounter++;
this.textValueToRender = newTextValue;
}
handleTextInputChange(event) {
const event = new FlowAttributeChangeEvent('textValue', event.target.value);
this.dispatchEvent(event);
}
在这种情况下,每当用户在文本输入中输入更改以及 Flow Runtime 确定属性已更改时,都会递增。这种方法考虑了组件初始化和通过反应性进行的跨组件交互。使用中间变量呈现来自 set 方法的更新。在此示例中,填充此角色。this.changeCountertextValuetextValueToRender
//colorPicker.js
@api color;
inputValue;
selectedSwatchId;
ALL_SWATCHES =[{
'id': 1,
'color': 'red'
}, {
'id': 2,
'color': 'blue'
}, {
'id': 3,
'color': 'green'
}, {
'id': 4,
'color': 'yellow'
}];
set color(newColor) {
// Set the content of the input text to the color value:
this.inputValue = newColor;
// If the color matches a swatch, highlight it in the UI:
const foundSwatch = this.ALL_SWATCHES.find(swatch => swatch.color === newColor);
this.selectedSwatchId = foundSwatch && foundSwatch.id;
}
get color() {
return this.inputValue;
}
handleSwatchSelected(event) {
// Select get the swatch data for the clicked swatch:
const selectedSwatch = this.ALL_SWATCHES.find(swatch => swatch.id === event.target.dataset.id);
const changeEvent = new FlowAttributeChangeEvent('color', selectedSwatch.color);
this.dispatchEvent(changeEvent);
}
handleInputValueChange(event) {
this.inputValue = event.target.value;
const changeEvent = new FlowAttributeChangeEvent('color', this.inputValue);
this.dispatchEvent(changeEvent);
}
使用 get 方法将多个属性组合在一起,为视图构造派生变量。@api
//Salutation.js
@api firstName;
@api lastName;
@api ageName;
get salutation() {
return `Hello ${this.firstName} ${this.lastName}, you are ${this.age} years old.`;
}
const changeEvent = new FlowAttributeChangeEvent("prop", "value");
changeEvent.bubbles = false; // Don’t do this
changeEvent.composed = false; // Don’t do this
避免同时发生射击和事件,因为它可能导致争用条件。我们永远无法保证您的 Lightning Web 组件在导航过程开始时有时间呈现更新的值。FlowAttributeChangeEventsFlowNavigationXxx
务必在驱动更改的属性的方法中为派生属性触发事件。此外,在组件的方法中触发派生属性的事件,例如:FlowAttributeChangeEventsetFlowAttributeChangeEventconnectedCallback// 'Driving' attribute set method set stringToCount(newStringToCount) { this._stringToCount = newStringToCount; // Fire an event for 'derived' attribute const lengthChangeEvent = new FlowAttributeChangeEvent('stringLength', this._stringToCount.length); this.dispatchEvent(lengthChangeEvent); } connectedCallback() { // Fire an event for 'derived' attribute here as well const lengthChangeEvent = new FlowAttributeChangeEvent('stringLength', this.\_stringToCount.length); this.dispatchEvent(lengthChangeEvent); }
不要在方法中省略事件。 您必须将事件包含在两个位置。当流执行驱动更改的属性的 in 方法时,组件不在 DOM 中,事件基本上无处可去。在方法中添加事件可确保当流将组件添加到 DOM 时,流运行时可以使用该事件。FlowAttributeChangeEventconnectedCallbackFlowAttributeChangeEventFlowAttributeChangeEventsetconnectedCallback
不要在方法中省略事件。 触发驱动更改的属性的 in 方法可确保将更改应用于驱动更改的属性和派生属性。因此,包含自定义组件的反应性链可以正常工作。FlowAttributeChangeEventsetFlowAttributeChangeEventset
不要通过使用来延迟触发方法内部来省略该方法。 延迟可确保当流首次调用该方法时,组件位于 DOM 中。但是,延迟还意味着组件在流初始化屏幕后触发事件。因此,流运行时始终覆盖保留的值,并否决重新访问的值的设置。connectedCallbackPromise.resolve().then(...)FlowAttributeChangeEventsetFlowAttributeChangeEventsetset stringToCount(newStringToCount) { this._stringToCount = newStringToCount; const lengthChangeEvent = new FlowAttributeChangeEvent('stringLength', this._stringToCount.length); // This code overwrites preserved values. Don't do this. Promise.resolve().then(() => { this.dispatchEvent(lengthChangeEvent); }); }
导航更改事件最佳实践
除了 之外,您的 Lightning Web 组件还可以触发导航事件以在屏幕之间移动、暂停或完成流程。要使用导航事件,请从以下位置导入这些函数:FlowAttributeChangeEventlightning/flowSupport
将虚拟化概念深入到 Web 应用程序级别,主机环境是浏览器。Lightning Web Security 是在主机环境中运行的虚拟化引擎,用于创建和控制虚拟环境。命名空间 JavaScript 沙箱是虚拟环境。Lightning Web Security 授予每个虚拟环境对主机环境中特定资源的有限访问权限。这些资源包括全局对象、网络访问、Cookie 访问、本地存储等。
由于组件在其命名空间自己的虚拟环境中运行,Lightning Web Security 可以通过修改 JavaScript API 级别的代码来防止不安全的行为。修改或扭曲将应用于沙箱中。
Lightning Web Security 以最新的 TC39 标准为蓝本,这些标准随着浏览器平台的发展而发展。
下表比较了 Lightning Locker 功能与 Lightning Web Security。
特征
闪电储物柜
Lightning Web 安全
JavaScript 严格模式
执行
执行
DOM 访问遏制
组件只能访问它们创建的元素。请参阅 DOM 访问遏制。Lightning Locker 可防止您破坏 Lightning Web 组件之间的 Shadow DOM 封装。您无法修改属性。shadowRoot
组件对 DOM 的访问由浏览器通过 Shadow DOM 控制,Shadow DOM 是在 Web 浏览器中实现的 Web 标准。Lightning Web Security 通过要求 to be 强制执行 closed,这提供了额外的封装限制。参见 MDN Web 文档:ShadowRoot.mode。shadowRootmodeclosed
Salesforce 建议将 Visual Studio Code 与 Salesforce Extensions for Visual Studio Code 结合使用来开发 Lightning Web 组件。针对 Lightning Web Security 的错误的 ESLint 规则显示在 VS Code 中,您的代码违反了这些规则。这些规则映射到影响代码的扭曲。规则冲突的弹出窗口包括指向规则文档的链接。
在这里,您可以看到 lint 规则冲突的弹出窗口,以及指向相关规则文档的链接。
此弹出窗口显示 lint 警告,而不是错误。
针对项目运行基本规则
安装 ESLint 规则和配置,如添加 ESLint 对 Lightning Web 安全的支持部分所述。
运行常用命令以对代码进行 lint 检查。如果您使用的是 Salesforce DX 项目,请运行适合您环境的命令。npmyarn$ npm run lint或$ yarn lint如果您使用的不是 Salesforce DX 项目,则可以在 LWC 组件上运行该命令。例如,如果您的组件包含在文件夹中:eslintlwc$ eslint **/lwc/**无论哪种情况,输出都类似于此示例。10:16 error Document#fullscreen is prohibited in Lightning Locker @locker/locker/distorted-document-blocked-properties ✖ 1 problem (1 error, 0 warnings) error Command failed with exit code 1.
在此示例中,在命名空间 A 中定义,在命名空间 B 中定义。redFunctionblueFunction
//module in namespace A
function redFunction(arg) {
arg.foo = "bar"; // mutation on the arg object
console.log(arg.foo); // "bar"
}
//module in namespace B
function blueFunction() {
var someObject = {};
redFunction(someObject); // blueFunction expects foo property added by redFunction
console.log(someObject.foo); // undefined
}
当 uses 时,它不会接收在命名空间 A 中完成的属性的突变。命名空间可以通过相互传递数据进行通信,但突变不会持续到命名空间边界之外。blueFunctionredFunctionfoo
Lightning Web 组件不能使用 或 全局属性来查询 DOM 元素。例如,使用 而不是 .windowdocumentthis.template.querySelector()document.querySelector()
注意
使用 Lightning Web Security (LWS),组件对 DOM 的访问由浏览器通过 Shadow DOM 控制,Shadow DOM 是在 Web 浏览器中实现的 Web 标准。LWS 通过要求 to be 来强制关闭,这提供了额外的封装限制。参见 MDN 中的 ShadowRoot.mode。shadowRootmodeclosed
如果 Lightning Web 组件和 Aura 组件属于同一命名空间,则它们共享同一实例。SecureElement
Lightning Web 组件还使用包装器。SecureLightningElementSecureLightningElement
可用:Lightning Web 组件
基类的安全包装器。Lightning Web 组件扩展了基类,并在运行时 Locker 使用 .创建 Lightning Web 组件时,请勿直接扩展。LightningElementLightningElementSecureLightningElementSecureLightningElement
不支持这些 API。
Element.attachShadow()
Element.shadowRoot – 改用this.template
Element.slot
SecureTemplate
可用:Lightning Web 组件
对象的安全包装器,表示节点。templateshadowRoot
不支持这些 API。
Node.localName
Node.namespaceURI
Node.nodeValue
Node.nextSibling
Node.previousSibling
Node.parentElement
Node.parentNode
Node.prefix
NonDocumentTypeChildNode.nextElementSibling
NonDocumentTypeChildNode.previousElementSibling
DocumentOrShadowRoot.getSelection()
DocumentOrShadowRoot.styleSheets
DocumentOrShadowRoot.pointerLockElement
DocumentFragment.getElementById()
ParentNode.append
ParentNode.prepend
constructor
EventTarget.dispatchEvent
pictureInPictureElement
注意
Lightning Web Security 不使用包装器。它使用 JavaScript 沙盒中的 API 失真来有选择地修改启用非安全行为的 API。
Lightning Locker 阻止使用第三方 Web 组件,以防止 Salesforce 平台上的安全风险。
Web 组件是自定义元素。若要定义自定义元素,必须使用 API。但是,此 API 允许您按标记名称全局注册组件。全局注册标记名称存在安全风险,因为攻击者可以创建任何已注册自定义元素的实例,并可能获得对敏感信息的访问权限。Lightning Locker 的包装器阻止了创建自定义 Web 组件的方法。customElements.defineSecureWindowcustomElements
Locker API 查看器工具中的包装器列表显示不受支持。SecureWindowcustomElements
启用 Lightning Locker 后,使用或 API 返回元素的组件可能会遇到大型 DOM 树的内存泄漏和性能问题。从 DOM 中删除组件时,内存有时不会释放。即使在 DevTools 中手动调用垃圾回收,垃圾回收也不会清除内存。浏览器可能会使用不断增加的内存量,并可能因内存不足错误而崩溃。this.template.querySelectorthis.template.querySelectorAll
此问题的唯一解决方法是在运行组件的组织中启用 Lightning Web Security。垃圾回收在 LWS 下正常工作。
或者,考虑使用 refs 而不是 .querySelector
Lightning Locker工具
Lightning Locker 工具可帮助您开发与 Lightning Locker 兼容并高效运行的安全代码。Lightning Locker API 查看器
使用 Lightning Locker API 查看器查看 Lightning Locker 支持哪些标准 DOM API 用于最复杂的包装器:、 和 。SecureDocumentSecureElementSecureWindow
选择 Lightning Locker 在整个组织中使用的 API 版本。默认值为当前 API 版本,其中包括最新的 Locker 安全增强功能。当自定义组件仅符合旧版本中的 Locker 时,请选择较早的 API 版本。当组件符合当前安全增强功能时,您可以将设置更改为当前 API 版本。
在您的沙盒组织更新到新的 Salesforce 版本后,自定义组件可能会与 Lightning Locker 安全增强功能发生冲突。检查 Web 控制台中是否有消息。
我们建议更新自定义组件以符合最新的 Locker API 版本,但我们知道更新可能需要一些时间。您的组织还可能依赖于包含第三方开发人员必须更新的自定义组件的托管包。将您的组织设置为使用较旧的 Locker API 版本,以便开发人员有时间更新自定义 Lightning 组件并遵守 Locker 的最新安全增强功能。
在沙盒组织中验证自定义组件是否在将 Locker API 版本设置为最新版本的情况下正确执行。然后,可以将 Locker API 版本更改为生产组织中的最新版本,以利用最新的安全增强功能。
注意
Locker API 版本设置在 Winter ’20 版本中首次提供。您可以选择的最早 Locker API 版本是 46.0,它启用了 Summer ’19 版本的 Locker 功能。
使用更衣室 API 版本的位置
组织的 Locker API 版本会影响 Lightning Locker 影响什么?中列出的区域中使用的所有 Lightning 组件。
在组织中启用 Lightning Web Security 时,组织的 Locker API 版本对 Lightning Web 组件没有影响。
注意
每个 Lightning Web 组件都有一个配置文件,其中包含 .组件和 Locker API 版本使用相同的版本号策略来与 Salesforce 版本保持一致。但是,Locker API 版本的组织设置与组件的 .组织中设置的 Locker API 版本适用于所有 Lightning 组件,无论其设置如何。apiVersionapiVersionapiVersionapiVersion
Locker API 版本更改
查看 API 版本中的安全更改,以帮助确定自定义组件的兼容性。
Locker API 版本
安全更改
描述
53.0 及更高版本
没有
此版本中的 Lightning Locker 更改不会影响自定义组件。
52.0
防止多个潜在的基于突变的跨站点脚本 (mXSS) 向量。
Lightning Locker 加强了标记的清理以提高安全性。此更改适用于所有 API 版本。无法通过选择较早的 API 版本来回滚此更改。
51.0
没有
此版本中的 Lightning Locker 更改不会影响自定义组件。
50.0
没有
此版本中的 Lightning Locker 更改不会影响自定义组件。
49.0
限制 API$A.getCallback()
Lightning Locker 包装该功能。包装的 JavaScript 必须遵守 Locker 的安全限制。请参阅 Locker API 查看器,了解 Lightning Locker 中 JavaScript API 的支持状态。$A.getCallback()$A.getCallback()
无论是用户驱动还是应用调用,在不重新加载整个页面的情况下同步数据的能力都是一项关键的用户体验要求。RefreshView API 和模块提供了一种在 LWC 和 Aura 中刷新组件数据的标准方法。lightning/refresh
RefreshView API 对刷新范围的详细控制使你能够创建精细的用户体验,同时保持向后兼容性。
RefreshView API 为您提供了更新组件层次结构(称为视图)的选项,而无需重新加载整个页面。此刷新可确保与订阅该视图中刷新事件的组件外部源的数据完全同步。RefreshView API 支持由最终用户或 Web 组件触发的刷新。RefreshView API 为 LWC 组件中的数据刷新体验提供了标准机制,允许灵活控制刷新范围。
RefreshView API 可以刷新 Salesforce 平台容器以及自定义 LWC 和 Aura 组件的数据。
Lightning 数据服务支持 RefreshView API。
使用 RefreshView API 的限制
RefreshView API 可以在已启用 Lightning Web Security 或 Lightning Locker 的组织中使用。每个安全体系结构的注册容器和处理程序的协议都不同。
基本 Lightning Aura 组件目前不支持 RefreshView API。
RefreshView API 用户体验
可靠的内容刷新是一项基本的 Web UX 要求,可以由用户驱动,也可以由应用调用。
用户触发的刷新的典型流程为:
Lightning Web 组件显示一个按钮(或其他用户界面控件),用于启动一个进程,该进程将组件中显示的数据与其源同步。
点击按钮时,按钮组件将调度一个事件。RefreshEvent
向 RefreshView API 注册的最近级别的容器组件将收到 ,停止其传播。RefreshEvent
import { LightningElement } from "lwc";
import { RefreshEvent } from "lightning/refresh";
export default class RefreshButton extends LightningElement {
// signal a refresh programmatically
// or via a button click
beginRefresh() {
this.dispatchEvent(new RefreshEvent());
}
}
// refreshButton.js
import { LightningElement } from "lwc";
import { RefreshEvent } from "lightning/refresh";
export default class RefreshButton extends LightningElement {
// signal a refresh programmatically
// or via a button click
beginRefresh() {
this.dispatchEvent(new RefreshEvent());
}
}
//refreshHandler.js
import { LightningElement } from "lwc";
import { registerRefreshHandler, unregisterRefreshHandler } from "lightning/refresh";
export default class RefreshHandler extends LightningElement {
refreshHandlerID;
connectedCallback() {
this.refreshHandlerID = registerRefreshHandler(this, this.refreshHandler);
// if the component runs in an org with Lightning Locker instead of LWS, use
// this.refreshHandlerID = registerRefreshHandler(this.template.host, this.refreshHandler.bind(this));
}
disconnectedCallback() {
unregisterRefreshHandler(this.refreshHandlerID);
}
refreshHandler() {
// example usage case for refresh participant
// fetch some data and report status once complete
let endPoint = "https://api.<your company>.com";
return new Promise((resolve) => {
fetch(endPoint, {
method: "GET",
});
resolve(true);
});
}
}
根据安全策略,由 Lightning 组件创建的会话不会启用 API 访问。此限制甚至会阻止您的 Apex 代码对 Salesforce 进行 API 调用。通过对特定 API 调用使用命名凭据,您可以谨慎且有选择地绕过此安全限制。
对启用 API 的会话的限制并非偶然。请仔细检查使用命名凭据的任何代码,以确保不会造成漏洞。
在使用 Apex 进行 API 调用之前,请查看是否可以从 JavaScript 进行 API 调用。您可以在 JavaScript 中使用 Lightning Data Service (LDS) 来处理 Salesforce 记录的数据和元数据。Lightning Data Service 建立在公共用户界面 API 之上,但它仅支持 API 的一个子集。该子集涵盖了许多处理数据的典型用例。您无法从 JavaScript 代码调用除 LDS 之外的 Salesforce API。
要向 Lightning Web 组件公开 Apex 方法,该方法必须是 and 或 。用 注释方法。staticglobalpublic@AuraEnabled
语法
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContactList() {
return [
SELECT Id, Name, Title, Phone, Email, Picture__c
FROM Contact
WHERE Picture__c != null
WITH SECURITY_ENFORCED
LIMIT 10
];
}
}
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContactList() {
return [
SELECT Id, Name, Title, Phone, Email, Picture__c
FROM Contact
WHERE Picture__c != null
WITH SECURITY_ENFORCED
LIMIT 10
];
}
}
// ContactController.cls
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> findContacts(String searchKey) {
String key = '%' + searchKey + '%';
return [
SELECT Id, Name, Title, Phone, Email, Picture__c
FROM Contact
WHERE Name LIKE :key AND Picture__c != null
WITH SECURITY_ENFORCED
LIMIT 10
];
}
}
// apexWireMethodWithParams.js
import { LightningElement, wire } from "lwc";
import findContacts from "@salesforce/apex/ContactController.findContacts";
/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 300;
export default class ApexWireMethodWithParams extends LightningElement {
searchKey = "";
@wire(findContacts, { searchKey: "$searchKey" })
contacts;
handleKeyChange(event) {
// Debouncing this method: Do not update the reactive property as long as this function is
// being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
window.clearTimeout(this.delayTimeout);
const searchKey = event.target.value;
this.delayTimeout = setTimeout(() => {
this.searchKey = searchKey;
}, DELAY);
}
}
public with sharing class ApexTypesController {
@AuraEnabled(cacheable=true)
public static String checkApexTypes(CustomWrapper wrapper) {
// The values are based on the data that is defined in the
// apexWireMethodWithComplexParams Lightning web component.
String response =
'You entered "' +
wrapper.someString +
'" as String, and "' +
wrapper.someInteger +
'" as Integer value. The list contained ' +
wrapper.someList.size() +
' items.';
return response;
}
}
public with sharing class CustomWrapper {
@TestVisible
class InnerWrapper {
@AuraEnabled
public Integer someInnerInteger { get; set; }
@AuraEnabled
public String someInnerString { get; set; }
}
@AuraEnabled
public Integer someInteger { get; set; }
@AuraEnabled
public String someString { get; set; }
@AuraEnabled
public List<InnerWrapper> someList { get; set; }
}
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContactList() {
return [
SELECT Id, Name, Title, Phone, Email, Picture__c
FROM Contact
WHERE Picture__c != NULL
WITH SECURITY_ENFORCED
LIMIT 10
];
}
}
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> findContacts(String searchKey) {
String key = '%' + searchKey + '%';
return [
SELECT Id, Name, Title, Phone, Email, Picture__c
FROM Contact
WHERE Name LIKE :key AND Picture__c != NULL
WITH SECURITY_ENFORCED
LIMIT 10
];
}
}
import { LightningElement, wire } from 'lwc';
import { getRecord, notifyRecordUpdateAvailable } from 'lightning/uiRecordApi';
import apexUpdateRecord from '@salesforce/apex/Controller.apexUpdateRecord';
export default class Example extends LightningElement {
@api recordId;
// Wire a record
@wire(getRecord, { recordId: '$recordId', fields: ... })
record;
async handler() {
// Do something before the record is updated
showSpinner();
// Update the record via Apex
await apexUpdateRecord(this.recordId);
// Notify LDS that you've changed the record outside its mechanisms
// Await the Promise object returned by notifyRecordUpdateAvailable()
await notifyRecordUpdateAvailable([{recordId: this.recordId}]);
hideSpinner();
}
}
import { refreshApex } from "@salesforce/apex";
refreshApex(valueProvisionedByApexWireService);
其中 是用 Apex 注释的属性,或者如果注释了函数,则为有线函数接收的参数。valueProvisionedByApexWireService@wire// Example of refreshing data for a wired property // after you update data via an LDS-managed module (lightning/uiRecordApi). import { updateRecord } from 'lightning/uiRecordApi'; import { refreshApex } from '@salesforce/apex'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record using updateRecord(recordInput) // Refresh Apex data that the wire service provisioned handler() { updateRecord(recordInput).then(() => { refreshApex(this.opptiesOverAmount); }); }// Example of refreshing data for a wired property // after you update data via imperative Apex. import { refreshApex } from '@salesforce/apex'; import { notifyRecordUpdateAvailable } from 'lightning/uiRecordApi'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record in Apex, such as via a button click // Refresh Apex data that the wire service provisioned handler() { updateRecordApexMethod() .then(() => { refreshApex(this.opptiesOverAmount); notifyRecordUpdateAvailable(recordIds); // Refresh the Lightning Data Service cache }); }//Example of refreshing data for a wired function import { refreshApex } from '@salesforce/apex'; import getActivityHistory from '@salesforce/apex/ActivityController.getActivityHistory; @wire(getActivityHistory, { accountId: '$recordId', max: '500' }) wiredGetActivityHistory(value) { // Hold on to the provisioned value so we can refresh it later. this.wiredActivities = value; // Destructure the provisioned value const { data, error } = value; if (data) { ... } else if (error) { ... } ... } handler() { refreshApex(this.wiredActivities); }
<!-- opportunitiesOverAmount.html -->
<template>
<!-- Display a list of opportunities -->
<p>
List of opportunities over
<lightning-formatted-number
value={amount}
format-style="currency"
></lightning-formatted-number>
</p>
<template lwc:if={opptiesOverAmount.data}>
<template for:each={opptiesOverAmount.data} for:item="oppty" for:index="idx">
<div key={oppty.Id} class="slds-m-bottom_medium">
<p>{idx}. {oppty.Name}</p>
<p>
<lightning-formatted-number
value={oppty.Amount}
format-style="currency"
></lightning-formatted-number>
</p>
<p>
<lightning-formatted-date-time value={oppty.CloseDate}></lightning-formatted-date-time>
</p>
<p><lightning-badge label={oppty.StageName}></lightning-badge></p>
</div>
</template>
<!-- Click the button to change the opportunities. Requires the data to be refetched
and rerendered -->
<lightning-button label="Mark all as Closed Won" onclick={handleClick}></lightning-button>
</template>
</template>
import { LightningElement, api, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getActivityHistory from '@salesforce/apex/GalleryApexController.getActivityHistory';
export default class GalleryApexMaster extends LightningElement {
@api recordId;
wiredActivities;
@wire(getActivityHistory, { accountId: '$recordId', max: '500' })
wiredGetActivityHistory(value) {
// Hold on to the provisioned value so we can refresh it later.
this.wiredActivities = value; // track the provisioned value
const { data, error } = value; // destructure the provisioned value
if (data) { ... }
else if (error) { ... }
...
}
handleLogACall() {
// Use the value to refresh wiredGetActivityHistory().
return refreshApex(this.wiredActivities);
}
}
若要进行长时间运行的标注,请定义一个返回对象的 Apex 方法。(先不用担心注解的属性。我们很快就会解释。Continuation@AuraEnabled@AuraEnabled(continuation=true cacheable=true) public static Object startRequest() { // Create continuation. Argument is timeout in seconds. Continuation con = new Continuation(40); // more to come here return con; }
public with sharing class SampleContinuationClass {
// Callout endpoint as a named credential URL
// or, as shown here, as the long-running service URL
private static final String LONG_RUNNING_SERVICE_URL =
'<insert your callout URL here>';
// Action method
@AuraEnabled(continuation=true cacheable=true)
public static Object startRequest() {
// Create continuation. Argument is timeout in seconds.
Continuation con = new Continuation(40);
// Set callback method
con.continuationMethod='processResponse';
// Set state
con.state='Hello, World!';
// Create callout request
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(LONG_RUNNING_SERVICE_URL);
// Add callout request to continuation
con.addHttpRequest(req);
// Return the continuation
return con;
}
// Callback method
@AuraEnabled(cacheable=true)
public static Object processResponse(List<String> labels, Object state) {
// Get the response by using the unique label
HttpResponse response = Continuation.getResponse(labels[0]);
// Set the result variable
String result = response.getBody();
return result;
}
}
// This class is an anti-pattern.
public with sharing class UnsafeExpenseController {
// ns refers to namespace; leave out ns__ if not needed
// This method is vulnerable because it doesn't enforce FLS.
@AuraEnabled
public static List<ns__Expense__c> get_UNSAFE_Expenses() {
return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
}
}
public with sharing class ExpenseController {
// This method is recommended because it enforces FLS.
@AuraEnabled
public static List<ns__Expense__c> getExpenses() {
// Query the object safely
return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
ns__Reimbursed__c, CreatedDate
FROM ns__Expense__c WITH SECURITY_ENFORCED];
}
}
public with sharing class ExpenseControllerStripped {
@AuraEnabled
public static List<ns__Expense__c> getExpenses() {
// Query the object but don't use WITH SECURITY_ENFORCED
List<ns__Expense__c> expenses =
[SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
ns__Reimbursed__c, CreatedDate
FROM ns__Expense__c];
// Strip fields that are not readable
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE,
expenses);
// Throw an exception if any data was stripped
if (!decision.getModifiedIndexes().isEmpty()) {
throw new AuraHandledException('Data was stripped');
}
return expenses;
}
}
有关更多详细信息和示例,请参阅 Apex 开发人员指南。遗留代码 using 和 methodsDescribeSObjectResultDescribeFieldResult
在子句和方法可用之前,强制执行对象和字段权限的唯一方法是通过调用 and 方法来检查当前用户的访问权限级别。然后,如果用户具有必要的权限,则执行特定的 DML 操作或查询。WITH SECURITY_ENFORCEDstripInaccessible()Schema.DescribeSObjectResultSchema.DescribeFieldResult
此示例使用 describe result 方法。此方法需要更多的样板代码行,因此我们建议改用子句或方法。WITH SECURITY_ENFORCEDstripInaccessible()
public with sharing class ExpenseControllerLegacy {
@AuraEnabled
public static List<ns__Expense__c> getExpenses() {
String [] expenseAccessFields = new String [] {'Id',
'Name',
'ns__Amount__c',
'ns__Client__c',
'ns__Date__c',
'ns__Reimbursed__c',
'CreatedDate'
};
// Obtain the field name/token map for the Expense object
Map<String,Schema.SObjectField> m = Schema.SObjectType.ns__Expense__c.fields.getMap();
for (String fieldToCheck : expenseAccessFields) {
// Call getDescribe to check if the user has access to view field
if (!m.get(fieldToCheck).getDescribe().isAccessible()) {
// Pass error to client
throw new System.NoAccessException();
}
}
// Query the object safely
return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
}
}
要访问对象和字段 API 名称,请使用语句。所有对象和字段导入都来自作用域包。import@salesforce/schema
若要导入对对象的引用,请使用以下语法。
import objectName from "@salesforce/schema/object";
import objectName from "@salesforce/schema/namespace__object";
import POSITION_OBJECT from "@salesforce/schema/Position__c";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
若要导入对字段的引用,请使用以下语法。
import FIELD_NAME from "@salesforce/schema/object.field";
import POSITION_LEVEL_FIELD from "@salesforce/schema/Position__c.Level__c";
import ACCOUNT_NAME_FIELD from "@salesforce/schema/Account.Name";
import SPANNING_FIELD_NAME from "@salesforce/schema/OBJECT.RELATIONSHIP.FIELD";
import POSITION_HIRINGMANAGER_NAME_FIELD__c from "@salesforce/schema/Position__c.HiringManager__r.Name__c";
import ACCOUNT_OWNER_NAME_FIELD from "@salesforce/schema/Account.Owner.Name";
// User.Place_of_birth__c is a compound custom field of type Geolocation
const PLACE_OF_BIRTH_LAT_FIELD = "User.Place_of_birth__Latitude__s";
const PLACE_OF_BIRTH_LON_FIELD = "User.Place_of_birth__Longitude__s";
提示
使用 Lightning Web Components Trailhead 构建熊跟踪应用程序项目使用复合地理位置字段,并将纬度和经度数据转换为地图标记。
导入限制
Salesforce 支持许多对象或字段后缀来表示不同类型的数据。Lightning Web 组件支持导入对标准对象的引用,以及仅支持导入对自定义对象的引用 ()。例如:__c
import POSITION_OBJECT from "@salesforce/schema/Position__c";
有关与 Wire Service 一起使用的示例,请参阅 github.com/trailheadapps/lwc-recipes 存储库中的组件。lightning-inputldsCreateRecordlightning-input-location
显示地理位置输入的复合字段。lightning-input-name
显示名称输入的复合字段。lightning-input-rich-text
显示带有用于设置内容格式的工具栏的富文本编辑器。lightning-radio-group
显示两个或多个复选框,用于选择单个或多个选项。lightning-textarea
显示多行文本输入字段。
尽管支持许多字段类型,但在使用不同的字段类型时,请考虑其他基本组件。lightning-input
假设您希望在用户界面中包含带有名字和姓氏字段的称呼选择列表。您可以添加 for the salutation 选择列表或使用 .下一个示例用于显示称呼、名字和姓氏字段及其当前值。要确定哪个基本组件最适合您的用例,请参阅组件参考。lightning-comboboxlightning-input-namelightning-input-name
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContacts(String accId) {
return [
SELECT AccountId, Id, FirstName, LastName, Title, Phone, Email
FROM Contact
WHERE AccountId = :accId
WITH SECURITY_ENFORCED
];
}
}
//CaseController.cls
public with sharing class CaseController {
@AuraEnabled(cacheable=true)
public static list<Account> getNewCasesForAccounts(){
return [SELECT Name, (
SELECT Id, CaseNumber, Status FROM Cases WHERE toLabel(Status) = 'New')
FROM Account WHERE Id IN (SELECT AccountId FROM Case)];
}
}
在 JavaScript 文件中,用于调用 Apex 方法。使用键定义包含子项的行。包含子项的行显示带有 V 形按钮,用于切换子项。myTreeGrid.js@wire_children