如何为MooEditable编写Action扩展:快捷键、状态到对话框完整指南
如何为MooEditable编写Action扩展:快捷键、状态到对话框完整指南
MooEditable 是一款基于 MooTools 的轻量级 Web WYSIWYG 所见即所得编辑器,它的每一个工具栏按钮、快捷键、按钮高亮状态乃至弹出对话框,背后都是一个注册在 MooEditable.Actions 对象里的 Action 扩展。本文以最短路径带你吃透 Action 的 6 个成员(title / options / states / command / dialogs / events),学会从"一句话按钮"到"带对话框+快捷键+状态高亮"的完整 Action 扩展写法,并附 4 个真实源码案例与排错清单,让你快速掌握 MooEditable 自定义按钮扩展的方法。
1. 先搞懂:MooEditable 的 Action 机制
在 MooEditable 里,"Action"= 工具栏上的一枚按钮 + 一串可被触发的行为。所有 Action 都注册在全局对象 MooEditable.Actions 上,核心定义位于 MooEditable.js。
编辑器初始化时会自动完成三件事:
- 遍历
actions选项中的每个名字,到MooEditable.Actions里查找对应对象,并在工具栏上渲染按钮; - 收集每个 Action 的
options.shortcut,注册成 Ctrl/Cmd + 字母 快捷键; - 绑定每个 Action 声明的
dialogs与events。
这段"自动绑定"逻辑就在构造函数里,源码见 MooEditable.js:
this.actions.each(function(action){
var act = MooEditable.Actions[action];
if (!act) return;
if (act.options){
var key = act.options.shortcut;
if (key) this.keys[key] = action;
}
if (act.dialogs){
Object.each(act.dialogs, function(dialog, name){
dialog = dialog.attempt(this);
dialog.name = action + ':' + name;
// ... 自动注入对话框并绑定 open/close
}, this);
}
if (act.events){
Object.each(act.events, function(fn, event){
this.addEvent(event, fn);
}, this);
}
}.bind(this));
💡 关键结论:注册 Action 的 JS 必须放在
mooEditable()初始化之前加载,否则找不到按钮定义。
Action 对象的 6 个成员速览
| 成员 | 作用 | 示例 |
|---|---|---|
title |
按钮标题(悬停提示),通常用 MooEditable.Locale.get() 取多语言文本 |
title: MooEditable.Locale.get('bold') |
options.shortcut |
注册快捷键(mac 上是 Cmd + 字母) | options: { shortcut: 'b' } |
states |
按钮状态高亮判定(tags / css,或自定义函数) | states: { tags: ['b', 'strong'] } |
command |
自定义执行函数,优先于浏览器 execCommand | command: function(){ ... } |
dialogs |
该 Action 可打开的对话框(打开时自动保存选区) | dialogs: { prompt: function(editor){...} } |
events |
监听编辑器事件(attach、beforeToggleView 等) |
见 pagebreak 案例 |
2. 给 Action 加快捷键:一行 options 就够
以核心源码中的加粗按钮为例(MooEditable.js):
bold: {
title: MooEditable.Locale.get('bold'),
options: {
shortcut: 'b'
},
states: {
tags: ['b', 'strong'],
css: {'font-weight': 'bold'}
}
}
快捷键的生效链路非常简洁——initialize 中把 shortcut 存入 this.keys,按键时由 keyListener 触发对应工具栏按钮(MooEditable.js):
keyListener: function(e){
var key = (Browser.Platform.mac) ? e.meta : e.control;
if (!key || !this.keys[e.key]) return;
e.preventDefault();
var item = this.toolbar.getItem(this.keys[e.key]);
item.action(e);
}
也就是说:你不需要写任何 keydown 监听代码,声明 shortcut 即可,且按钮悬停提示里会自动显示 Ctrl + B 之类的字样。
⚠️ 注意:同一个字母只能属于一个 Action,声明顺序以
MooEditable.Actions中先出现的为准。
3. 状态高亮原理:states 的三种写法
光标停留在某段加粗文字上时,加粗按钮会亮起——这就是 states 的工作。状态检测发生在每次鼠标抬起/按键之后,由 checkStates() 统一调度(MooEditable.js),判定顺序是:
- 优先尝试浏览器的
queryCommandState(action); - 失败后向上查找祖先节点,匹配
states.tags(标签名列表); - 再匹配
states.css(行内样式,如text-align: center)。
三种写法对照:
// 写法1:标签匹配(bold 按钮)
states: { tags: ['b', 'strong'] }
// 写法2:CSS 匹配(justifycenter 按钮)
states: { tags: ['center'], css: {'text-align': 'center'} }
// 写法3:自定义函数(完全掌控判定逻辑)
states: function(element, item){
if (element.get('tag') == 'custom-tag') item.activate();
}
写法 1/2 来自 MooEditable.js 与 MooEditable.Extras.js;写法 3 由 checkStates 中的 typeOf(states) == 'function' 分支支持。
4. command:让 Action 执行自定义逻辑
按钮被点击(或快捷键被按下)时,核心 action() 方法会这样分发(MooEditable.js):
action: function(command, args){
var action = MooEditable.Actions[command];
if (action.command && typeOf(action.command) == 'function'){
action.command.apply(this, args);
} else {
this.focus();
this.execute(command, false, args); // 走浏览器 execCommand
if (this.mode == 'iframe') this.checkStates();
}
}
由此得到两条规则:
- 没有
command:按钮名必须是一个原生execCommand命令(如bold、undo、createlink),编辑器自动执行; - 有
command:函数体内的this就是编辑器实例,可自由调用this.execute()、this.selection、this.dialogs等 API。
经典例子——"插入水平线",不依赖 execCommand,直接往选区插入 HTML(MooEditable.Extras.js):
insertHorizontalRule: {
title: MooEditable.Locale.get('insertHorizontalRule'),
states: { tags: ['hr'] },
command: function(){
this.selection.insertContent('<hr>');
}
}
带参数的场景:下拉菜单按钮(type: 'menu-list')点击后,选中项会作为第 2 个参数传入(MooEditable.Extras.js):
formatBlock: {
title: MooEditable.Locale.get('blockFormatting'),
type: 'menu-list',
options: {
list: [
{text: MooEditable.Locale.get('paragraph'), value: 'p'},
{text: MooEditable.Locale.get('heading1'), value: 'h1', style: 'font-size:24px; font-weight:bold;'}
]
},
command: function(menulist, name){ // name 即选中的 value
this.focus();
this.execute('formatBlock', false, '<' + name + '>');
}
}
💡
this上下文是 MooTools 惯用设计:command里直接写this.execute(...)、this.selection.insertContent(...)即可,无需手动传 editor 对象。
5. 对话框扩展:dialogs 让按钮弹出输入框
给 Action 声明 dialogs 后,初始化时会自动实例化并注入页面,且框架替你处理了繁琐细节:打开对话框时自动保存选区、禁用工具栏,关闭时恢复选区(MooEditable.js)。
最简示例——内置的 urlimage(按图片 URL 插入图片,MooEditable.js):
urlimage: {
title: MooEditable.Locale.get('addImage'),
options: { shortcut: 'm' },
dialogs: {
prompt: function(editor){
return MooEditable.UI.PromptDialog(MooEditable.Locale.get('enterImageURL'), 'http://', function(url){
editor.execute('insertimage', false, url.trim());
});
}
},
command: function(){
this.dialogs.urlimage.prompt.open();
}
}
两个常用对话框工厂函数:
MooEditable.UI.PromptDialog(question, answer, fn)—— 带输入框的对话框,点击 OK 后回调fn(answer);MooEditable.UI.AlertDialog(text)—— 纯提示,用于校验拦截(如未选中文字时提示"请先选中文本")。
更复杂的自定义对话框(表单、图片属性面板)可参考 MooEditable.Image.js 中的 MooEditable.UI.ImageDialog:自己构建 MooEditable.UI.Dialog 的 HTML 与事件,再挂到 dialogs 上即可,打开逻辑同样是一句 this.dialogs.image.prompt.open()。
6. events:监听编辑器生命周期
Action 还可以通过 events 挂接编辑器事件,最实用的两个是:
attach:编辑器挂载完成时执行(可注入额外 CSS、监听特殊 DOM 事件);beforeToggleView:源码/所见即所得视图切换前执行(可替换内容里的可视化占位符)。
核心源码里的分页符插件就是完整范本(MooEditable.Pagebreak.js):
MooEditable.Actions.pagebreak = {
title: MooEditable.Locale.get('pageBreak'),
command: function(){
this.selection.insertContent('<img class="mooeditable-visual-aid mooeditable-pagebreak">');
},
events: {
// 切回源码视图时,把可视化占位图片替换成 HTML 注释
beforeToggleView: function(){
if (this.mode == 'iframe'){
var s = this.getContent().replace(
/<img([^>]*)class="mooeditable-visual-aid mooeditable-pagebreak"([^>]*)>/gi,
'<!-- page break -->');
this.setContent(s);
}
},
// 初始化时注入专属 CSS
render: function(){
this.options.extraCSS = 'img.mooeditable-pagebreak { ... }' + this.options.extraCSS;
}
}
};
💡 插件里还有
MooEditable.Actions.Settings这一约定俗成的配置对象(如分页符图片路径),便于用户不改源码就修改行为,值得在自己插件里沿用。
7. 从零写一个 Action:插入水平线实战
把前 5 节组合起来,30 秒写出一个完整 Action:
// 第 1 步:在 MooEditable.js 加载之后注册 Action
MooEditable.Actions.hline = {
title: '插入水平线',
options: { shortcut: 'h' },
states: { tags: ['hr'] },
command: function(){
this.selection.insertContent('<hr>');
}
};
// 第 2 步:初始化时把它放进 actions 字符串
$('textarea-1').mooEditable({
actions: 'bold italic | hline | toggleview'
});
actions 字符串就是工具栏的"排版代码":空格分隔按钮,| 是分组分隔线,/ 是换行分隔。更多自定义工具栏组合可参考示例页 MooEditable.custom-toolbarbuttons.html。
页面多实例场景下,MooEditable.Group 用一个工具栏控制多个编辑器,自定义 Action 只需注册一次、在所有实例中共享,同样通过 actions 选项声明。
常见 Action 写法速查表
| 案例 | 位置 | 用到的成员 |
|---|---|---|
bold |
MooEditable.js | title / options / states / events |
createlink |
MooEditable.js | 对话框 + 选区校验 + 自定义 command |
insertHorizontalRule |
MooEditable.Extras.js | 纯 command 插入 HTML |
forecolor |
MooEditable.Forecolor.js | button-overlay 覆盖层按钮 |
pagebreak |
MooEditable.Pagebreak.js | command + events + Settings |
image |
MooEditable.Image.js | 完整自定义 Dialog |
8. 排错清单与总结
扩展开发翻车的 5 个高频原因:
- 加载顺序错误:注册
MooEditable.Actions.xxx的脚本晚于mooEditable()调用,按钮直接消失(addItem找不到 Action 会静默跳过); - 快捷键冲突:
shortcut使用了已被占用的字母(如b、m),后注册者不生效; - 按钮不高亮:忘记写
states,或标签名与清理后的输出不一致(注意semantics选项开启后<b>会变成<strong>,tags里两者都要写); - command 里 this 丢失:用箭头/回调包装函数后
this不再是编辑器实例,执行命令前记得this.focus(); - 对话框内丢失选区:自己
new MooEditable.UI.Dialog却绕过dialogs注册,失去了框架的选区自动保存/恢复能力。
📌 一句话总结:写 MooEditable Action = 往 MooEditable.Actions 挂一个对象(title + shortcut + states + command/dialogs/events),再把它写进 actions 字符串。从 MooEditable.js 的基础按钮、MooEditable.Extras.js 的菜单按钮,到 MooEditable.Pagebreak.js 的完整插件,把这三个文件通读一遍,你就具备了扩展 MooEditable 的全部能力。更多接口细节可查阅 Docs/MooEditable/MooEditable.md。