网上好像几乎没有研究ueditor源码的文章,原因可能是ueditor源码太复杂了,接近浏览器代码和word/excel源码。本文分析ueditor源码整体流程逻辑以及重点难点细节。
首先,编辑器是如何实现输入的?本人开始始终不得其解,在源码找不到输入事件绑定的处理函数,后来在白云峰同学的提醒下才顿悟,整个iframe网页就相当于是一个
页面调用ueditor:
// iframe的container元素var editor = UE.getEditor('editor');
多次调用可以多实例运行,每个实例都是单独的,编辑器实例保存在UE实例中,从UE.instants[]也可以获取到每个编辑器实例,0就是第一个实例,以此类推,
因此可以不用变量引用编辑器实例:
UE.getEditor('editor');
setTimeout(function(){
UE.instants.ueditorInstant0.setContent('
},1000);
执行ueditor文件之后产生三个全局对象:
UEDITORUI - 所有工具按钮插件的api
UE - api入口
UEDITOR_CONFIG - 配置数据
先看ueditor的全局api接口:
window.UE = baidu.editor = window.UE || {}; // UE实例提供ueditor的入口接口,也就是api入口,调用UE的方法才创建真正的编辑器实例
var Editor = UE.Editor = function (options) { // 这是编辑器构造函数
/* 尝试异步加载后台配置 */
me.loadServerConfig(); //所谓异步加载就是用js构造<tag src=url加载文件,正常是直接在网页写<tag src=url 加载文件
UE.Editor.prototype.loadServerConfig = function(){
//用ajax请求http://localhost/plugins/ueditor/ueditor/php/controller.php?action=config&&noCache=1525847581688,返回后台php配置参数,主要是涉及upload的配置参数,其实前端的配置数据可以直接写一个js文件直接从网页写<script src=加载。
element.onload = element.onreadystatechange = function () { // 这是构造<tag src=url加载文件之后再通过onload事件触发执行一个回调
}
}
if(!utils.isEmptyObject(UE.I18N)){ // i18n是语言国际化,就是多语言包
//修改默认的语言类型
me.options.lang = checkCurLang(UE.I18N);
UE.plugin.load(me);
langReadied(me);
}else{
utils.loadFile(document, {
src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js", //加载zh-cn.js中英文对照语言包
}, function () { // 这个匿名函数回调会执行一次,具体是在哪一次加载文件时执行的不清楚
UE.plugin.load(me); // 这是加载内部插件,执行ueditor文件时会执行UE.plugin.register()注册所有的插件,然后在这里加载所有的插件
UE.plugin = function(){
var _plugins = {};
return {
register : function(pluginName,fn,oldOptionName,afterDisabled){
_plugins[pluginName] = {
optionName : oldOptionName || pluginName,
execFn : fn,
//当插件被禁用时执行
afterDisabled : afterDisabled
}
},
load : function(editor){ // 这就是load()函数,加载插件,所有的插件在执行ueditor文件时已经注册
utils.each(_plugins,function(plugin){ //_plugins是执行register方法产生的插件集合
var _export = plugin.execFn.call(editor); //execFn是plugin的构造函数,执行构造函数产生plugin object {}
utils.each(_export,function(v,k){ // 针对plugin的每一个属性处理一次,把plugin的方法函数保存到编辑器实例中
switch(k.toLowerCase()){
case 'commands':
utils.each(v,function(execFn,execName){
editor.commands[execName] = execFn //editor.commands{}含所有的按钮操作指令
});
});
utils.each(UE.plugins,function(plugin){ // 插件好像分两部分有两种写法,这是针对旧写法插件进行处理
plugin.call(editor); //执行插件构造函数
});
langReadied(me);
});
//loadFile代码:
return function (doc, obj, fn) { // fn就是传入的匿名函数,用<script src=url加载执行js文件之后再执行这个回调函数
doc.getElementsByTagName("head")[0].appendChild(element); // 构造<script src=加载执行zh-cn.js文件
}
}
UE.instants['ueditorInstant' + me.uid] = me; // 如果多实例运行,均存储在UE中,每个实例按id区分,多实例运行可以利用UE.instants[id]找实例,把每个编辑器实例保存在自己定义的全局对象中也可以
}
Editor.prototype = {
render: function (container) { // container是iframe holder,之前已经构造iframe相关的几个div插入网页,render方法构造iframe代码并且把iframe插入网页生效
var me = this,
options = me.options, // options是实例里面的参数,包含config参数
var html = 'iframe里面的html代码';
container.appendChild(domUtils.createElement(document, 'iframe', { // 插入iframe,并执行以下js代码
editor = window.parent.UE.instants['ueditorInstant0']; // ueditor实例保存在iframe的父窗口也就是当前网页窗口
editor._setup(document);
}));
},
_setup: function (doc) {
doc.body.contentEditable = true; // iframe html相当于一个input
}
}
先建立一个UE实例放在全局,入口初始化方法是getEditor。
再看编辑器初始化入口:
UE.getEditor = function (id, opt) {
var editor = instances[id];
if (!editor) {
editor = instances[id] = new UE.ui.Editor(opt); // UE是api入口实例,editor是编辑器实例
editor.render(id); //执行新的render,构造几个/几层container元素,再执行old render,构造iframe代码插入网页
}
return editor;
};
UE.ui.Editor = function (options) {
var editor = new UE.Editor(options); // 这是真正的编辑器实例
var oldRender = editor.render; // UE.editor的render方法(构造iframe插入网页)
editor.render = function (holder) { // 重新构造一个render,构造几个容器元素,然后再调old render构造iframe
utils.domReady(function () { //事件触发异步调度执行
editor.langIsReady ? renderUI() : editor.addListener("langReady", renderUI);
function renderUI() { //事件触发异步调度执行
new EditorUI(editor.options); // 没有接收实例,在其它程序位置不能引用这个实例,在实例的方法中用this引用实例,在事件handler中引用实例(实例“复制”到handler方法中),这是创建实例后如何使用实例的高级方法
function EditorUI(options) {
this.initOptions(options);
UIBase.prototype = {
initOptions:function (options) {
//把options复制到EditorUI实例中
}
this.initEditorUI();
EditorUI.prototype = {
initEditorUI:function () {
//用addeventlistener绑定鼠标操作事件和处理函数
}
}
var newDiv = document.createElement('div'); //在这里创建div插入网页替换