b2c-forms

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Forms Skill

表单开发技能

This skill guides you through creating forms with validation in Salesforce B2C Commerce using the SFRA patterns.
本技能将指导你使用SFRA模式在Salesforce B2C Commerce中创建带验证功能的表单。

Overview

概述

B2C Commerce forms consist of three parts:
  1. Form Definition - XML file defining fields, validation, and actions
  2. Controller Logic - Server-side form handling and processing
  3. Template - ISML template rendering the HTML form
B2C Commerce表单由三部分组成:
  1. 表单定义 - 定义字段、验证规则和操作的XML文件
  2. 控制器逻辑 - 服务器端表单处理与业务逻辑
  3. 模板 - 渲染HTML表单的ISML模板

File Location

文件位置

Forms are defined in the cartridge's
forms
directory:
/my-cartridge
    /cartridge
        /forms
            /default              # Default locale
                profile.xml
                contact.xml
            /de_DE               # German-specific (optional)
                address.xml
表单定义在 cartridge 的
forms
目录下:
/my-cartridge
    /cartridge
        /forms
            /default              # 默认语言环境
                profile.xml
                contact.xml
            /de_DE               # 德语专属(可选)
                address.xml

Form Definition (XML)

表单定义(XML)

Basic Structure

基本结构

xml
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.demandware.com/xml/form/2008-04-19">
    <field formid="email" label="form.email.label" type="string"
           mandatory="true" max-length="50"
           regexp="^[\w.%+-]+@[\w.-]+\.\w{2,6}$"
           parse-error="form.email.invalid"/>

    <field formid="password" label="form.password.label" type="string"
           mandatory="true" min-length="8" max-length="255"
           missing-error="form.password.required"/>

    <field formid="rememberMe" label="form.remember.label" type="boolean"/>

    <action formid="submit" valid-form="true"/>
    <action formid="cancel" valid-form="false"/>
</form>
xml
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.demandware.com/xml/form/2008-04-19">
    <field formid="email" label="form.email.label" type="string"
           mandatory="true" max-length="50"
           regexp="^[\w.%+-]+@[\w.-]+\.\w{2,6}$"
           parse-error="form.email.invalid"/>

    <field formid="password" label="form.password.label" type="string"
           mandatory="true" min-length="8" max-length="255"
           missing-error="form.password.required"/>

    <field formid="rememberMe" label="form.remember.label" type="boolean"/>

    <action formid="submit" valid-form="true"/>
    <action formid="cancel" valid-form="false"/>
</form>

Field Types

字段类型

TypeDescriptionHTML Input
string
Text input
<input type="text">
integer
Whole number
<input type="number">
number
Decimal number
<input type="number">
boolean
Checkbox
<input type="checkbox">
date
Date value
<input type="date">
类型描述HTML输入控件
string
文本输入
<input type="text">
integer
整数
<input type="number">
number
小数
<input type="number">
boolean
复选框
<input type="checkbox">
date
日期值
<input type="date">

Key Field Attributes

关键字段属性

AttributePurposeExample
formid
Field identifier (required)
formid="email"
label
Resource key for label
label="form.email.label"
type
Data type (required)
type="string"
mandatory
Required field
mandatory="true"
max-length
Max string length
max-length="100"
min-length
Min string length
min-length="8"
regexp
Validation pattern
regexp="^\d{5}$"
属性用途示例
formid
字段标识符(必填)
formid="email"
label
标签对应的资源键
label="form.email.label"
type
数据类型(必填)
type="string"
mandatory
是否为必填字段
mandatory="true"
max-length
字符串最大长度
max-length="100"
min-length
字符串最小长度
min-length="8"
regexp
验证正则表达式
regexp="^\d{5}$"

Validation Error Messages

验证错误消息

AttributeWhen Triggered
missing-error
Mandatory field is empty
parse-error
Value doesn't match regexp or type
range-error
Value outside min/max range
value-error
General validation failure
See Form XML Reference for complete field attributes, groups, lists, and validation patterns.
属性触发场景
missing-error
必填字段为空时
parse-error
值不符合正则表达式或数据类型时
range-error
值超出最小/最大范围时
value-error
通用验证失败时
完整的字段属性、分组、列表及验证规则请参考 Form XML Reference

Controller Logic (SFRA)

控制器逻辑(SFRA)

Rendering a Form

渲染表单

javascript
'use strict';

var server = require('server');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');

server.get('Show',
    csrfProtection.generateToken,
    function (req, res, next) {
        var form = server.forms.getForm('profile');
        form.clear();  // Reset previous values

        res.render('account/profile', {
            profileForm: form
        });
        next();
    }
);

module.exports = server.exports();
javascript
'use strict';

var server = require('server');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');

server.get('Show',
    csrfProtection.generateToken,
    function (req, res, next) {
        var form = server.forms.getForm('profile');
        form.clear();  // 重置之前的值

        res.render('account/profile', {
            profileForm: form
        });
        next();
    }
);

module.exports = server.exports();

Processing Form Submission

处理表单提交

javascript
server.post('Submit',
    server.middleware.https,
    csrfProtection.validateAjaxRequest,
    function (req, res, next) {
        var form = server.forms.getForm('profile');

        if (!form.valid) {
            res.json({
                success: false,
                fields: getFormErrors(form)
            });
            return next();
        }

        // Access form values
        var email = form.email.value;
        var firstName = form.firstName.value;

        // Process and save data
        this.on('route:BeforeComplete', function () {
            var Transaction = require('dw/system/Transaction');
            Transaction.wrap(function () {
                customer.profile.email = email;
                customer.profile.firstName = firstName;
            });
        });

        res.json({ success: true });
        next();
    }
);

