CKEditor插件开发

From: http://www.voofie.com/content/2/ckeditor-plugin-development/ 根据comment有修改
CKeditor API: http://docs.cksource.com/ckeditor_api/index.html

CKEditor 是目前市场上比较灵活的在线WYSIWYG编辑器之一. 它灵活的设计, 开放的API和详细的文档使得用户扩展功能非常容易. 本文尝试勾勒出 CKEditor插件开发的基础,包含了增加按钮,对话框和执行命令.

源代码的结构

在开始之前,感性的认知一下CKEditor源码的组织形式是很有用的. CKEditor固有的一些文件被组织到ckeditor\_source目录里. 核心的功能,诸如DOM元素操作,事件处理,初始化脚本和一些环境设置被包含在ckeditor\_source\core文件夹内. 而其它的一些功能, 比如格式化,拷贝和粘贴, 图片和链接, 都被实现为插件形式放在ckeditor\_source\plugins文件夹内. 每个文件夹表示一个插件. 并且在每个文件夹内, 有一个plugin.js的文件包含了该插件需要用到的代码.

你可以看到源代码被组织成不同的文件. 为了减少HTTP请求, CKEditor把不同的文件压缩并打包到ckeditor.js和ckeditor_basic.js里, 这种方式是运行编辑器的默认方式. 在开发的过程中, 你会希望通过ckedtior_source.js来代替ckeditor.js的执行.

现在, 创建ckeditor\_source\plugins\footnote目录,并在目录里创建plugin.js文件.

配置插件

为了开始开发你的插件, 你需要首先注册你的插件,这样CKEditor才能载入它. 在ckeditor/config.js里,增加:

config.extraPlugins = 'footnote';

此配置将会告诉编辑器在footnote目录下载入plugin.js. 基本的plugin.js结构如下:
CKEDITOR.plugins.add('footnote',
{
    init: function(editor)
    {
        //plugin code goes here
    }
});

按钮

CKEditor中主要的功能的实现基本都是通过命令的形式. 命令由事件,函数调用或者点击某个工具栏的按钮触发. 下列代码增加了一个’Footnote’的按钮和‘footnote’的命令.

CKEDITOR.plugins.add('footnote',
{
    init: function(editor)
    {
        var pluginName = 'footnote';
        CKEDITOR.dialog.add(pluginName, this.path + 'dialogs/footnote.js');
        editor.addCommand(pluginName, new CKEDITOR.dialogCommand(pluginName));
        editor.ui.addButton('Footnote',
            {
                label: 'Footnote or Citation',
                command: pluginName
            });
    }
});

editor.ui.addButton函数声明包含了两个参数, 按钮名字和按钮定义. 在按钮定义中可能的属性还包含:
  • label: 当鼠标位于按钮之上时所出现的文字提示
  • className: 按钮的css类名. 默认为: ‘cke_button_’ + 命令名称
  • click: 当点击按钮后所调用的函数. 默认为: 执行由 命令键值 指定的命令.
  • command: 将在按钮点击之后执行的命令
    上述代码利用了CKEDTIOR.dialogCommand,该函数将在下面的对话章节中介绍. 在添加了一个按钮后,你需要把‘Footnote’增加到config.js的工具栏的定义中, 把该按钮放到工具栏的合适的位置. 之后,你就能看到一个空的按钮出现在工具栏里.

按钮图片

最简单的增加按钮图片的办法是利用属性icon. 代码如下:

CKEDITOR.plugins.add('myplugin',
{
    init: function(editor)
    {
        //plugin code goes here
        editor.ui.addButton('myplugin',
            {
                label: 'myplugin',
                command: FCKCommand_myplugin,
                icon: CKEDITOR.plugins.getPath('myplugin') + 'myplugin.png'
            });
    }
});

命令

CKEditor以命令的方式提供了大部分的功能. 命令和函数之间的不同是 命令是有“ON”, “OFF“和未启用状态的.

定义一个命令

editor.addCommand函数声明了两个参数, 命令名字和命令的对象定义. 可能的命令定义的属性包含:

  • exec: 最小形式, 定义的对象拥有一个exec方法, 该方法会在命令执行时执行.
  • modes: 命令能被执行的模式. 默认为: {wysiwyg:1} 有效的模式是: wysiwyg, source
  • editorFocus: 在执行命令时,是否给予编辑器焦点. 默认: true
  • state: 命令的状态. 默认: CKEDITOR.TRISTATE_OFF
  • canUndo: 该命令是否会和redo/undo系统挂钩
  • async: 在异步功能,例如ajax中被用到. 在执行完命令后, afterCommandExec事件将不会被自动触发. 它将期望程序员能够手工触发该事件.

执行命令

触发命令是很简单的, 通过

editor.execCommand(commandName);

命令状态

命令有三个状态: ON, OFF和DISABLED. 状态能够通过setState进行设置,例如:

editor.getCommand(commandName).setState(state);

