GrapesJS Panels 面板模块 API 完全指南:用 panelManager 打造自定义编辑器工具栏
【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs
GrapesJS 的 Panels 模块负责管理编辑器界面中的面板(Panel)与按钮(Button),构成了顶部工具栏、侧边栏等所有 UI 控件的骨架。本文基于官方 API 文档 docs/api/panels.md,结合仓库源码(panels/index.ts、panels/config/config.ts 等),系统讲解如何通过配置对象和editor.Panels模块 API 动态增删面板与按钮、绑定命令并控制其行为,读完即可在真实项目中定制属于自己的编辑器工具栏。
模块总览:Panels 在 GrapesJS 中的角色
在 GrapesJS 中,"面板(Panel)"是承载按钮(Button)的容器,按钮则是触发命令(Command)的入口。整个编辑器的交互入口几乎都由该模块驱动:顶部工具栏(Top Bar)由commands与options面板组成,右侧"视图切换"区域由views面板承载 Style Manager、图层、块等面板的开关按钮。从源码看,该模块由PanelManager类实现,它继承自Module基类,内部维护一个Panels集合(panels/index.ts),对应的渲染由PanelsView完成。
使用面板模块有两种途径:
- 初始化配置:在
grapesjs.init()时通过panels选项定义模块的初始状态(如默认面板、按钮); - 运行时 API:编辑器实例化后,通过
editor.Panels获取模块实例,调用其方法动态操作面板与按钮。
const editor = grapesjs.init({ panels: { // options } }); // 编辑器实例化后获取模块 const panelManager = editor.Panels;初始化配置:panels 配置对象
在编辑器初始化阶段传入panels配置对象即可自定义模块的初始状态。配置类型定义于 panels/config/config.ts,共两个顶层选项:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
stylePrefix | string | 'pn-' | 面板/按钮的 CSS 类名前缀 |
defaults | PanelProps[] | 见下方说明 | 默认面板集合 |
其中defaults数组中的每个面板对象可包含:id、buttons(按钮对象数组)、以及 Panel.ts 中定义的其余属性(content、visible、attributes、appendTo、resizable等)。
从源码 config.ts 可以看到内置的默认面板结构,这正是标准 GrapesJS 界面顶部工具栏的由来:
commands面板:预留给自定义命令按钮的占位面板;options面板:包含"显示组件轮廓(core:component-outline)""预览(preview)""全屏(fullscreen)""查看代码(export-template)"四个按钮;views面板:包含 Style Manager、设置(Traits)、图层管理、块管理四个视图切换按钮。
因此,一个常见的自定义方式是覆盖defaults,只保留你需要的按钮:
const editor = grapesjs.init({ panels: { defaults: [ { id: 'options', buttons: [ { id: 'preview', className: 'fa fa-eye', command: 'preview', attributes: { title: '预览' }, }, ], }, ], }, });面板与按钮的数据模型
在动手调用 API 之前,理解底层数据模型有助于写出正确的配置。面板模型Panel(Panel.ts)的默认属性为:
| 属性 | 默认值 | 说明 |
|---|---|---|
id | '' | 面板唯一标识 |
content | '' | 面板内 HTML 内容 |
visible | true | 面板是否可见 |
buttons | [] | 按钮集合(内部被包装为Buttons集合) |
attributes | {} | 渲染到面板 DOM 上的属性 |
appendTo | 无 | 指定 CSS 选择器,将面板追加到该元素下 |
resizable | 无 | boolean或ResizerOptions,启用面板尺寸调整 |
按钮模型Button(Button.ts)的默认属性为:
| 属性 | 默认值 | 说明 |
|---|---|---|
id | '' | 按钮唯一标识 |
label | '' | 按钮显示文本 |
tagName | 'span' | 按钮渲染的 HTML 标签 |
className | '' | 额外 CSS 类 |
command | '' | 绑定的命令(字符串/对象/函数) |
context | '' | 互斥上下文,同 context 的按钮互斥激活 |
attributes | {} | 渲染到按钮 DOM 上的属性(常用于title提示) |
active | false | 按钮激活状态 |
togglable | true | 是否可切换激活状态 |
runDefaultCommand | true | 激活时是否同时运行默认命令 |
stopDefaultCommand | false | 取消激活时是否同时停止默认命令 |
disable | false | 禁用按钮(点击无响应) |
dragDrop | false | 是否可拖拽 |
options | {} | 传给命令的附加选项 |
核心 API 详解
模块实例panelManager = editor.Panels提供 8 个公开方法,覆盖面板与按钮的增、删、查三个维度。以下逐一讲解,示例均来自官方文档并可复制运行。
获取面板集合与 DOM:getPanels / getPanelsEl
// 返回面板集合(Panels Collection) const panels = panelManager.getPanels(); // 返回面板容器的 HTMLElement const panelsEl = panelManager.getPanelsEl();getPanels()返回的是内部Panels集合实例(panels/index.ts),基于 Backbone Collection,可用each、where等集合方法遍历。getPanelsEl()返回PanelsView渲染出的根元素(panels/index.ts),它带有pn-panels类名,方便你通过 DOM 操作定制外层布局。
添加面板:addPanel
const newPanel = panelManager.addPanel({ id: 'myNewPanel', visible: true, buttons: [...], });addPanel接受一个属性对象或Panel实例,返回添加后的Panel(panels/index.ts)。从 Panel.ts 的实现可以看到,传入的buttons数组会在构造时被自动包装为Buttons集合,因此你可以放心地以普通对象数组的形式传入按钮。
一个带完整功能的示例:
const panel = panelManager.addPanel({ id: 'myToolbar', visible: true, content: '<div class="my-toolbar-label">自定义工具栏</div>', buttons: [ { id: 'btn-1', className: 'fa fa-star', command: 'someCommand', attributes: { title: '点我执行命令' }, }, ], // 可选:让面板支持拖拽调整大小 resizable: true, });获取面板:getPanel
const myPanel = panelManager.getPanel('myPanel');getPanel(id)按 id 在集合中查找面板,找到返回面板实例,找不到返回null(panels/index.ts)。
移除面板:removePanel
// 通过面板实例移除 const somePanel = panelManager.getPanel('somePanel'); const removedPanel = panelManager.removePanel(somePanel); // 或直接传面板 id const removedPanel = panelManager.removePanel('myNewPanel');removePanel接受面板实例或面板 id,返回被移除的面板(panels/index.ts)。从 PanelsView.ts 的onRemove处理可以看到,面板移除后其对应视图也会被同步销毁,DOM 会自动清理。
添加按钮:addButton
const newButton = panelManager.addButton('myNewPanel', { id: 'myNewButton', className: 'someClass', command: 'someCommand', attributes: { title: 'Some title' }, active: false, });addButton(panelId, button)向指定面板追加按钮,面板不存在时返回null(panels/index.ts)。
command属性是按钮的核心,支持三种写法:
写法一:字符串命令名(推荐,复用 Commands 模块中已注册的命令):
command: 'someCommand',写法二:带run/stop方法的命令对象:
command: { run: function(editor) { // 按钮激活时执行 }, stop: function(editor) { // 按钮取消激活时执行 } },写法三:函数(等价于只有run的命令):
command: function(editor) { // 每次点击按钮时执行 }从按钮视图 ButtonView.ts 的updateActive实现可以看出三种写法的解析逻辑:字符串会通过commands.get()查找已注册命令;对象与函数会被包装为命令执行。同时,激活按钮时若该命令没有stop方法(command.noStop),按钮会自动取消激活。
移除按钮:removeButton
const removedButton = panelManager.addButton('myNewPanel', { id: 'myNewButton', className: 'someClass', command: 'someCommand', attributes: { title: 'Some title' }, active: false, }); const removedButton = panelManager.removeButton('myNewPanel', 'myNewButton');removeButton(panelId, buttonId)从指定面板移除按钮,返回被移除的按钮,面板不存在时返回null(panels/index.ts)。
获取按钮:getButton
const button = panelManager.getButton('myPanel', 'myButton');getButton(panelId, id)在指定面板内按按钮 id 查找,返回按钮实例或null(panels/index.ts)。
按钮的激活、互斥与禁用机制
掌握按钮的状态机是定制工具栏的关键,这些行为定义在 ButtonView.ts 中:
- 激活切换:点击按钮触发
clicked→toggleActive,通过model.active = !active切换状态。若按钮处于激活态且togglable: false,点击不会取消激活(ButtonView.ts),这正是views面板中 Style Manager 等按钮只开不关的原因。 - 同组互斥:
Buttons.deactivateAll会根据context字段把同 context 的其他按钮置为非激活(Buttons.ts)。因此给同一组模式按钮设置相同的context即可实现单选互斥,例如默认options面板中"预览"与"组件轮廓"按钮都通过context实现互斥。 - 禁用:
disable: true的按钮在点击时直接忽略(ButtonView.ts),且视图层会添加禁用样式类。源码中还提供了disableAllButtons/disableAllButtonsExceptOne等集合级方法(Buttons.ts),适合按上下文批量控制按钮可用性。 - 命令联动:当
listen开启且命令为字符串时,按钮会监听run:${command}与stop:${command}事件,使按钮激活状态与命令执行状态自动同步(ButtonView.ts)。
面板渲染与自定义位置:el 与 appendTo
默认情况下,所有面板都会被渲染进同一个pn-panels根容器中。但部分场景下你希望把面板放到页面其他位置,此时可利用PanelProperties中的两个属性(PanelsView.ts):
appendTo:传入 CSS 选择器字符串,面板会被追加到document.querySelector(appendTo)命中的元素中;el:直接传入已有的 DOM 元素引用,面板视图将复用它,不再生成新容器。
panelManager.addPanel({ id: 'floatingPanel', appendTo: '#my-custom-container', // 渲染到页面指定容器 buttons: [{ id: 'b1', command: 'someCommand' }], });从 PanelView.ts 的render方法可见,面板最终渲染结构包含按钮集合视图与content内容,并带有pn-panel前缀的类名与ppfx(editor stylePrefix)衍生样式类,便于主题定制。
完整示例:为编辑器添加自定义导出工具栏
综合以上内容,下面是一个可运行的完整示例——为编辑器新增一个"数据操作"面板,包含两个按钮:一个通过字符串命令名触发已注册命令,一个通过函数直接执行逻辑:
const editor = grapesjs.init({ container: '#gjs', // 先注册一个命令,供按钮引用 commands: { add: { exportData() { const data = editor.getProjectData(); console.log('项目数据', data); }, }, }, panels: { // 保留默认面板,同时新增自定义面板 defaults: [ ...(grapesjs.plugins.get('grapesjs-preset-webpage') ? [] : []), // 说明:此处可改为从编辑器默认配置继承,或直接使用默认配置再追加 ], }, }); const panelManager = editor.Panels; // 方案 A:完全自定义一个新面板 panelManager.addPanel({ id: 'data-panel', visible: true, buttons: [ { id: 'export-btn', label: '导出', command: 'exportData', // 字符串命令 attributes: { title: '导出项目数据' }, }, { id: 'greet-btn', label: '问候', // 函数命令:点击即执行 command: (ed) => { console.log(`Hello from ${ed.getProjectName() || 'GrapesJS'}`); }, }, ], }); // 方案 B:往已存在的默认面板中追加按钮 panelManager.addButton('options', { id: 'my-toggle', className: 'fa fa-magic', command: { run(ed) { console.log('开始'); }, stop(ed) { console.log('结束'); }, }, togglable: true, context: 'myGroup', // 与同 context 按钮互斥 }); // 运行时读取 const panel = panelManager.getPanel('data-panel'); const btn = panelManager.getButton('options', 'my-toggle'); console.log(panel, btn);若默认面板被自定义defaults覆盖,需要注意commands/options/views这些内置面板 id 的可用性——源码中的默认面板结构(config.ts)是理解这一行为的最佳参照。
相关测试与进一步探索
仓库为面板模块提供了完整的测试覆盖,可作深入参考:
- 模块级 API 测试:packages/core/test/specs/panels/index.ts
- 端到端交互测试:packages/core/test/specs/panels/e2e/PanelsE2e.js
- 类型定义与导出:packages/core/src/index.ts
需要更深一层定制时,可继续阅读 panels/view/ButtonView.ts 了解按钮点击与命令解析的完整链路,或参考 docs/modules/Commands.md 掌握命令的注册与执行机制,从而让面板按钮与编辑器命令体系无缝协作。
【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考