function getFormErrors(form) {
    var errors = {};
    Object.keys(form).forEach(function (key) {
        if (form[key] && form[key].error) {
            errors[key] = form[key].error;
        }
    });
    return errors;
}
javascript
server.post('Submit',
    server.middleware.https,
    csrfProtection.validateAjaxRequest,
    function (req, res, next) {
        var form = server.forms.getForm('profile');

        if (!form.valid) {
            res.json({
                success: false,
                fields: getFormErrors(form)
            });
            return next();
        }

        // 访问表单值
        var email = form.email.value;
        var firstName = form.firstName.value;

        // 处理并保存数据
        this.on('route:BeforeComplete', function () {
            var Transaction = require('dw/system/Transaction');
            Transaction.wrap(function () {
                customer.profile.email = email;
                customer.profile.firstName = firstName;
            });
        });

        res.json({ success: true });
        next();
    }
);

function getFormErrors(form) {
    var errors = {};
    Object.keys(form).forEach(function (key) {
        if (form[key] && form[key].error) {
            errors[key] = form[key].error;
        }
    });
    return errors;
}

Prepopulating Forms

预填充表单

javascript
server.get('Edit', function (req, res, next) {
    var form = server.forms.getForm('profile');
    form.clear();

    var profile = req.currentCustomer.profile;
    form.firstName.value = profile.firstName;
    form.lastName.value = profile.lastName;
    form.email.value = profile.email;

    res.render('account/editProfile', { profileForm: form });
    next();
});
javascript
server.get('Edit', function (req, res, next) {
    var form = server.forms.getForm('profile');
    form.clear();

    var profile = req.currentCustomer.profile;
    form.firstName.value = profile.firstName;
    form.lastName.value = profile.lastName;
    form.email.value = profile.email;

    res.render('account/editProfile', { profileForm: form });
    next();
});

Template (ISML)

模板(ISML)

Basic Form Template

基础表单模板

html
<form action="${pdict.actionUrl}" method="POST" name="profile-form"
      class="form-horizontal" data-action="${URLUtils.url('Profile-Submit')}">

    <!-- CSRF Token -->
    <input type="hidden" name="${pdict.csrf.tokenName}" value="${pdict.csrf.token}"/>

    <div class="form-group ${pdict.profileForm.email.mandatory ? 'required' : ''}">
        <label for="email" class="form-control-label">
            ${Resource.msg('form.email.label', 'forms', null)}
        </label>
        <input type="email"
               id="email"
               name="email"
               class="form-control ${pdict.profileForm.email.error ? 'is-invalid' : ''}"
               value="${pdict.profileForm.email.value || ''}"
               <isif condition="${pdict.profileForm.email.mandatory}">required</isif>
               maxlength="${pdict.profileForm.email.maxLength || 50}"/>
        <isif condition="${pdict.profileForm.email.error}">
            <div class="invalid-feedback">${pdict.profileForm.email.error}</div>
        </isif>
    </div>

    <button type="submit" class="btn btn-primary">
        ${Resource.msg('button.submit', 'forms', null)}
    </button>
</form>
html
<form action="${pdict.actionUrl}" method="POST" name="profile-form"
      class="form-horizontal" data-action="${URLUtils.url('Profile-Submit')}">

    <!-- CSRF Token -->
    <input type="hidden" name="${pdict.csrf.tokenName}" value="${pdict.csrf.token}"/>

    <div class="form-group ${pdict.profileForm.email.mandatory ? 'required' : ''}">
        <label for="email" class="form-control-label">
            ${Resource.msg('form.email.label', 'forms', null)}
        </label>
        <input type="email"
               id="email"
               name="email"
               class="form-control ${pdict.profileForm.email.error ? 'is-invalid' : ''}"
               value="${pdict.profileForm.email.value || ''}"
               <isif condition="${pdict.profileForm.email.mandatory}">required</isif>
               maxlength="${pdict.profileForm.email.maxLength || 50}"/>
        <isif condition="${pdict.profileForm.email.error}">
            <div class="invalid-feedback">${pdict.profileForm.email.error}</div>
        </isif>
    </div>

    <button type="submit" class="btn btn-primary">
        ${Resource.msg('button.submit', 'forms', null)}
    </button>
</form>

Localization

本地化

Form labels and errors use resource bundles:
forms.properties:
properties
form.email.label=Email Address
form.email.required=Email is required
form.email.invalid=Please enter a valid email address
form.password.label=Password
button.submit=Submit
forms_de_DE.properties:
properties
form.email.label=E-Mail-Adresse
form.email.required=E-Mail ist erforderlich
表单标签和错误消息使用资源包:
forms.properties:
properties
form.email.label=Email Address
form.email.required=Email is required
form.email.invalid=Please enter a valid email address
form.password.label=Password
button.submit=Submit
forms_de_DE.properties:
properties
form.email.label=E-Mail-Adresse
form.email.required=E-Mail ist erforderlich

Best Practices

最佳实践

  1. Always use CSRF protection for form submissions
  2. Clear forms before displaying to reset state
  3. Use resource keys for labels and errors (localization)
  4. Validate server-side even with client-side validation
  5. Use
    route:BeforeComplete
    for database operations
  6. Return JSON for AJAX form submissions
  1. 始终为表单提交启用CSRF保护
  2. 显示表单前先清空以重置状态
  3. 使用资源键来定义标签和错误消息(支持本地化)
  4. 即使有客户端验证,也要进行服务器端验证
  5. **使用
    route:BeforeComplete
    **执行数据库操作
  6. AJAX表单提交返回JSON格式数据

Detailed Reference

详细参考

For comprehensive form patterns:
  • Form XML Reference - Complete XML schema, validation patterns, and examples
如需了解完整的表单模式:
  • Form XML Reference - 完整的XML schema、验证规则及示例