状态值将会是: CKEDITOR.TRISTATE_ON, CKEDITOR.TRISTATE_OFF, CKEDITOR.TRISTATE_DISABLED 中的一个.
当设置为未启用的时候, 按钮将会表现为灰色, 并且通过execCommand执行的命令将无效. 当设置为on的时候, 按钮就会被高亮. 当命令的状态改变的时候, 命令将会触发一个’state’事件.

对话框

对话框是一些插件的核心. 为了使用该对话框, 他们必须首先在plugin.js中注册, 包括定义, 通过调用CKEDITOR.dialog.add如下:

CKEDITOR.dialog.add(pluginName, this.path + 'dialogs/pluginName.js');

之后,如果你想通过点击一个按钮触发这个对话框, 你可以通过使用CKEDITOR.dialogCommand来简单的完成这项工作.
editor.addCommand(pluginName, new CKEDITOR.dialogCommand(pluginName));

对话框定义

按约定,有关对话框的代码将会被放到dialogs/.js中. 和按钮和命令类似, 对话框也是通过定义一些属性和方法来定义的. 下面的代码展示了一个标准的.js的模板:

( function(){
 
  var exampleDialog = function(editor){
    return {
      title : /* title in string*/,
      minWidth : /*number of pixels*/,
      minHeight : /*number of pixels*/,
      buttons: /*array of button definitions*/,
      onOk: /*function*/ ,
      onLoad: /*function*/,
      onShow: /*function*/,
      onHide: /*function*/,
      onCancel: /*function*/,
      resizable: /* none,width,height or both  */,
      contents: /*content definition, basically the UI of the dialog*/
    }
  }
 
  CKEDITOR.dialog.add('insertHTML', function(editor) {
    return exampleDialog(editor);
  });
 
})();

定义里有以下一些属性/方法:

Buttons

该属性定义了对话框底部可用的按钮. 它是一个 CKEDITOR.dialog.buttonDefinition对象的数组. 默认的值是 [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ]. 为了增加你自己的按钮, 你须要写下按钮的定义,并把它加到该数组里. 例如:

buttons:[{
    type:'button',
    id:'someButtonID', /* note: this is not the CSS ID attribute! */
    label: 'Button',
    onClick: function(){
       //action on clicking the button
    }
},CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton],

效果如下:

对话框事件

onOK, onLoad, onShow, onHide, onCancel 事件在不同的情况下会被触发. onLoad在对话框第一次打开的时候被触发, 可以用来针对该对话框做一些初始化. onShow在对话框显示的时候被触发. onLoad和onShow的不同之处在于onLoad将只触发一次,而不管对话框被打开或关掉多少次, 而同时onShow将在对话框每次显示的时候被触发. 这是因为在按下ok或cancel的时候,对话框将被隐藏(触发onHide),而不是完全的卸载. onOk和onCancel将在ok或cancel按钮被按下的时候触发.

Resizable

该属性用来描述对话框是否可以被调整大小. 可能的值包含: CKEDITOR.DIALOG_RESIZE_NONE, CKEDITOR.DIALOG_RESIZE_WIDTH, CKEDITOR.DIALOG_RESIZE_HEIGHT and CKEDITOR.DIALOG_RESIZE_BOTH. Default is CKEDITOR.DIALOG_RESIZE_NONE

对话框的UI

对话框的用户接口是对话框属性定义的内容. 对话框的UI部分会首先被组织为几个页面, 每个页面表示了对话框的一个tab页. 在每个页面中, 输入元素(按钮,文本输入,文本区域, 单选, 下拉, 多选和文件)都将被辅助性的结构元素(vbox,hbox和label)组织起来. 通过写这些UI元素的定义, 你将不须要写任何html代码而完成对这些UI的行为和外表的定义. 当然,替代使用这些所提供的对象来构建用户界面的方法是依然选择用原始的HTML代码去实现html的UI元素。

对话框的页面

内容域是一个contentDefinition的数组, 每个定义代表了一个对话框的tab页. 下面是一个模板:

contents: [{
        id: 'page1',  /* not CSS ID attribute! */
        label: 'Page1',
        accessKey: 'P',
        elements:[ /*elements */]
    }, {
        id:'page2',
        label:'Page2',
        accessKey: 'Q',
        elements:[/*elements*/]
    }]

结果如下:

UI 元素

在定义了tab页面之后, 你可以通过传递一个uiElements的数组作为contentDefinition的元素来定义每个tab页的UI. 那里右两种类型的uiElements, 结构型的和输入型的. 结构型元素(vbox和hbox)使用表格来放置元素的位置. 结构型的元素可以镶嵌从而形成复杂的结构. 当输入的uiElements拥有一些比较重要的特征的时候这会使得他们比DOM输入元素更有用.

结构型元素

