在自适应表单块中创建自定义表单组件
Edge Delivery Services Forms 提供自定义功能,允许前端开发人员构建定制的表单组件。这些自定义组件可以无缝集成到所见即所得的创作体验中,使表单作者能够在表单编辑器中轻松添加、配置和管理这些组件。通过自定义组件,作者可以增强功能,同时确保流畅、直观的创作过程。
本文档概述了通过设置原生 HTML 表单组件的样式来创建自定义组件的步骤,以改善用户体验并增加表单的视觉吸引力。
架构概述
表单块的自定义组件遵循 MVC(模型-视图-控制器) 架构模式:
模型
-
由每个
field/component
的 JSON 架构定义。 -
可编辑属性在相应的 JSON 文件中指定(参见 blocks/form/models/form-components)。
-
这些属性可在表单构建器中供作者使用,并作为字段定义(fd)的一部分传递给组件。
视图
-
每种字段类型的 HTML 结构在 form-field-types 中进行描述。
-
这是组件的基础结构,可扩展或修改。
-
每个 OOTB(开箱即用)组件的基础 HTML 结构在 form-field-types 中均有文档说明。
控制器/组件逻辑
- 通过 JavaScript 实施,可以是 OOTB(开箱即用)组件,也可以是自定义组件。- 自定义组件位于
blocks/form/components
文件夹中。
OOTB 组件
OOTB(开箱即用) 组件为自定义开发提供基础:
-
OOTB 组件位于
blocks/form/models/form-components
中。 -
每个 OOTB 组件都有一个 JSON 文件,用于定义其可编辑属性(例如
_text-input.json
、_drop-down.json
)。 -
这些属性可在表单构建器中供作者使用,并作为字段定义(fd)的一部分传递给组件。
-
每个 OOTB(开箱即用)组件的基础 HTML 结构在 form-field-types 中均有文档说明。
扩展现有的 OOTB 组件,可以复用其基础结构、行为和属性,并根据需要进行自定义。
-
自定义组件必须从预定义的 OOTB 组件集扩展。
-
系统会根据字段 JSON 中的
viewType
属性识别应扩展的 OOTB 组件。 -
系统维护了允许的自定义组件变体注册表。只能使用注册表中列出的变体,例如
mappings.js
中的customComponents[]
。 -
渲染表单时,系统会检查变体属性或
:type/fd:viewType
,如果与已注册的自定义组件匹配,则从blocks/form/components
文件夹加载相应的 JS 和 CSS 文件。 -
然后系统会将自定义组件应用到 OOTB 组件的基础 HTML 结构上,从而增强或覆盖其行为和外观。
自定义组件的结构
要创建自定义组件,可以使用 Scaffolder CLI 设置组件所需的文件和文件夹,然后为自定义组件编写代码。
-
自定义组件存放在
blocks/form/components
文件夹中。 -
每个自定义组件必须放置在其独立文件夹中,文件夹名称与组件名称相同,例如卡片。在该文件夹内,应包含以下文件:
- _cards.json——扩展某个 OOTB 组件定义的 JSON 文件,定义其可编辑属性(模型[])以及加载时的内容结构(定义[])。
- cards.js——包含主要逻辑的 JavaScript 文件。
- cards.css——可选,用于样式。
-
文件夹名称必须与 JS/CSS 文件名称保持一致。
在自定义组件中复用和扩展字段
在自定义组件的 JSON 文件中定义字段(无论是基础、验证、帮助等任意字段组)时,请遵循以下最佳做法以保持可维护性和一致性:
-
通过引用现有的共享容器或字段定义来复用标准/共享字段(例如
../form-common/_basic-input-placeholder-fields.json#/fields
、../form-common/_basic- validation-fields.json#/fields
)。这样可继承所有标准选项,而无需重复定义。 -
仅在容器中显式添加新的或自定义字段。这样可以保持架构精简(DRY)且聚焦。
-
移除或避免重复已通过引用包含的字段。只需定义与组件逻辑唯一相关的字段。
-
根据需要引用帮助容器和其他共享内容(例如
../form-common/_help-container.json
),以确保一致性和可维护性。
- 这种模式可简化未来的逻辑更新或扩展,并确保自定义组件与表单系统的其他部分保持一致。
- 在添加新字段之前,请务必检查是否已有共享容器或字段定义可用。
为自定义组件定义新属性
-
如果需要从作者那里获取自定义组件的新属性,可以在组件的 JSON 文件中通过在
fields[]
数组中定义字段来实现。 -
自定义组件通过 :type 属性进行识别,该属性可在 JSON 文件中设置为
fd:viewType
(例如fd:viewType: cards
)。这样系统就能识别并加载正确的自定义组件,因此这对自定义组件来说是必需的。 -
在 JSON 定义中添加的任何新属性都会作为属性在字段定义中可用。组件的 JS 逻辑中的
<propertyName>
自定义组件 JavaScript API
自定义组件 JavaScript API 定义了如何控制自定义表单组件的行为、外观和响应性。
修饰函数
修饰 函数是自定义组件的入口点。它会初始化组件,将其与 JSON 定义关联,并允许您操作组件的 HTML 结构和行为。
函数签名:
export default function decorate(element, fieldJson, container, formId)
{
// element: The HTML structure of the OOTB component you are extending
// fieldJson: The JSON field definition (all authorable properties)
// container: The parent element (fieldset or form)
// formId: The id of the form
// ... your logic here ...
}
它可以:
-
修改元素:添加事件监听器、更新属性或插入额外标记。
-
访问 JSON 属性:使用
fd.properties.<propertyName>
读取 JSON 架构中定义的值,并在组件逻辑中应用。
订阅函数
订阅 函数使您的组件能够对字段值或自定义事件的变化作出响应。这确保组件与表单的数据模型保持同步,并可动态更新其 UI。
函数签名:
import { subscribe } from '../../rules/index.js';
export default function decorate(fieldDiv, fieldJson, container, formId) {
// Access custom properties defined in the JSON
const { initialText, finalText, time } = fieldJson?.properties;
// ... setup logic ...
subscribe(fieldDiv, formId, (_fieldDiv, fieldModel) => {
fieldModel.subscribe(() => {
// React to custom event (e.g., resetCardOption)
// ... logic ...
}, 'resetCardOption');
});
}
它可以:
-
注册回调:调用 subscribe(element, formId, callback) 来注册回调,以便在字段数据发生变化时执行。使用两个回调参数:
- element:表示字段的 HTML 元素。
- fieldModel:表示字段状态和事件 API 的对象。
-
监听更改或事件:使用
fieldModel.subscribe((event) => { ... }, 'eventName')
在值发生变化或触发自定义事件时执行逻辑。事件对象包含有关更改的详细信息。
创建自定义组件
在本节中,您将学习如何通过扩展 OOTB 单选按钮组件来创建一个 卡片自定义组件。
1. 代码设置
1.1 文件与文件夹
第一步是设置自定义组件所需的文件,并将其与代码库中的代码连接起来。此过程由 AEM Forms Scaffolder CLI 自动完成,可更快速地生成基架并连接所需文件。
- 打开终端并导航到表单项目的根目录。
- 运行以下命令:
npm install
npm run create:custom-component
该命令将:
- 提示您为新组件命名。例如,在此用例中输入卡片。
- 要求您选择 一个基础组件(选择单选按钮组)
这将创建所有必要的文件夹和文件,包括:
blocks/form/
└── components/
└── cards/
├── cards.js
└── cards.css
└── _cards.json
如 CLI 输出所示,将其与代码库的其他部分连接起来。
它会自动执行以下功能:
- 将卡片添加到过滤器中,以便在自适应表单块中进行添加。
- 更新
mappings.js
的允许列表,以包含新的卡片组件。 - 在通用编辑器中的 自定义组件 列表下注册卡片组件的定义。
1.2 在通用编辑器中使用组件
-
刷新通用编辑器:在通用编辑器中打开表单并刷新页面,以确保加载来自代码库的最新代码。
-
添加自定义组件
-
单击表单画布上的 添加 (+) 按钮。
-
滚动到“自定义组件”部分。
-
选择新创建的 卡片组件 并将其插入表单中。
-
由于 cards.js
中尚无代码,自定义组件将呈现为一个单选组。
1.3 在本地预览和测试
现在表单已包含自定义组件,您可以为该表单创建代理,在本地进行修改并实时查看变更:
-
前往终端并运行
aem up
。 -
打开在
http://localhost:3000/{path-to-your-form}
启动的代理服务器(路径示例:/content/forms/af/custom-component-form
)。
2. 为自定义组件实施自定义行为
2.1 设置自定义组件的样式
为组件添加 card 类以进行样式化,并为每个单选项添加一张图片;请使用以下示例代码。
使用card.js为组件设置样式
import { createOptimizedPicture } from '../../../../scripts/aem.js';
export default function decorate(element, fieldJson, container, formId) {
element.classList.add('card');
element.querySelectorAll('.radio-wrapper').forEach((radioWrapper) => {
const image = createOptimizedPicture(
'https://main--afb--jalagari.hlx.live/lab/images/card.png',
'card-image'
);
radioWrapper.appendChild(image);
});
return element;
}
使用cards.css添加运行时行为
.card .radio-wrapper {
min-width: 320px; /* or whatever width fits your design */
max-width: 340px;
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
flex: 0 0 auto;
scroll-snap-align: start;
padding: 24px 16px;
margin-bottom: 0;
position: relative;
transition: box-shadow 0.2s;
display: flex;
align-items: flex-start;
gap: 12px;
}
现在,卡片组件将显示如下效果:
2.2 使用订阅函数添加动态行为
当下拉列表发生变化时,会获取卡片并设置在单选按钮组的枚举中。但当前视图并未对此进行处理。因此呈现效果如下:
当调用 API 时,会设置字段模型,并且必须监听其变化以相应地渲染视图。这可以通过 订阅函数 实现。
让我们将上一步的视图代码转换为一个函数,并在 cards.js
的订阅函数中调用,如下所示:
import { createOptimizedPicture } from '../../../../scripts/aem.js';
import { subscribe } from '../../rules/index.js';
function createCard(element, enums) {
element.querySelectorAll('.radio-wrapper').forEach((radioWrapper, index) => {
if (enums[index]?.name) {
let label = radioWrapper.querySelector('label');
if (!label) {
label = document.createElement('label');
radioWrapper.appendChild(label);
}
label.textContent = enums[index]?.name;
}
const image = createOptimizedPicture(
enums[index]?.image || 'https://main--afb--jalagari.hlx.page/lab/images/card.png',
'card-image'
);
radioWrapper.appendChild(image);
});
}
export default function decorate(element, fieldJson, container, formId) {
element.classList.add('card');
createCard(element, fieldJson.enum);
subscribe(element, formId, (fieldDiv, fieldModel) => {
fieldModel.subscribe((e) => {
const { payload } = e;
payload?.changes?.forEach((change) => {
if (change?.propertyName === 'enum') {
createCard(element, change.currentValue);
}
});
});
});
return element;
}
在 cards.js 中使用订阅函数监听事件变化
现在,当您更改下拉列表时,卡片会自动填充,如下所示:
2.3 将视图更新与字段模型同步
为了将视图中的更改同步到字段模型,您需要设置所选卡片的值。因此,请在 cards.js 中添加如下所示的更改事件监听器:
在 cards.js 中使用字段模型 API
import { createOptimizedPicture } from '../../../../scripts/aem.js';
import { subscribe } from '../../rules/index.js';
function createCard(element, enums) {
element.querySelectorAll('.radio-wrapper').forEach((radioWrapper, index) => {
if (enums[index]?.name) {
let label = radioWrapper.querySelector('label');
if (!label) {
label = document.createElement('label');
radioWrapper.appendChild(label);
}
label.textContent = enums[index]?.name;
}
// Attach index to input element for later reference
radioWrapper.querySelector('input').dataset.index = index;
const image = createOptimizedPicture(
enums[index]?.image || 'https://main--afb--jalagari.hlx.page/lab/images/card.png',
'card-image'
);
radioWrapper.appendChild(image);
});
}
export default function decorate(element, fieldJson, container, formId) {
element.classList.add('card');
createCard(element, fieldJson.enum);
subscribe(element, formId, (fieldDiv, fieldModel) => {
fieldModel.subscribe((e) => {
const { payload } = e;
payload?.changes?.forEach((change) => {
if (change?.propertyName === 'enum') {
createCard(element, change.currentValue);
}
});
});
element.addEventListener('change', (e) => {
e.stopPropagation();
const value = fieldModel.enum?.[parseInt(e.target.dataset.index, 10)];
fieldModel.value = value.name;
});
});
return element;
}
现在,自定义卡片组件会呈现如下效果:
3. 提交并推送更改
在为自定义组件实现了 JavaScript 和 CSS 并完成本地验证后,将更改提交并推送到 Git 存储库。
git add . && git commit -m "Add card custom component" && git push
这样,您就通过几个简单步骤成功创建了一个复杂的自定义卡片选择组件。
旧版的方法是手动执行以下步骤:
-
选择一个 OOTB 组件进行扩展(如按钮、下拉框、文本输入框等)。在此示例中,扩展单选按钮组件。
-
在
blocks/form/components
中创建一个文件夹,并使用您的组件名称命名(此处为卡片)。 -
添加一个同名的 JS 文件:
blocks/form/components/cards/cards.js
。
-
(可选)添加一个 CSS 文件 用于自定义样式:
blocks/form/components/cards/cards.css.
-
在与 组件 JS 文件 相同的文件夹中 定义一个新的 JSON 文件(例如
_cards.json
,路径为blocks/form/components/cards/_cards.json
)。该 JSON 应继承现有组件,并在定义中将fd:viewType
设置为您的组件名称(此处为卡片):- 针对所有字段组(基础、验证、帮助等),显式添加您的自定义字段。
-
实施 JS 和 CSS 逻辑:
-
导出一个默认函数,如上所述。
-
使用 element 参数修改基础 HTML 结构。
-
如需标准字段数据,可使用 fieldJson 参数。
-
如需监听字段变化或自定义事件,可使用 订阅 函数。
note note NOTE 按照上述说明为自定义组件实现 JS 和 CSS 逻辑。
-
-
在表单生成器中将您的组件注册为一个变体,并在 JSON 中设置变体属性或
fd:viewType/:type
为您的组件名称。例如,将definitions[]
中的fd:viewType
值(卡片)添加到id="form
对象的组件数组中。code language-none { "definitions": [ { "title": "Cards", "id": "cards", "plugins": { "xwalk": { "page": { "resourceType": "core/fd/components/form/radiobutton/v1/radiobutton", "template": { "jcr:title": "Cards", "fieldType": "radio-button", "fd:viewType": "cards", "enabled": true, "visible": true } } } } } ] }
-
更新 mappings.js:将您的组件名称添加到 OOTBComponentDecorators(用于 OOTB 风格组件)或 customComponents 列表中,以便系统识别并加载该组件。
code language-javascript let customComponents = ["cards"]; const OOTBComponentDecorators = [];
-
更新 _form.json:将您的组件名称添加到
filters.components
数组中,以便在创作 UI 中使用。code language-javascript "filters": [ { "id": "form", "components": [ "cards"]} ]
-
更新 _component-definition.json:在
models/_component-definition.json
中,更新id custom-components
组内的数组,新增一个对象,如下所示:code language-javascript { "...":"../blocks/form/components/cards/_cards.json#/definitions" }
这样可为新建的卡片组件提供引用,以便与其他组件一起构建。
-
运行 build:json 脚本:执行
npm run build:json
,将所有组件 JSON 定义编译并合并为一个文件,以便服务器提供服务。这将确保新组件的架构包含在合并的输出中。 -
将更改提交并推送到 Git 存储库。
现在,您可以在表单中添加该自定义组件。
创建复合组件
复合组件是通过组合多个组件创建的。
例如,一个“条款与条件”复合组件包含一个父面板,其中包括:
-
一个用于显示条款的纯文本字段
-
一个用于获取用户同意的复选框
这种组合结构在相应组件的 JSON 文件中定义为一个模板。以下示例展示了如何为“条款与条件”组件定义一个模板:
{
"definitions": [
{
"title": "Terms and conditions",
"id": "tnc",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/fd/components/form/termsandconditions/v1/termsandconditions",
"template": {
"jcr:title": "Terms and conditions",
"fieldType": "panel",
"fd:viewType": "tnc",
"text": {
"value": "Text related to the terms and conditions come here.",
"sling:resourceType": "core/fd/components/form/text/v1/text",
"fieldType": "plain-text",
"textIsRich": true
},
"approvalcheckbox": {
"name": "approvalcheckbox",
"jcr:title": "I agree to the terms & conditions.",
"sling:resourceType": "core/fd/components/form/checkbox/v1/checkbox",
"fieldType": "checkbox",
"required": true,
"type": "string",
"enum": [
"true"
]
}
}
}
}
}
}
],
...
}
最佳做法
在创建自定义组件前,请注意以下几点:
-
保持组件逻辑聚焦: 仅添加或覆盖实现自定义行为所必需的内容
-
充分利用基础结构:以 OOTB HTML 为起点
-
使用可编辑属性: 通过 JSON 架构提供可配置选项
-
为 CSS 添加命名空间:使用唯一的类名以避免样式冲突
引用
-
form-field-types:所有字段类型的基本HTML结构和属性。
-
blocks/form/models/form-components:OOTB 和自定义组件的属性定义。
-
blocks/form/components:用于放置自定义组件的目录。例如:
blocks/form/components/countdown-timer/_countdown-timer.json
展示了如何扩展基础组件并添加新属性。