轻量级富文本编辑器wangEditor源码结构介绍

时间:2022-09-08 13:21:24

1. 引言

  wangEditor——一款轻量级html富文本编辑器(开源软件)

  从我发布wangEditor到现在,大概有七八个月了,随着近期增加的插入视频,表情,地图这三个功能,目前为止基本的功能已经大体完善了。这期间也修改了几个bug,都是各位网友反映的。至于程序是不是已经很稳定了,我不敢说。毕竟应用的人不是特别多,目前只有几十个关注wangEditor的人在应用。他们会偶尔提出一些bug,不过只要告诉我,我会第一时间解决,至少大家对我修改bug增加功能的速度和态度,还是比较认可的。

  轻量级富文本编辑器wangEditor源码结构介绍

  根据github记载,目前有105个commits,即我已经提交了105次代码更新,这个数量也会继续增加。大家有bug,有需求可以通过QQ群向我提交。

2. 介绍源码结构

  wangEditor.js源码目前2200多行,用书写文字书写博客的方式介绍它的结构,还真不是一件简单的事儿。所以,这里我就长话短说,尽量简单的介绍一下重点,不要搞的太罗嗦,否则大家最后会不耐烦的。

  如果让我自己对这个源码的设计和架构做一个评价的话,我会打70分。它并不是完美的,但是它已经满足了我基本的需求。比方说,我最近新增的几个功能(插入视频,地图,表情)都是通过修改其中的配置项增加上去的,而没有改动源码中的核心部分。开放封闭原则——对扩展开放,对修改封闭,我想我已经基本做到了这一点。

  最后,我分享wangEditor源码设计的目的,为的是让大家给一些意见。提出一些疑问,一些建议,或者我目前还没有意识到的一些问题。总之,我是希望这个软件越做越好。

3. 一个jQuery插件

  wangEditor是一款jQuery插件,也是基于jquery开发的(不理解jquery插件的同学,请自行补课,本文不讲)。定义一个jquery插件其实很简单,wangEditor.js源码的最后几十行定义了。

//------------------------------------生成jquery插件------------------------------------
$.fn.extend({
/*
* options: {
* $initContent: $elem, //配置要初始化内容
* menuConfig: [...], //配置要显示的菜单(menuConfig会覆盖掉hideMenuConfig)
* onchange: function(){...}, //配置onchange事件,
* uploadUrl: 'string' //图片上传的地址
* }
*/
'wangEditor': function(options){
if(this[0].nodeName !== 'TEXTAREA'){
//只支持textarea
alert('wangEditor提示:请使用textarea扩展富文本框。详情可参见作者的demo.html');
return;
} var options = options || {},
menuConfig = options.menuConfig,
$initContent = options.$initContent || $('<p><br/></p>'),
onchange = options.onchange,
uploadUrl = options.uploadUrl; //获取editor对象
var editor = $E(this, $initContent, menuConfig, onchange, uploadUrl); //渲染editor,并隐藏textarea
this.before(editor.$editorContainer);
this.hide(); //页面刚加载时,初始化selection
editor.initSelection(); return editor;
}
});

  以上代码其实都很简单,就是接受一些配置项然后调用一个 $E 函数,返回一个 editor 对象,最后渲染到页面上。最关键的就是 $E 函数这一句话。

//获取editor对象
var editor = $E(this, $initContent, menuConfig, onchange, uploadUrl);

  大家看这种方式是不是有点 var $div = $('div'); 的意思?——对了,这的设计我就是模仿着jquery来的。

4. 仿jQuery的对象化设计

  上文中提到的 $E 函数是这样定义的。

//全局的构造函数
$E = function($textarea, $initContent, menuConfig, onchange, uploadUrl){
return new $E.fn.init($textarea, $initContent, menuConfig, onchange, uploadUrl);
};

  如上代码,其实构造函数是 $E.fn.init 。$E 只不过是一个入口,返回这个构造函数 new 出来的一个对象。

  那么 $E.fn 是什么呢? ——它是 $E.prototype 的简写而已——好多js系统都喜欢这么干,我也就随着高大上一些啦!

    //prototype简写为fn
$E.fn = $E.prototype;

  既然 $E.fn.init 是构造函数,那么它 new 出来的对象(即上文中的 editor)的原型要指向:$E.fn.init.prototype ,这样岂不是太长?不如来个简单一些的,将原型指向 $E.fn 吧。