elements:[{
                    type : 'hbox',
                    widths : [ '100px', '100px', '100px' ],
                    children :
                    [{
                        type:'html',
                        html:'<div>Cell1</div>',
                    },{
                        type:'html',
                        html:'<div>Cell2</div>',
                    },{
                        type: 'vbox',
                        children:[{
                            type:'html',
                            html:'<div>Cell3</div>',
                        },{
                            type:'html',
                            html:'<div>Cell4</div>'
                        }]
                    }]

输入型元素

输入型元素有很多的属性和方法可用. 更多的细节请参考uiElement文档, 同时这里也有一些比较重要的:

  • id:(必须的) 注意这不是css的id属性, 但是是被CKEditor所使用的标识符. CKEditor将会自动设置css id属性但和这个id无关. 为了得到此对象所表示的元素, 你可以使用getContentElement. 例如, 如果你想得到uiElement内部的某个uiElement, 你可以使用:
    this.getDialog().getContentElement(pageIdName,elementIdName)  //pageIdName is the ID for page containing the element
  • type:(必须的) 是以下的其中一个: text, password, textarea, checkbox, button, select, file, fileButton, html
  • label: 和输入元素同时显示的文本标签
  • labelLayout: ‘horizontal’横向 或 ‘vertical’ 纵向. 当设置为纵向的时候,标签会在元素的顶部.
  • on* events: 函数或事件. 第一个字符必须大写. 事件可以是DOM事件,例如onChange, onClick等,就像onShow, onHide 和onLoad, 这些在对话框被激活的时候调用的事件一样.
  • validate: 函数用来验证输入元素的值. 例如为了验证值不为空, 你可以使用:
    validate : CKEDITOR.dialog.validate.notEmpty(ErrorMessage)

    如果域是空的,那么错误消息将在你点击ok按钮的时候弹出.
    内置的验证器包含:
    • regex(regex, msg)
    • notEmpty(msg)
    • integer(msg) //regex: /^\d*$/
    • number(msg) //regex: /^\d*(? :\.\d+)?$/
    • equals(value, msg)
    • notEqual(value, msg)
  • setup: 函数会在父对话框的setupContent方法被调用时执行. 能够被用来初始化一些域的值. 例如当编辑一个文档中已存图片域的时候, 你可能希望初始化该图片的宽度. 这里有个例子:
    onShow : function(){ //onShow function for the dialog definition
        //... other code ...
        var elem= this.getParentEditor().getSelection().getSelectedElement(); //get the current selected element
        this.setupContent(elem);  //this function will call all the setup function for all uiElements
        //... other code...
    },
    contents: [{
        id: 'InfoTab',
        label: 'Info Tab',
        elements:[
        {  //input element for the width
            type: 'text',
            id: 'widthInput',
            label: 'Width',
            labelLayout: 'horizontal',
            setup: function(element){
                this.setValue(element.getAttribute('width'));
            }
        }]
    }]

  • commit: 函数在父对话框的commitContent方法被调用的时候执行. 例如, 它能在ok按钮被按下时变更图片的大小.

上下文菜单

上下文菜单是你在CKEditor中右键所弹出的菜单. 就像CKEditor的其它特征一样,它也很容易被定制. 下面的代码展示了如何增加一个“Code“菜单项的过程:

if(editor.addMenuItems)
{
    editor.addMenuItems(  //have to add menu item first
        {
            insertCode:  //name of the menu item
            {
                label: 'Code',  
                command: 'insertCode',
                group: 'insertCode'  //have to be added in config
            }
        });
}
if(editor.contextMenu)
{
    editor.contextMenu.addListener(function(element, selection)  //function to be run when context menu is displayed
        {
            if(! element || !element.is('pre'))
                    return null;
            return { insertCode: CKEDITOR.TRISTATE_OFF };
        });
    }

前述代码在上下文菜单中增加了额外的insertCode元素. 当右击在非‘pre’元素上时, insertCode菜单将不会显示. 当右击在‘pre’元素上时insertCode菜单将会显示OFF状态.

除了上面的代码, 你依然需要把insertCode组放到config中,这样才能排列菜单显示的顺序. 以下是需要在config.js中增加的代码:

config.menu_groups = 'clipboard,form,tablecell,tablecellproperties,tablerow,tablecolumn,table,anchor,link,image,flash,checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,insertCode';

你会看到insertCode插入在了menu_groups配置的最后面. 最终的结果是:

源码打包

写完所有的代码之后, 就须要把你的代码产品化. 就像前面提到的那样, 原始的代码包含了很多文件,都会被压缩成一些个文件从而降低HTTP请求. 有必要把你的代码和这些文件合并。

打包这些文件实际上是由ckeditor.peck控制的. 把你的源代码文件放到‘ckeditor.js’的列表中, 将会把你的文件添加进去.

而事实上的打包,你须要额外的两个文件ckpackager.exe或ckpackager.jar. 在你下载完这两个文件后,在你的ckeditor根下运行下面的命令:

ckpackager.exe ckeditor.pack

现在你的源码文件将会自动被打包并拷贝到ckeditor的根目录下. 可以开始享用你的插件了!

评论

 
分页导航: