--- name: b2c-forms description: Create forms with validation in SFRA patterns for B2C Commerce. Use when building checkout forms, account forms, or any form with field definitions, validation rules, and error handling. Covers form XML definitions, server-side validation, and template rendering. --- # Forms Skill This skill guides you through creating forms with validation in Salesforce B2C Commerce using the SFRA patterns. ## 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 ## File Location Forms are defined in the cartridge's `forms` directory: ``` /my-cartridge /cartridge /forms /default # Default locale profile.xml contact.xml address.xml /de_DE # German-specific (optional) address.xml ``` ## Form Definition (XML) ### Basic Structure ```xml
``` ### Field Types | Type | Description | HTML Input | |------|-------------|------------| | `string` | Text input | `` | | `integer` | Whole number | `` | | `number` | Decimal number | `` | | `boolean` | Checkbox | `` | | `date` | Date value | `` | ### Field Attributes | Attribute | Purpose | Example | |-----------|---------|---------| | `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}$"` | | `default` | Default value | `default="US"` | | `min` | Min numeric value | `min="0"` | | `max` | Max numeric value | `max="100"` | | `format` | Date format | `format="yyyy-MM-dd"` | ### Validation Error Messages ```xml ``` | Attribute | When 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 | ### Grouped Fields ```xml ``` Access in controller: `form.address.street.value` ### Actions ```xml ``` ## Controller Logic (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(); ``` ### Processing Form Submission ```javascript server.post('Submit', server.middleware.https, csrfProtection.validateAjaxRequest, function (req, res, next) { var form = server.forms.getForm('profile'); // Check validation 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(); } ); // Helper to extract form errors 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(); // Prepopulate from existing data 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(); }); ``` ### Accessing Raw Form Data ```javascript server.post('Submit', function (req, res, next) { // Access raw POST data directly var email = req.form.email; var firstName = req.form.firstName; // Useful when not using form definitions next(); }); ``` ## Template (ISML) ### Basic Form Template ```html
required maxlength="${pdict.profileForm.email.maxLength || 50}"/>
${pdict.profileForm.email.error}
``` ### Form with Groups ```html
${Resource.msg('form.address.title', 'forms', null)}
``` ### AJAX Form Submission ```html ``` ## 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 form.password.required=Password is required button.submit=Submit button.cancel=Cancel ``` **forms_de_DE.properties:** ```properties form.email.label=E-Mail-Adresse form.email.required=E-Mail ist erforderlich ``` ## Custom Validation ### In Form Definition ```xml ``` ### Validation Script ```javascript // scripts/validation.js exports.validatePassword = function (formfield) { var value = formfield.value; if (value && value.length < 8) { return false; // Triggers range-error } if (!/[A-Z]/.test(value) || !/[0-9]/.test(value)) { return false; } return true; }; ``` ## 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 ## Detailed Reference For comprehensive form patterns: - [Form XML Reference](references/FORM-XML.md) - Complete XML schema and validation patterns