$E.fn.init.prototype = $E.fn;

  到了这里,没有看过jquery设计或者源码的人,一定觉得绕晕了——那是很正常的。我一开始接触jquery时,也是绕不过来。不过后来看多了,再后来自己用起来,还真觉得挺简单易用。大家在做自己的js代码时候,也不放试一试!

5. 工具函数 & 对象函数

  其实这里也是仿照jquery来设计的。在jquery中,函数都是 $ 的属性,例如 $.trim() ,对象函数都是 $.fn 的属性,例如 $('div').html() 的 html 方法就是 $.fn.html 定义的。

  在wangEditor.js也一样。有许多工具函数(例如log输出,引号转译,url安全性检查等)都是 $E 的属性;许多对象函数(例如text,append,change等)都是 $E.fn 的属性。

  为什么把函数定义在 $E.fn 上即可成为对象函数呢?——因为构造函数是 $E.fn.init ,而 $E.fn.init.prototype = $E.fn;  不知道大家明白了没有?

6. menu配置项

  wangEditor目前有28个功能菜单,不可能为每一个菜单都写一遍执行代码。因为我们是面向对象的编程,我们是遵循“开放封闭原则”的设计。

  还别说,在第一个版本中,我还真就是一个菜单写一遍执行代码,后来发现那样根本无法扩展。现在我的宗旨是:写一个菜单处理引擎(包括菜单初始化,页面弹出关闭,命令执行),菜单的扩展通过配置项实现。这个菜单处理引擎今天就不在本文讲解了,那块挺麻烦的,有时间再通过视频的方式跟大家分享吧。

  首先,我们需要把所有的菜单归归类,否则如何确定配置项啊?我把所有的菜单分为4类:

  • command类型:点击按钮即可执行命令,如“粗体”,“下划线”
  • dropMenu类型:点击按钮弹出下拉menu,再选择命令。如“字体”,“字号”
  • dropPanel类型:点击按钮弹出panel,再选择命令。如“背景色”,“表情”
  • modal类型:点击按钮弹出对话框,需要填写内容,再执行命令。如“插入图片”,“插入地图位置”

  下面是一个菜单按钮配置时的说明:

'menuId-1': {
'title': (字符串,必须)标题,
'type':(字符串,必须)类型,可以是 btn / dropMenu / dropPanel / modal,
'txt': (字符串,必须)fontAwesome字体样式,例如 'fa fa-head',
'style': (字符串,可选)设置btn的样式
'hotKey':(字符串,可选)快捷键,如'ctrl + b', 'ctrl,shift + i', 'alt,meta + y'等,支持 ctrl, shift, alt, meta 四个功能键(只有type===btn才有效)
'command':(字符串)document.execCommand的命令名,如'fontName';也可以是自定义的命令名,如“撤销”、“插入表格”按钮(type===modal时,command无效),
'dropMenu': ($ul,可选)type===dropMenu时,要返回一个$ul,作为下拉菜单,
'dropPanel':($div,可选)type===dropPanel是,要返回一个$div,作为弹出框
'modal':($div,可选)type===modal是,要返回一个$div,作为弹出框,
'callback':(函数,可选)回调函数,
},

  再配置一个菜单时,必须要遵守这个规则,否则解析引擎无法正确解析配置项。在此,为每个类型的菜单按钮,粘贴几个简单的配置项:

