b2c-page-designer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePage Designer Skill
Page Designer 技能
This skill guides you through creating custom Page Designer page types and component types for Salesforce B2C Commerce.
本技能将指导你为Salesforce B2C Commerce创建自定义的Page Designer页面类型和组件类型。
Overview
概述
Page Designer allows merchants to create and manage content pages through a visual editor. Developers create:
- Page Types - Define page structures with regions
- Component Types - Reusable content blocks with configurable attributes
Page Designer允许商家通过可视化编辑器创建和管理内容页面,开发者需要创建两类资源:
- 页面类型 - 定义带有区域的页面结构
- 组件类型 - 带有可配置属性的可复用内容块
File Structure
文件结构
Page Designer files are in the cartridge's directory:
experience/my-cartridge
/cartridge
/experience
/pages
homepage.json # Page type meta definition
homepage.js # Page type script
/components
banner.json # Component type meta definition
banner.js # Component type script
/templates
/default
/experience
/pages
homepage.isml # Page template
/components
banner.isml # Component templateNaming: The and files must have matching names. Use only alphanumeric or underscore in file names and in any subdirectory names under or .
.json.jsexperience/pagesexperience/componentsComponent types in subfolders: You can put component meta and script in a subdirectory (e.g. ). The component type ID is then the path with dots: . The template path under must mirror that path (e.g. ), and the script must call so the path matches. Stored ID is ; total length must not exceed 256 characters.
experience/components/assets/assets.hero_image_blocktemplates/default/templates/default/experience/components/assets/hero_image_block.ismlTemplate('experience/components/assets/hero_image_block')component.{component_type_id}Page Designer相关文件存放在cartridge的目录下:
experience/my-cartridge
/cartridge
/experience
/pages
homepage.json # 页面类型元定义
homepage.js # 页面类型脚本
/components
banner.json # 组件类型元定义
banner.js # 组件类型脚本
/templates
/default
/experience
/pages
homepage.isml # 页面模板
/components
banner.isml # 组件模板命名规范:和文件必须同名,或下的文件名和子目录名仅允许使用字母数字或下划线。
.json.jsexperience/pagesexperience/components子文件夹中的组件类型:你可以将组件元定义和脚本放在子目录中(例如),此时组件类型ID为带点的路径格式:。下的模板路径必须与该路径对应(例如),且脚本中必须调用保证路径匹配。存储的ID格式为,总长度不可超过256个字符。
experience/components/assets/assets.hero_image_blocktemplates/default/templates/default/experience/components/assets/hero_image_block.ismlTemplate('experience/components/assets/hero_image_block')component.{component_type_id}Page Types
页面类型
Meta Definition (pages/homepage.json)
元定义 (pages/homepage.json)
json
{
"name": "Home Page",
"description": "Landing page with hero and content regions",
"region_definitions": [
{
"id": "hero",
"name": "Hero Section",
"max_components": 1
},
{
"id": "content",
"name": "Main Content"
},
{
"id": "footer",
"name": "Footer Section",
"component_type_exclusions": [
{ "type_id": "video" }
]
}
]
}json
{
"name": "Home Page",
"description": "Landing page with hero and content regions",
"region_definitions": [
{
"id": "hero",
"name": "Hero Section",
"max_components": 1
},
{
"id": "content",
"name": "Main Content"
},
{
"id": "footer",
"name": "Footer Section",
"component_type_exclusions": [
{ "type_id": "video" }
]
}
]
}Page Script (pages/homepage.js)
页面脚本 (pages/homepage.js)
javascript
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
module.exports.render = function (context) {
var model = new HashMap();
var page = context.page;
model.put('page', page);
return new Template('experience/pages/homepage').render(model).text;
};javascript
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
module.exports.render = function (context) {
var model = new HashMap();
var page = context.page;
model.put('page', page);
return new Template('experience/pages/homepage').render(model).text;
};Page Template (templates/experience/pages/homepage.isml)
页面模板 (templates/experience/pages/homepage.isml)
html
<isdecorate template="common/layout/page">
<isscript>
var PageRenderHelper = require('*/cartridge/experience/utilities/PageRenderHelper');
</isscript>
<div class="homepage">
<div class="hero-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('hero'))}" encoding="off"/>
</div>
<div class="content-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('content'))}" encoding="off"/>
</div>
<div class="footer-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('footer'))}" encoding="off"/>
</div>
</div>
</isdecorate>html
<isdecorate template="common/layout/page">
<isscript>
var PageRenderHelper = require('*/cartridge/experience/utilities/PageRenderHelper');
</isscript>
<div class="homepage">
<div class="hero-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('hero'))}" encoding="off"/>
</div>
<div class="content-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('content'))}" encoding="off"/>
</div>
<div class="footer-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('footer'))}" encoding="off"/>
</div>
</div>
</isdecorate>Component Types
组件类型
Meta Definition (components/banner.json)
元定义 (components/banner.json)
json
{
"name": "Banner",
"description": "Promotional banner with image and CTA",
"group": "content",
"region_definitions": [],
"attribute_definition_groups": [
{
"id": "image",
"name": "Image Settings",
"attribute_definitions": [
{
"id": "image",
"name": "Banner Image",
"type": "image",
"required": true
},
{
"id": "alt",
"name": "Alt Text",
"type": "string",
"required": true
}
]
},
{
"id": "content",
"name": "Content",
"attribute_definitions": [
{
"id": "headline",
"name": "Headline",
"type": "string",
"required": true
},
{
"id": "body",
"name": "Body Text",
"type": "markup"
},
{
"id": "ctaUrl",
"name": "CTA Link",
"type": "url"
},
{
"id": "ctaText",
"name": "CTA Button Text",
"type": "string"
}
]
},
{
"id": "layout",
"name": "Layout Options",
"attribute_definitions": [
{
"id": "alignment",
"name": "Text Alignment",
"type": "enum",
"values": ["left", "center", "right"],
"default_value": "center"
},
{
"id": "fullWidth",
"name": "Full Width",
"type": "boolean",
"default_value": false
}
]
}
]
}Component meta: Always include . Use when the component has no nested regions (no slots for other components).
region_definitions[]json
{
"name": "Banner",
"description": "Promotional banner with image and CTA",
"group": "content",
"region_definitions": [],
"attribute_definition_groups": [
{
"id": "image",
"name": "Image Settings",
"attribute_definitions": [
{
"id": "image",
"name": "Banner Image",
"type": "image",
"required": true
},
{
"id": "alt",
"name": "Alt Text",
"type": "string",
"required": true
}
]
},
{
"id": "content",
"name": "Content",
"attribute_definitions": [
{
"id": "headline",
"name": "Headline",
"type": "string",
"required": true
},
{
"id": "body",
"name": "Body Text",
"type": "markup"
},
{
"id": "ctaUrl",
"name": "CTA Link",
"type": "url"
},
{
"id": "ctaText",
"name": "CTA Button Text",
"type": "string"
}
]
},
{
"id": "layout",
"name": "Layout Options",
"attribute_definitions": [
{
"id": "alignment",
"name": "Text Alignment",
"type": "enum",
"values": ["left", "center", "right"],
"default_value": "center"
},
{
"id": "fullWidth",
"name": "Full Width",
"type": "boolean",
"default_value": false
}
]
}
]
}组件元定义要求:必须包含字段,如果组件没有嵌套区域(不支持嵌入其他组件的槽位),则赋值为。
region_definitions[]Component Script (components/banner.js)
组件脚本 (components/banner.js)
javascript
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
var URLUtils = require('dw/web/URLUtils');
module.exports.render = function (context) {
var model = new HashMap();
var content = context.content;
// Access merchant-configured attributes
model.put('image', content.image); // Image object
model.put('alt', content.alt); // String
model.put('headline', content.headline); // String
model.put('body', content.body); // Markup string
model.put('ctaUrl', content.ctaUrl); // URL object
model.put('ctaText', content.ctaText); // String
model.put('alignment', content.alignment || 'center');
model.put('fullWidth', content.fullWidth);
return new Template('experience/components/banner').render(model).text;
};Template path: The path passed to must match the template path under . If the component lives in a subfolder (e.g. ), use and place the ISML at .
Template(...)templates/default/experience/components/assets/hero_image_blockTemplate('experience/components/assets/hero_image_block')templates/default/experience/components/assets/hero_image_block.ismlHandling colors (string or color picker object): If an attribute can be a hex string or a color picker object , use a small helper so the script works with both:
{ color: "#hex" }javascript
function getColor(colorAttr) {
if (!colorAttr) return '';
if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
if (colorAttr.color) return colorAttr.color;
return '';
}javascript
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
var URLUtils = require('dw/web/URLUtils');
module.exports.render = function (context) {
var model = new HashMap();
var content = context.content;
// 访问商家配置的属性
model.put('image', content.image); // 图片对象
model.put('alt', content.alt); // 字符串
model.put('headline', content.headline); // 字符串
model.put('body', content.body); // 富文本字符串
model.put('ctaUrl', content.ctaUrl); // URL对象
model.put('ctaText', content.ctaText); // 字符串
model.put('alignment', content.alignment || 'center');
model.put('fullWidth', content.fullWidth);
return new Template('experience/components/banner').render(model).text;
};模板路径要求:传入的路径必须与下的模板路径完全匹配。如果组件存放在子文件夹中(例如),则需要使用,并将ISML文件放在路径下。
Template(...)templates/default/experience/components/assets/hero_image_blockTemplate('experience/components/assets/hero_image_block')templates/default/experience/components/assets/hero_image_block.isml颜色属性处理(字符串或颜色选择器对象):如果属性可能是十六进制字符串,或是颜色选择器返回的对象,可以使用以下工具函数兼容两种格式:
{ color: "#hex" }javascript
function getColor(colorAttr) {
if (!colorAttr) return '';
if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
if (colorAttr.color) return colorAttr.color;
return '';
}Component Template (templates/experience/components/banner.isml)
组件模板 (templates/experience/components/banner.isml)
html
<div class="banner ${pdict.fullWidth ? 'banner--full-width' : ''}"
style="text-align: ${pdict.alignment}">
<isif condition="${pdict.image}">
<img src="${pdict.image.file.absURL}"
alt="${pdict.alt}"
class="banner__image"/>
</isif>
<div class="banner__content">
<h2 class="banner__headline">${pdict.headline}</h2>
<isif condition="${pdict.body}">
<div class="banner__body">
<isprint value="${pdict.body}" encoding="off"/>
</div>
</isif>
<isif condition="${pdict.ctaUrl && pdict.ctaText}">
<a href="${pdict.ctaUrl}" class="banner__cta btn btn-primary">
${pdict.ctaText}
</a>
</isif>
</div>
</div>html
<div class="banner ${pdict.fullWidth ? 'banner--full-width' : ''}"
style="text-align: ${pdict.alignment}">
<isif condition="${pdict.image}">
<img src="${pdict.image.file.absURL}"
alt="${pdict.alt}"
class="banner__image"/>
</isif>
<div class="banner__content">
<h2 class="banner__headline">${pdict.headline}</h2>
<isif condition="${pdict.body}">
<div class="banner__body">
<isprint value="${pdict.body}" encoding="off"/>
</div>
</isif>
<isif condition="${pdict.ctaUrl && pdict.ctaText}">
<a href="${pdict.ctaUrl}" class="banner__cta btn btn-primary">
${pdict.ctaText}
</a>
</isif>
</div>
</div>Attribute Types
属性类型
| Type | Description | Returns |
|---|---|---|
| Text input | String |
| Multi-line text | String |
| Rich text editor | Markup string (use |
| Checkbox | Boolean |
| Number input | Integer |
| Single select dropdown | String |
| Image picker | Image object with |
| File picker | File object |
| URL picker | URL string |
| Category selector | Category object |
| Product selector | Product object |
| Page selector | Page object |
| JSON object or custom editor | Object (or editor-specific) |
Enum — critical for component visibility: Use a string array for : . Do not use objects like ; that format can cause the component type to be rejected and not appear in the Page Designer component list.
values"values": ["left", "center", "right"]{ "value": "x", "display_value": "X" }Custom and colors: with e.g. requires a cartridge that provides that editor on the Business Manager site cartridge path. If the component does not show up in the editor, use for color attributes (merchant types a hex). In the script, support both: accept a string or an object like (e.g. a small helper that returns the string).
type: "custom"editor_definition.type: "styling.colorPicker"type: "string"{ color: "#hex" }getColor(attr)default_value: Used for storefront rendering only; it is not shown as preselected in the Page Designer visual editor.
| 类型 | 描述 | 返回值 |
|---|---|---|
| 单行文本输入框 | 字符串 |
| 多行文本输入框 | 字符串 |
| 富文本编辑器 | 富文本字符串(渲染时需使用 |
| 复选框 | 布尔值 |
| 数字输入框 | 整数 |
| 单选下拉框 | 字符串 |
| 图片选择器 | 带有 |
| 文件选择器 | 文件对象 |
| URL选择器 | URL字符串 |
| 分类选择器 | 分类对象 |
| 商品选择器 | 商品对象 |
| 页面选择器 | 页面对象 |
| JSON对象或自定义编辑器 | 对象(或编辑器自定义格式) |
枚举类型注意事项(对组件可见性至关重要):必须使用字符串数组格式:,不要使用这种对象格式,否则会导致组件类型被拒绝,不会出现在Page Designer的组件列表中。
values"values": ["left", "center", "right"]{ "value": "x", "display_value": "X" }自定义属性与颜色属性:搭配这类配置时,需要Business Manager站点的cartridge路径中包含提供该编辑器的cartridge。如果组件没有出现在编辑器中,可以先将颜色属性的类型改为(让商家手动输入十六进制颜色值)测试。在脚本中建议兼容两种格式:同时支持字符串和对象(比如使用上面的工具函数返回颜色字符串)。
type: "custom"editor_definition.type: "styling.colorPicker"type: "string"{ color: "#hex" }getColor(attr)default_value说明:仅用于前端店铺渲染,不会在Page Designer可视化编辑器中作为默认值预填。
Region Definitions
区域定义
json
{
"region_definitions": [
{
"id": "main",
"name": "Main Content",
"max_components": 10,
"component_type_exclusions": [
{ "type_id": "heavy-component" }
],
"component_type_inclusions": [
{ "type_id": "text-block" },
{ "type_id": "image-block" }
]
}
]
}| Property | Description |
|---|---|
| Unique region identifier |
| Display name in editor |
| Max number of components (optional) |
| Components NOT allowed |
| Only these components allowed |
json
{
"region_definitions": [
{
"id": "main",
"name": "Main Content",
"max_components": 10,
"component_type_exclusions": [
{ "type_id": "heavy-component" }
],
"component_type_inclusions": [
{ "type_id": "text-block" },
{ "type_id": "image-block" }
]
}
]
}| 属性 | 描述 |
|---|---|
| 区域唯一标识符 |
| 编辑器中显示的区域名称 |
| 区域可容纳的最大组件数量(可选) |
| 禁止在该区域使用的组件 |
| 仅允许在该区域使用的组件 |
Rendering Pages
页面渲染
In Controllers
在控制器中渲染
javascript
var PageMgr = require('dw/experience/PageMgr');
server.get('Show', function (req, res, next) {
var page = PageMgr.getPage(req.querystring.cid);
if (page && page.isVisible()) {
res.page(page.ID);
} else {
res.setStatusCode(404);
res.render('error/notfound');
}
next();
});javascript
var PageMgr = require('dw/experience/PageMgr');
server.get('Show', function (req, res, next) {
var page = PageMgr.getPage(req.querystring.cid);
if (page && page.isVisible()) {
res.page(page.ID);
} else {
res.setStatusCode(404);
res.render('error/notfound');
}
next();
});In ISML
在ISML中渲染
html
<isscript>
var PageMgr = require('dw/experience/PageMgr');
var page = PageMgr.getPage('homepage-id');
</isscript>
<isif condition="${page && page.isVisible()}">
<isprint value="${PageMgr.renderPage(page.ID)}" encoding="off"/>
</isif>html
<isscript>
var PageMgr = require('dw/experience/PageMgr');
var page = PageMgr.getPage('homepage-id');
</isscript>
<isif condition="${page && page.isVisible()}">
<isprint value="${PageMgr.renderPage(page.ID)}" encoding="off"/>
</isif>Component Groups
组件分组
Organize components in the editor sidebar:
json
{
"name": "Product Card",
"group": "products"
}Common groups: , , , ,
contentproductsnavigationlayoutmedia可以通过分组对编辑器侧边栏的组件进行分类整理:
json
{
"name": "Product Card",
"group": "products"
}常用分组:(内容)、(商品)、(导航)、(布局)、(媒体)
contentproductsnavigationlayoutmediaIf the component does not appear in Page Designer
组件未在Page Designer中显示的排查方案
- Enums: Ensure all attributes use
enum(string array), not objects with"values": ["a", "b"]/value.display_value - Custom editors: Replace (e.g. color picker) with
type: "custom"for the problematic attributes and redeploy; if the component then appears, the issue is likely the custom editor or BM cartridge path.type: "string" - Naming: File and subdirectory names only alphanumeric or underscore.
- region_definitions: Component meta must include (use
region_definitionsif no nested regions).[] - Template path: Script path must match the template path under
Template('...')(including subfolders liketemplates/default/).experience/components/assets/... - Logs: In Business Manager, Administration > Site Development > Development Setup, check error logs when opening Page Designer for script or meta errors related to your component type ID.
- Code version: Deploy the cartridge to the correct code version; in non-production, meta can be cached for a few seconds—try a code version switch or short wait.
- 枚举类型配置:确认所有属性的
enum都是字符串数组格式,没有使用values/value的对象格式。display_value - 自定义编辑器问题:将有问题的属性从(比如颜色选择器)改为
type: "custom"后重新部署,如果组件正常显示,说明问题大概率出在自定义编辑器或Business Manager的cartridge路径配置上。type: "string" - 命名规范检查:文件和子目录名称仅允许使用字母数字或下划线。
- region_definitions字段检查:组件元定义必须包含字段,无嵌套区域时赋值为
region_definitions。[] - 模板路径检查:脚本中的路径必须与
Template('...')下的模板路径完全匹配(包括templates/default/这类子文件夹路径)。experience/components/assets/... - 日志排查:在Business Manager的Administration > Site Development > Development Setup页面,打开Page Designer时查看错误日志,排查与你的组件类型ID相关的脚本或元定义错误。
- 代码版本检查:确认cartridge已经部署到正确的代码版本;非生产环境中元数据可能有几秒缓存,可以尝试切换代码版本或稍等片刻再测试。
Best Practices
最佳实践
- Use for rendering (NOT
dw.util.Template)dw.template.ISML - Keep components self-contained - avoid cross-component dependencies
- Provide default values for optional attributes (they apply to rendering; not shown as preselected in the editor)
- Group related attributes in
attribute_definition_groups - Use meaningful IDs - they're used programmatically
- Don't change type IDs or attribute types after merchants create content (creates inconsistency with stored data); add a new component type and deprecate the old one if needed
- Prefer string arrays for enums and string for colors unless you control the BM cartridge path and custom editors
- 渲染时使用(不要使用
dw.util.Template)dw.template.ISML - 保持组件自治 - 避免组件之间的交叉依赖
- 为可选属性提供默认值(仅作用于渲染,不会在编辑器中预填)
- 使用对相关属性进行分组
attribute_definition_groups - 使用有语义的ID - 这些ID会被程序调用
- 商家创建内容后不要修改类型ID或属性类型(会导致已存储的数据不一致);如果需要修改,建议新增组件类型并废弃旧版本
- 优先使用字符串数组格式的枚举、字符串类型的颜色属性,除非你完全控制Business Manager的cartridge路径和自定义编辑器
Detailed Reference
详细参考
For comprehensive attribute documentation:
- Meta Definitions Reference - Full JSON schema
- Attribute Types Reference - All attribute types with examples
完整的属性文档请查看:
- 元定义参考 - 完整JSON schema
- 属性类型参考 - 所有属性类型及示例",