系列文章快速阅读:
富文本编辑器开发系列-1-基础概念
富文本编辑器开发系列2-document.execCommand 的API
富文本编辑器开发系列3-selection
富文本编辑器开发系列4——Range对象
富文本编辑器开发系列5——浏览器Selection API探究
富文本编辑器开发系列6——Range API 探究
富文本编辑器开发系列7——textRange对象详解
富文本编辑器开发系列8——常用DOM API
原生API编写简单富文本编辑器001
原生API编写简单富文本编辑器002
原生API编写简单富文本编辑器003
原生API编写简单富文本编辑器004
原生API编写简单富文本编辑器005
经过前几节的层层递进,我们了解了一个利用原生的contenteditable 属性和 execCommand 方法来实现一个简单版的富文本编辑器,但是目前版本的编辑器虽然功能上基本实现了,但是作为一个应用或者工程,还很低级,它的部分变量和全部方法暴露在全局变量下,并且无法在同一个页面实例化多个互不干扰的编辑器。
本节我们就利用面向对象方法来对该编辑器进行封装设计。
如果每一个编辑器是对象,那么我们所编写的程序一定是一个对象工厂,可以根据传入的配置来生成不同的编辑器实例对象,并且要将生成的编辑器实例插入指定的DOM元素中,生成方式应该类似于下面这种:
var editor = new Editor($el, options);
目前,每一个编辑器实例至少应该包含以下部分:
而为了保证工具条与编辑区不会抢夺焦点,编辑区必须位于一个框架(iframe)内.
而为了完整的封装性,我们需要保证编辑器的各个组件DOM都动态生成,所以我们不能再像前面几节一样,把编辑器的骨架HTML写在html文件中,所以,每个组件都要有自己的UI模板,需要有一个获取模板的方法。
为了更好的扩展性,我们还需要为每一个组件提供显示/隐藏方法。
工具条上的按钮又分以下几种:
这里面,我们又可以抽象出一个新的组件:对话框,同样,它也有自己的模板方法,显示/隐藏方法。
当然,我们还需要一个事件系统,用来为组件(按钮)增加事件监听,并且在组件销毁(隐藏)时移除监听。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ak7EHSqM-1670513320945)(https://gitee.com/hjb2722404/tuchuang/raw/master/img/202211170937952.png)]
class Editor {constructor($el, options) {this.id = options.id || this.createId();this.$el = $el;this.options = options;this.init();}init() {this.editorBar = new EditorBar(this.id, this.options);this.editorContent = new EditorContent(this.id, this.options);this.getTemplate();this.show();this.initEvents();}getTemplate() {const editorBarTpl = this.editorBar.getTemplate();const editorContentTpl = this.editorContent.getTemplate();return `this.id}" editor>${editorBarTpl}${editorContentTpl}`;}show() {document.getElementById('editor-' + this.id).display = 'block';}/*** [initEvents 初始化事件绑定]** @return {[type]} [return description]*/initEvents() {const btns = document.getElementById(this.editorBar.id).getElementsByTagName('button');let btnsArray = [];for(let i=0; ibtnsArray.push(btns[i]);}const that = this;for (let i=0; iconst btn = btnsArray[i];const command = btn.getAttribute('command');btn.onclick = function(e) {switch (command) {case 'fontColor':that.dialog = new editorDialog(that.id);that.colorPicker = new colorPicker(that.id, btn, 'foreColor');that.dialog.setCom(that.colorPicker.getTemplate());that.colorPicker.show();that.dialog.location(command);that.dialog.show();that.colorPicker.initEvents(that);break;case 'backColor':that.dialog = new editorDialog(that.id);that.colorPicker = new colorPicker(that.id, btn, 'backColor');that.dialog.setCom(that.colorPicker.getTemplate());that.colorPicker.show();that.dialog.location(command);that.dialog.show();that.colorPicker.initEvents(that);break;case 'createLink':that.dialog = new editorDialog(that.id);that.linkEditor = new linkEditor(that.id);that.dialog.setCom(that.linkEditor.getTemplate());that.linkEditor.show();that.dialog.location(command);that.dialog.show();that.linkEditor.initEvents(that);break;case 'insertImage':that.dialog = new editorDialog(that.id);that.imageEditor = new imageEditor(that.id);that.dialog.setCom(that.imageEditor.getTemplate());that.imageEditor.show();that.dialog.location(command);that.dialog.show();that.imageEditor.initEvents(that);break;default:that.editor.document.execCommand(command, 'true', '');}};}this.editorBar.events.map((event) => {let el = document.getElementById(event.id);el.addEventListener(event.type, function (e) {console.log(event.key);console.log(e.target.value);that.editor.document.execCommand(event.key, false, e.target.value);});});}
}
class EditorBar {constructor(id, options) {this.id = `editorBar-${id}`;this.options = options;this.init();}init() {this.getTemplate();}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}getTemplate() {const $tpl = this.createEditorBar();return `this.id}" class="editor-toolbar">${$tpl}`;}createEditorBar() {let $tpl ='';for (key in this.options.btns) {if (commandsMap[key].options) {let id = this.id + '-' + key + 'Selector';let customStyleName = commandsMap[key].styleName;$tpl += getSelectTpl(id, commandsMap[key].options, customStyleName);} else {$tpl += ``;}}$tpl += '
';return $tpl;}}
class EditorContent {constructor(id, options) {this.id = `editorContent-${id}`;this.options = options;this.init();}init() {this.getTemplate();}getTemplate() {return ``;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}}
class editorDialog {constructor(id) {this.id = 'editorDialog-' + id;this.editorId = 'editor-' + idthis.init();}getSelf() {return document.getElementById(this.id);}init() {const div = document.createElement('div');div.innerHTML = this.getTemplate();document.getElementById(this.editorId).appendChild(div);}setCom(tpl) {document.getElementById(this.id).innerHTML = tpl;}getTemplate() {return `this.id}">`;}show() {document.getElementById(this.id).style.display = 'block';}hide() {document.getElementById(this.id).style.display = 'none';}location(cmd) {const btns = document.getElementsByClassName(cmd += '-btn');if (btns.length) {const btn = btns[0];const style = {top: (btn.offsetTop + btn.offsetHeight + 15) + 'px',left: btn.offsetLeft + 'px',};const dialog = this.getSelf();dialog.style.top = style.top;dialog.style.left = style.left;}}}
class colorPicker {constructor(id, btn, type) {this.id = 'colorPicker-' + id;this.btn = btn;this.type = type;}getSelf() {return document.getElementById(this.id);}getTemplate() {return `this.id}" />`;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}initEvents(editInstance) {const colorPicker = this.getSelf();const that = this;colorPicker.addEventListener("input", function(event){editInstance.editor.document.execCommand(that.type, 'false', event.target.value);editInstance.dialog.hide();}, false);colorPicker.addEventListener("change", function(event){editInstance.editor.document.execCommand(that.type, 'false', event.target.value);editInstance.dialog.hide();}, false);}}
class linkEditor {constructor(id, btn, type) {this.id = 'linkEditor-' + id;}getSelf(id) {return document.getElementById(id || this.id);}getTemplate() {return `this.id}" />`;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}initEvents(editInstance) {const linkEditorBtn = this.getSelf(this.id + '-okbtn');const linkEditor = this.getSelf();const that = this;linkEditorBtn.addEventListener("click", function(event){editInstance.editor.document.execCommand('createLink', 'false', linkEditor.value);editInstance.dialog.hide();}, false);}}
class imageEditor {constructor(id, btn, type) {this.id = 'imageEditor-' + id;}getSelf(id) {return document.getElementById(id || this.id);}getTemplate() {return `this.id}" />`;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}initEvents(editInstance) {const imageEditorBtn = this.getSelf(this.id + '-okbtn');const imageEditor = this.getSelf();const that = this;imageEditorBtn.addEventListener("click", function(event){editInstance.editor.document.execCommand('insertImage', 'false', imageEditor.value);editInstance.dialog.hide();}, false);}}