'fontFamily': {
'title': '字体',
'type': 'dropMenu',
'txt': 'icon-wangEditor-font',
'command': 'fontName ',
'dropMenu': function(){
var arr = [],
//注意,此处commandValue必填项,否则程序不会跟踪
temp = '<li><a href="#" commandValue="${value}" style="font-family:${family};">${txt}</a></li>',
$ul; $.each($E.styleConfig.fontFamilyOptions, function(key, value){
arr.push(
temp.replace('${value}', value)
.replace('${family}', value)
.replace('${txt}', value)
);
});
$ul = $( $E.htmlTemplates.dropMenu.replace('{content}', arr.join('')) );
return $ul;
},
'callback': function(editor){
//console.log(editor);
}
},
'bold': {
'title': '加粗',
'type': 'btn',
'hotKey': 'ctrl + b',
'txt':'icon-wangEditor-bold',
'command': 'bold',
'callback': function(editor){
//console.log(editor);
}
},
'foreColor': {
'title': '前景色',
'type': 'dropPanel',
'txt': 'icon-wangEditor-pencil', //如果要颜色: 'txt': 'fa fa-pencil|color:#4a7db1'
'style': 'color:blue;',
'command': 'foreColor',
'dropPanel': function(){
var arr = [],
//注意,此处commandValue必填项,否则程序不会跟踪
temp = '<a href="#" commandValue="${value}" style="background-color:${color};" title="${txt}" class="forColorItem">&nbsp;</a>',
$panel; $.each($E.styleConfig.colorOptions, function(key, value){
var floatItem = temp.replace('${value}', key)
.replace('${color}', key)
.replace('${txt}', value);
arr.push(
$E.htmlTemplates.dropPanel_floatItem.replace('{content}', floatItem)
);
});
$panel = $(
$E.htmlTemplates.dropPanel.replace('{content}', arr.join(''))
);
return $panel;
}
},
'createLink': {
'title': '插入链接',
'type': 'modal',
'txt': 'icon-wangEditor-link',
'modal': function (editor) {
var urlTxtId = $E.getUniqeId(),
titleTxtId = $E.getUniqeId(),
blankCheckId = $E.getUniqeId(),
btnId = $E.getUniqeId();
content = '链接:<input id="' + urlTxtId + '" type="text" style="width:300px;"/><br />' +
'标题:<input id="' + titleTxtId + '" type="text" style="width:300px;"/><br />' +
'新窗口:<input id="' + blankCheckId + '" type="checkbox" checked="checked"/><br />' +
'<button id="' + btnId + '" type="button" class="wangEditor-modal-btn">插入链接</button>',
$link_modal = $(
$E.htmlTemplates.modalSmall.replace('{content}', content)
);
$link_modal.find('#' + btnId).click(function(e){
//注意,该方法中的 $link_modal 不要跟其他modal中的变量名重复!!否则程序会混淆
//具体原因还未查证??? var url = $.trim($('#' + urlTxtId).val()),
title = $.trim($('#' + titleTxtId).val()),
isBlank = $('#' + blankCheckId).is(':checked'),
link_callback = function(){
//create link callback
$('#' + urlTxtId).val('');
$('#' + titleTxtId).val('');
}; if(url !== ''){
//xss过滤
if($E.filterXSSForUrl(url) === false){
alert('您的输入内容有不安全字符,请重新输入!')
return;
}
if(title === '' && !isBlank){
editor.command(e, 'createLink', url, link_callback);
}else{
editor.command(e, 'customCreateLink', {'url':url, 'title':title, 'isBlank':isBlank}, link_callback);
}
}
}); return $link_modal;
}
}

7. 总结

  以上只是一些重点部分,其他的还有很多。例如富文本编辑器的核心技术:execCommand,如何支持IE6的fontIcon,菜单按钮如何解析,以及表情,地图是如何实现的。时间有限,就不一一说明了,大家有兴趣可以去看源码。

  最后还是欢迎大家多多指正!

-------------------------------------------------------------------------------------------------------------

欢迎关注我的教程:从设计到模式深入理解javascript原型和闭包系列》《css知多少》《微软petshop4.0源码解读视频》《json2.js源码解读视频

也欢迎关注我的开源项目——wangEditor,轻量化web富文本编辑器

-------------------------------------------------------------------------------------------------------------

