轻量级富文本编辑器wangEditor源码结构介绍
1. 引言
wangEditor——一款轻量级html富文本编辑器(开源软件)
- 网站:http://www.wangeditor.com/
- demo演示:http://www.wangeditor.com/wangEditor/demo.html
- 下载(github):https://github.com/wangfupeng1988/wangEditor
- QQ群:164999061
从我发布wangEditor到现在,大概有七八个月了,随着近期增加的插入视频,表情,地图这三个功能,目前为止基本的功能已经大体完善了。这期间也修改了几个bug,都是各位网友反映的。至于程序是不是已经很稳定了,我不敢说。毕竟应用的人不是特别多,目前只有几十个关注wangEditor的人在应用。他们会偶尔提出一些bug,不过只要告诉我,我会第一时间解决,至少大家对我修改bug增加功能的速度和态度,还是比较认可的。
根据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"> </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,菜单按钮如何解析,以及表情,地图是如何实现的。时间有限,就不一一说明了,大家有兴趣可以去看源码。
最后还是欢迎大家多多指正!
- 网站:http://www.wangeditor.com/
- demo演示:http://www.wangeditor.com/wangEditor/demo.html
- 下载(github):https://github.com/wangfupeng1988/wangEditor
- QQ群:164999061
-------------------------------------------------------------------------------------------------------------
欢迎关注我的教程:《从设计到模式》《深入理解javascript原型和闭包系列》《css知多少》《微软petshop4.0源码解读视频》《json2.js源码解读视频》
也欢迎关注我的开源项目——wangEditor,轻量化web富文本编辑器
-------------------------------------------------------------------------------------------------------------
轻量级富文本编辑器wangEditor源码结构介绍的更多相关文章
- 简易富文本编辑器bootstrap-wysiwyg源码注释
好久没写随笔了,因为最近比较忙,小公司基本都是一个前端干所有属于和部分不属于前端的事情,所以就没空弄了,即使想分享,也因为没有时间和精力就搁置了. 这周周六日休息,正好时间比较充裕(ps:目前处在单休 ...
- 轻量级富文本编辑器wangEditor
开发公司一个系统的时候需要一个富文本编辑器,找了几个,最后选择这个,蛮不错的. 百度搜索wangEditor,进入官网根据所介绍的使用进行开发就可以了,很不错的一个工具.
- 前端轻量级、简单、易用的富文本编辑器 wangEditor 的基本用法
1.富文本编辑器市面上有很多,但是综合考虑之后wangEditor是最易用的框架,推荐使用 首先进入官网 http://www.wangeditor.com 基本是2中方式引入: 使用CDN://un ...
- 富文本编辑器 - wangEditor 上传图片
效果: . 项目结构图: wangEditor-upload-img.html代码: <html> <head> <title>wangEditor-图片上传< ...
- 富文本编辑器(wangEditor)
近期在产品的开发工作中遇到要使用富文本编辑器的地方.于是对比了几款编辑器, 最后选择了wangEditor. 优点:轻量.简洁.界面美观.文档齐全. 缺点: 相较于百度ueditor等编辑器功能较 ...
- PostgreSQL9.2.4内核源码结构介绍
PostgreSQL的源代码可以随意获得,其开源协议也允许研究者任意修改,这里介绍一下PostgreSQL的源码结构以及部分实现机制.下载PostgreSQL源代码并减压后,其一级目录结构如下图: P ...
- QT_文本编辑器_源码下载
源码下载: 链接: http://pan.baidu.com/s/1c21EVRy 密码: qub8 实现主要的功能有:新建,打开,保存,另存为,查找(查找的时候需要先将光标放到最下面位置才能查全,不 ...
- 富文本编辑器 - wangEditor 插入代码
效果: 项目结构: 注意事项: highlightJS 代码高亮插件,wangEditor 本身就是集成的highlightJS代码高亮插件. 在wangEditor-1.3.12.js里找到var ...
- 富文本编辑器 wangEditor.js
1.引用 wangEditor 相关js 和 css 下载地址:https://files.cnblogs.com/files/kitty-blog/WangEditor.zip 3.页面: < ...
随机推荐
- leancloud 用户登录(调用API) 教程
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; ...
- C++中“类”相关知识点汇总
一:类中默认的成员函数 一个空的class在C++编译器处理过后就不再为空,编译器会自动地为我们声明一些member function,如果你写 class Empty{}; 就相当于: class ...
- maven -- 学习笔记(四)实现在Eclipse用maven搭建springmvc项目(附构建步骤和详细实现代码)
Learn from:http://www.cnblogs.com/fangjins/archive/2012/05/06/2485459.html,感谢楼主的分享,才有下面的这篇学习小结 一.环境准 ...
- 关于AJAX跨域调用ASP.NET MVC或者WebAPI服务的问题及解决方案
作者:陈希章 时间:2014-7-3 问题描述 当跨域(cross domain)调用ASP.NET MVC或者ASP.NET Web API编写的服务时,会发生无法访问的情况. 重现方式 使用模 ...
- Hessian 原理分析--转
原文地址:http://blog.csdn.net/zhtang0526/article/details/4788879 一. 远程通讯协议的基本原理 网络通信需要做的就是将流从一台计算机传 ...
- Sql Server,如何去含有非数字的nvarchar,再转为为数值
菜鸟一枚,今天做项目时,突然遇到一个问题: 在数据库中中存放的nvarchar类型的“时间”,例如:‘08:00’,而我需要进行时间的范围比较,这时就想到了将nvarchar类型转化为int类型. 这 ...
- CSS魔法堂:再次认识font
一.前言 文字承载着站点内涵,而良好的字体.排版则为用户提供舒适的阅读体验.本文打算对字体稍微深入一下子网页字体的内容,若有纰漏请大家指正,谢谢! 目录一坨: 二, 字体分类 1. 衬线体(Serif ...
- 从设计到开发,硅谷技术专家教你做“声控”APP
编者:本文为携程机票研发部技术专家祁一鸣在携程技术微分享中的分享内容,关注携程技术中心微信公号ctriptech,获知更多一手干货. [携程技术微分享]是携程技术中心推出的线上公开分享课程,每月1-2 ...
- angularJS配合bootstrap动态加载弹出提示内容
1.bootstrp的弹出提示 bootstrap已经帮我们封装了非常好用的弹出提示Popover. http://v3.bootcss.com/javascript/#popovers 2.自定义p ...
- 【转】ASP.NET"正在中止线程"错误原因
最近做的系统中老出现的一些问题不太明白,在使用 Response.End.Response.Redirect 或 Server.Transfer 时出现 ThreadAbortException , ...