GrapesJS Trait API 完全指南:组件特性的定义、读写与自定义渲染
【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs
GrapesJS 中的Trait(特性)是组件"设置面板"的核心概念,用户通过它在编辑器中调整组件的属性(attribute)与属性(property),例如<input>的placeholder、type、required。本文以 docs/api/trait.md 的TraitAPI 为骨架,结合仓库源码 Trait.ts 与 Traits.md 模块文档,系统讲解 Trait 的完整属性定义、读写流程、运行时更新以及自定义 Trait 类型的实现原理,帮助你在二次开发中灵活掌控组件的"设置"层。
一、Trait 是什么
在 GrapesJS 中,Trait 定义了组件可被用户调整的参数与行为。用户视角下,Trait 通常呈现为组件选中后的"设置(Settings)"面板;开发视角下,Trait 的默认行为是绑定到组件的 DOM 属性(attribute),也可以绑定到组件模型属性(property)并响应其变化。从源码看,Trait是一个继承自Model的类,每个 Trait 关联一个目标组件(target)与编辑器实例(em),并在构造时完成id的兜底赋值与目标组件的绑定:
// packages/core/src/trait_manager/model/Trait.ts constructor(prop: TraitProperties, em: EditorModel) { super(prop); const { target, name } = this.attributes; !this.get('id') && this.set('id', name); // 未显式指定 id 时,用 name 兜底 if (target) { this.setTarget(target); } this.em = em; }同时setTarget会根据changeProp决定监听change:${name}(属性)还是change:attributes:${name}(DOM 属性)事件,实现组件变化到 Trait 的单向同步(见 Trait.ts)。
二、Trait 属性(Properties)
Trait的核心属性定义如下(类型声明见 types.ts):
| 属性 | 类型 | 说明 |
|---|---|---|
id | String | Trait 的唯一标识,如my-trait-id。不指定时默认取name |
type | String | Trait 类型,决定渲染方式。可选值:text(默认)、number、select、checkbox、color、button |
label | String|false | 渲染时显示的标签;设为false可隐藏标签列 |
name | String | Trait 的关键字,作为 attribute 或 property 的键。changeProp开启时作为属性名,否则作为 attribute 名 |
default | String | 组件上未定义值时的默认值 |
placeholder | String | 默认输入框中的占位提示(若 UI 类型支持) |
category | String|Category | Trait 分组,用于设置面板中的归类折叠 |
changeProp | Boolean | 为true时 Trait 值作用于组件 property,否则作用于 attributes |
除上表外,defaults()中还有unit(number 类型单位)、step(number 类型步长,默认 1)、value、options等默认项(见 Trait.ts)。
字符串简写与类型化定义
在组件定义中,Trait 可以写成字符串,由TraitFactory自动转换为text类型:
'name', // 等价于 { type: 'text', name: 'name' }从源码 TraitFactory.ts 可以看到build()的分流逻辑:字符串会走buildFromString,其中name为target时有特殊处理——自动转换为select类型并使用配置的optionsTarget选项:
private buildFromString(name: string, em: EditorModel): Trait { const obj: TraitProperties = { name, type: 'text' }; switch (name) { case 'target': obj.type = 'select'; obj.default = false; obj.options = this.config.optionsTarget as any; // 默认 [{ value: false }, { value: '_blank' }] break; } return new Trait(obj, em); }三、Trait 核心方法 API
以下方法对应 docs/api/trait.md 中的 API 清单,均可直接调用。
基础取值方法
| 方法 | 返回值 | 说明 |
|---|---|---|
getId() | String | 获取 Trait 的 id(未指定时为 name) |
getType() | String | 获取 Trait 类型 |
getName() | String | 获取 Trait 名称(作为 attribute/property 的键) |
getDefault() | any | 获取默认值 |
getOptions() | Array<TraitOption> | 获取选项数组(select 类型使用) |
props() | Object | 返回 Trait 的全部属性对象(见 Trait.ts) |
标签相关方法
getLabel(opts)— 获取 Trait 标签。opts.locale默认为true,此时优先使用 i18n 模块中的traitManager.traits.labels.${id}翻译,否则回退到label或name:
getLabel(opts: { locale?: boolean } = {}) { const { locale = true } = opts; const id = this.getId(); const name = this.get('label') || this.getName(); return (locale && this.em?.t(`traitManager.traits.labels.${id}`)) || name; }getCategoryLabel(opts)— 获取分类标签,locale 开启时查找traitManager.categories.${catId}(见 Trait.ts)。
值读写方法
getValue(opts)— 获取 Trait 值。默认从组件 attributes 取值,changeProp开启时从组件 property 取值;opts.useType为true时按类型归一化(如 checkbox 始终返回布尔值):
getValue(opts?: TraitGetValueOptions) { return this.getTargetValue(opts); }getTargetValue的内部逻辑(Trait.ts)优先级为:自定义getValue回调 >changeProp时的component.get(name)>component.getAttributes()[name];若useType且类型为checkbox,则对照valueTrue/valueFalse将值归一化为布尔。
setValue(value, opts)— 更新 Trait 值。默认作用于组件 attributes,changeProp开启时作用于 property;opts.partial为true时更新不会被 UndoManager 完整记录:
setValue(value: any, opts: TraitSetValueOptions = {}) { const { partial } = opts; const valueOpts: { avoidStore?: boolean } = {}; if (partial) { valueOpts.avoidStore = true; } this.setTargetValue(value, valueOpts); }若 Trait 定义了自定义setValue回调(与getValue成对使用),setValue会优先调用该回调,并把value、component、editor、trait、partial、options以及emitUpdate一并传入(见 Trait.ts)。setTargetValue还会把字符串'false'/'true'转换为布尔,并对 checkbox 类型套用valueTrue/valueFalse映射(Trait.ts)。
选项相关方法
| 方法 | 说明 |
|---|---|
getOption(id?) | 获取当前选中项或按 id 查找的选项;不传 id 时用当前值匹配,返回Object \| null |
getOptionId(option) | 从选项对象中取 id,option.id不存在时回退到option.value |
getOptionLabel(id, opts) | 获取选项标签,locale 开启时查找traitManager.traits.options.${name}.${optId},否则用option.label/option.name/optId(见 Trait.ts) |
命令执行
runCommand()— 执行按钮类型(button)Trait 绑定的命令。command可以是命令 ID 字符串(走em.Commands.run(command))或函数(以(em.Editor, trait)调用),见 Trait.ts。
四、组件上定义 Trait:从属性绑定到属性绑定
默认情况下 Trait 修改组件的 attributes,因此初始值也要通过attributes声明:
editor.Components.addType('input', { isComponent: (el) => el.tagName === 'INPUT', model: { defaults: { traits: [ 'name', // 字符串自动转为 text 类型 'placeholder', { type: 'select', name: 'type', // (必填) 作用到组件的 attribute/property 名 label: 'Type', options: [ { id: 'text', label: 'Text' }, { id: 'email', label: 'Email' }, { id: 'password', label: 'Password' }, { id: 'number', label: 'Number' }, ], }, { type: 'checkbox', name: 'required' }, ], attributes: { type: 'text', required: true }, // 初始值 }, }, });开启changeProp: 1后,Trait 值作用于组件 property,初始值也需改从 property 声明,且监听事件从change:attributes:*变为change:*:
editor.Components.addType('input', { model: { defaults: { traits: [{ name: 'placeholder', changeProp: 1 }], placeholder: 'Initial placeholder', // 从 property 设置初始值 }, init() { this.on('change:placeholder', this.handlePlhChange); }, }, });动态 Trait
Trait 还可以定义为函数,在组件初始化时按需生成,例如根据draggable属性决定返回哪些 Trait(详见 Traits.md):
editor.Components.addType('input', { model: { defaults: { traits(component) { const result = []; if (component.get('draggable')) { result.push('name'); } else { result.push({ type: 'select', name: 'type', options: [...] }); } return result; }, }, }, });内置 Trait 类型速查
| 类型 | 关键参数 | 说明 |
|---|---|---|
text | label(可设false隐藏标签列)、placeholder | 默认类型,简单文本输入 |
number | min、max、step、unit | 数字输入。源码中由 TraitNumberView.ts 借助InputNumber渲染,getValueForTarget会把value + unit拼回组件 |
checkbox | valueTrue(默认true)、valueFalse(默认false) | 复选输入。渲染与取值见 TraitCheckboxView.ts,onChange直接以checked布尔写回 model |
select | options: [{ id, label }] | 下拉选择。渲染逻辑见 TraitSelectView.ts:字符串选项直接用自身作 name/value,对象选项支持name/label/value/style;选中值不在列表中时回退default |
color | — | 颜色选择器 |
button | text/labelButton、full(全宽)、command(命令 ID 或函数) | 按钮,点击执行runCommand() |
五、运行时更新 Trait
Trait 是组件的普通属性,运行时可通过 Component API 读取与修改:
const component = editor.getSelected(); // 画布中选中的组件 // 获取全部 traits const traits = component.get('traits'); traits.forEach((trait) => console.log(trait.props())); // 按 name/id 查找单个 trait console.log(component.getTrait('type').props()); // 更新 trait 属性(如 select 的 options) component.getTrait('type').set('options', [ { id: 'opt1', label: 'New option 1' }, { id: 'opt2', label: 'New option 2' }, ]); // 或一次性设置多个属性 component.getTrait('type').set({ label: 'My type', options: [...] });新增与移除 Trait 使用addTrait/removeTrait,其底层实现在 Component.ts:
// 新增:at 指定插入位置,缺省追加到末尾 component.addTrait({ name: 'type', ... }, { at: 0 }); // 也支持字符串或数组批量添加 component.addTrait(['title', { type: 'checkbox', name: 'disabled' }]); // 移除:支持单个或数组 component.removeTrait('type'); component.removeTrait(['title', 'id']);addTrait内部调用this.traits.add(...)(集合逻辑见 Traits.ts,字符串经TraitFactory.build转换),两者都会触发ComponentsEvents.toggled事件刷新设置面板。
分类(Categories)
Trait 可按category分组,组内折叠状态可配置(无分类的 Trait 渲染在底部):
const category1 = { id: 'first', label: 'First category' }; const category2 = { id: 'second', label: 'Second category', open: false }; editor.Components.addType('input', { model: { defaults: { traits: [ { name: 'trait-1', category: category1 }, { name: 'trait-2', category: category1 }, { name: 'trait-3', category: category2 }, { name: 'trait-4', category: category2 }, { name: 'trait-5' }, // 无分类,渲染在底部 { name: 'trait-6' }, ], }, }, });模块层可用editor.Traits.getTraitsByCategory()获取按分类分组后的 Trait 列表(见 index.ts)。
六、Trait 值的底层读写链路
理解值读写链路有助于排查"Trait 为什么没生效"类问题。从源码看,整条链路为:
- 输入触发:用户操作输入框,
TraitView监听捕获事件(默认change,见 TraitView.ts),onChange先把值写入 model 的value,再调用自定义onEvent。 - 写回组件:
onValueChange调用model.setValue(value, opts)→setTargetValue→ 根据changeProp分别走component.set(props)或component.addAttributes(props);partial时通过props.__p = null标记避免被 UndoManager 记录。 - 组件反向同步:组件属性变化触发
setTarget中绑定的change:attributes:${name}(或change:${name})事件 →targetUpdated()→ 以useType: true重新取值并写回 Trait model,同时触发trait:value与trait:update事件(见 Trait.ts)。
对 checkbox 类型的值归一化,getTargetValue的useType分支会对照valueTrue/valueFalse返回布尔;而setTargetValue写回时会反向把布尔映射回valueTrue/valueFalse,二者成对保证了双向一致性。
七、自定义 Trait 类型与自定义 Trait Manager
默认类型覆盖多数场景,需要更复杂 UI 时可用editor.Traits.addType注册新类型,其核心只需三个钩子方法(基于TraitView的extend,见 index.ts):
createInput({ trait, component })— 返回 HTML 字符串或 DOM 元素,定义输入控件;onEvent({ elInput, component, event })— 输入变化时更新组件;onUpdate({ elInput, component })— 组件变化时回填输入。
以一个href-next自定义类型为例,先替换link组件的 Trait:
editor.Components.addType('link', { model: { defaults: { traits: [{ type: 'href-next', name: 'href', label: 'New href' }], }, }, });再注册类型并定义控件与双向绑定:
editor.Traits.addType('href-next', { createInput({ trait }) { const traitOpts = trait.get('options') || []; const options = traitOpts.length ? traitOpts : [ { id: 'url', label: 'URL' }, { id: 'email', label: 'Email' }, ]; const el = document.createElement('div'); el.innerHTML = ` <select class="href-next__type"> ${options.map((opt) => `<option value="${opt.id}">${opt.label}</option>`).join('')} </select> <div class="href-next__url-inputs"><input class="href-next__url" placeholder="Insert URL"/></div> <div class="href-next__email-inputs"> <input class="href-next__email" placeholder="Insert email"/> <input class="href-next__email-subject" placeholder="Insert subject"/> </div> `; // ... 类型切换逻辑(切换 url/email 输入区显示) return el; }, onEvent({ elInput, component }) { const inputType = elInput.querySelector('.href-next__type'); let href = ''; switch (inputType.value) { case 'url': href = elInput.querySelector('.href-next__url').value; break; case 'email': { const valEmail = elInput.querySelector('.href-next__email').value; const valSubj = elInput.querySelector('.href-next__email-subject').value; href = `mailto:${valEmail}${valSubj ? `?subject=${valSubj}` : ''}`; break; } } component.addAttributes({ href }); }, onUpdate({ elInput, component }) { const href = component.getAttributes().href || ''; // ... 按 href 前缀回填 url / email 输入框并同步 select 状态 }, });补充说明(源码佐证):
- 事件捕获:默认监听
change事件(TraitView.ts 的TraitView.prototype.eventCapture = ['change']);如需在输入过程中实时更新,用eventCapture: ['input']覆盖。 - 布局定制:
createLabel({ label })自定义标签列;noLabel: true强制隐藏标签列(与label: false等效);templateInput可替换默认输入包裹层——传''完全去掉包裹,或传含data-input占位属性的模板(也可为函数)。默认模板见 TraitView.ts。 - 手动触发:在外部 UI 中要触发
onEvent时,使用内置的onChange方法而非直接调用onEvent(如 Vue Slider 集成示例中的sliderInst.$on('change', (ev) => this.onChange(ev)))。
若需要完全替换默认设置面板 UI,初始化时开启traitManager.custom: true并订阅trait:custom事件(配置项定义见 config.ts):
const editor = grapesjs.init({ traitManager: { custom: true }, }); editor.on('trait:custom', (props) => { // props.container (HTMLElement) — 默认容器,可挂载自定义 UI // 在此实现渲染/更新逻辑 });八、I18n 与事件
Trait 支持通过 I18n 模块国际化,完整 schema 如下(对应 Traits.md 的 i18n 章节):
{ en: { traitManager: { empty: 'Select an element before using Trait Manager', label: 'Component settings', categories: { categoryId: 'Category label', // 分类标签 }, traits: { labels: { href: 'Href label', // 以 trait 的 name 为键 }, attributes: { href: { placeholder: 'eg. https://google.com' }, // 输入 DOM 属性 }, options: { target: { _blank: 'New window', // 以 option 的 id 为键 }, }, }, }, }, }模块事件(枚举定义见 types.ts):
| 事件 | 触发时机 | 回调数据 |
|---|---|---|
trait:select | 切换组件导致 Trait 重新选中 | { traits, component } |
trait:value | Trait 值更新 | { trait, component, value } |
trait:update | 任意 Trait 属性变化 | { trait, component, value } |
trait:category:update | 分类更新 | { category, changes } |
trait:custom | 自定义 UI 需要刷新 | { container } |
trait | 以上所有事件的聚合事件 | { event, trait, component, value, ... } |
editor.on('trait:value', ({ trait, component, value }) => { console.log(`${trait.getName()} -> ${value}`); });九、总结
本文完整覆盖了 docs/api/trait.md 中Trait的全部属性与方法,并向下延伸了组件定义、运行时更新、分类、内置类型参数、自定义类型钩子与 i18n/事件体系。核心要点回顾:
- Trait 本质是绑定到组件 attributes 或 properties 的模型对象,
changeProp决定绑定目标; getValue/setValue构成双向读写入口,partial控制是否进入 UndoManager,useType控制类型归一化;- 字符串简写经
TraitFactory转换为text类型,target有内置的 select 特化; - 自定义 Trait 只需实现
createInput/onEvent/onUpdate三件套,配合eventCapture、templateInput、noLabel即可完全掌控渲染与交互。
进一步可阅读 Traits 模块文档 了解完整实战示例,或查看 TraitManager API 掌握模块级方法。
【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考