轻量级富文本编辑器wangEditor源码结构介绍的更多相关文章

  1. 简易富文本编辑器bootstrap-wysiwyg源码注释

    好久没写随笔了,因为最近比较忙,小公司基本都是一个前端干所有属于和部分不属于前端的事情,所以就没空弄了,即使想分享,也因为没有时间和精力就搁置了. 这周周六日休息,正好时间比较充裕(ps:目前处在单休 ...

  2. 轻量级富文本编辑器wangEditor

    开发公司一个系统的时候需要一个富文本编辑器,找了几个,最后选择这个,蛮不错的. 百度搜索wangEditor,进入官网根据所介绍的使用进行开发就可以了,很不错的一个工具.

  3. 前端轻量级、简单、易用的富文本编辑器 wangEditor 的基本用法

    1.富文本编辑器市面上有很多,但是综合考虑之后wangEditor是最易用的框架,推荐使用 首先进入官网 http://www.wangeditor.com 基本是2中方式引入: 使用CDN://un ...

  4. 富文本编辑器 - wangEditor 上传图片

    效果: . 项目结构图: wangEditor-upload-img.html代码: <html> <head> <title>wangEditor-图片上传&lt ...

  5. 富文本编辑器&lpar;wangEditor&rpar;

    近期在产品的开发工作中遇到要使用富文本编辑器的地方.于是对比了几款编辑器, 最后选择了wangEditor. 优点:轻量.简洁.界面美观.文档齐全.   缺点: 相较于百度ueditor等编辑器功能较 ...

  6. PostgreSQL9&period;2&period;4内核源码结构介绍

    PostgreSQL的源代码可以随意获得,其开源协议也允许研究者任意修改,这里介绍一下PostgreSQL的源码结构以及部分实现机制.下载PostgreSQL源代码并减压后,其一级目录结构如下图: P ...

  7. QT&lowbar;文本编辑器&lowbar;源码下载

    源码下载: 链接: http://pan.baidu.com/s/1c21EVRy 密码: qub8 实现主要的功能有:新建,打开,保存,另存为,查找(查找的时候需要先将光标放到最下面位置才能查全,不 ...

  8. 富文本编辑器 - wangEditor 插入代码

    效果: 项目结构: 注意事项: highlightJS 代码高亮插件,wangEditor 本身就是集成的highlightJS代码高亮插件. 在wangEditor-1.3.12.js里找到var ...

  9. 富文本编辑器 wangEditor&period;js

    1.引用 wangEditor 相关js  和 css 下载地址:https://files.cnblogs.com/files/kitty-blog/WangEditor.zip 3.页面: &lt ...

随机推荐

  1. 非技术1-学期总结&amp&semi;ending 2016

    好久好久没写博客了,感觉动力都不足了--12月只发了一篇博客,好惭愧-- 今天是2016年最后一天,怎么能不写点东西呢!! 学期总结 大学中最关键一年的第一个学期,共4个月.前20天在学网络方面的,当 ...

  2. ExtJs 获取Dom对象

    对象指页面上的某一部分,如:Input等.我觉得在EXT JS中会有三类基本对象,htmlelement , EXT.Element和CompositeElement .分别解释一下: htmlele ...

  3. 白话Https

    本文试图以通俗易通的方式介绍Https的工作原理,不纠结具体的术语,不考证严格的流程.我相信弄懂了原理之后,到了具体操作和实现的时候,方向就不会错,然后条条大路通罗马.阅读文本需要提前大致了解对称加密 ...

  4. Tomcat 6 JNDI数据源详解

    数据库连接池这个概念应该都不陌生,在Java中连接池也就是数据库的连接池,它是一种采用连接复用的思想避免多次连接造成资源的浪费机制. 最常见的连接池就是DBCP和C30P了,在tomcat中默认使用的 ...

  5. Python version 2&period;7 required&comma; which was not found in the registry

    http://blog.csdn.net/zdnlp/article/details/12171687

  6. &lbrack;转&rsqb; MovieClip转Bitmap方法

    package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Loader; ...

  7. linux(ubuntu)安装时遇到的问题

    window环境下安装linux虚拟机=时,由于在初始系统语言选择了中文,当linux虚拟机安装成功后, 按[Ctrl + alt +f1~f6]任一一键都行,进入到命令行模式,这时你会发现,哎,我的 ...

  8. F12调试打开时,出现很多多余内容问题解决

    关闭vs2013的browserlink功能即可: <appSettings> <add key="vs:EnableBrowserLink" value=&qu ...

  9. c&num; 调用EXCEL在VS上能正常运行,部署在IIS上不能实现,在VS中运行页面和发布之后在IIS中运行的区别

    发现一篇文章,很好,解决了这个问题:感谢原博主!特此做个笔记. 地址:http://www.cnblogs.com/zhongxinWang/p/3275154.html 发布在IIS上的Web程序, ...

  10. &lbrack;NOI2012&rsqb;

    来自FallDream的博客,未经允许,请勿转载,谢谢. 一天一套noi 简直了.... 昨天勉强做完了noi2011 今天教练又丢出来一套noi2012  去掉提答还有5题 勉强做了3题  先占个坑 ...