我们看一下CKEditor4的编辑器内容的设置和获取过程,也就是setData和getData过程,下面我们就来聊聊关于如何在项目中添加ckeditor?接下来我们就一起去了解一下吧!

如何在项目中添加ckeditor(CKEditor系列五编辑器内容的设置和获取过程)

如何在项目中添加ckeditor

我们看一下CKEditor4的编辑器内容的设置和获取过程,也就是setData和getData过程。

我们在调用editor.setData的时候,调用的就是core/editor.js里面的setData方法。

// src/core/editor.js setData: function( data, options, internal ) { var fireSnapshot = true, // Backward compatibility. callback = options, eventData; if ( options && typeof options == 'object' ) { internal = options.internal; callback = options.callback; fireSnapshot = !options.noSnapshot; } if ( !internal && fireSnapshot ) this.fire( 'saveSnapshot' ); if ( callback || !internal ) { this.once( 'dataReady', function( evt ) { if ( !internal && fireSnapshot ) this.fire( 'saveSnapshot' ); if ( callback ) callback.call( evt.editor ); } ); } // Fire "setData" so data manipulation may happen. eventData = { dataValue: data }; !internal && this.fire( 'setData', eventData ); this._.data = eventData.dataValue; !internal && this.fire( 'afterSetData', eventData ); },

我们可以看到里面的set过程实际是分三步

  1. 判断是否需要saveSnapshot
  2. 判断是否需要触发setData事件
  3. 判断是否需要触发afterSetData事件
setData之saveSnapshot

saveSnapshot主要是方便撤销操作的

// src/plugins/undo.plugin.js // Save snapshots before doing custom changes. editor.on( 'saveSnapshot', function( evt ) { undoManager.save( evt.data && evt.data.contentOnly ); } );

setData之setData

我们接着看setData事件的处理

src/core/section.js editor.on( 'setData', function() { // Invalidate locked selection when unloading DOM. // (https://dev.ckeditor.com/ticket/9521, https://dev.ckeditor.com/ticket/5217#comment:32 and https://dev.ckeditor.com/ticket/11500#comment:11) editor.unlockSelection(); // Webkit's selection will mess up after the data loading. if ( CKEDITOR.env.webkit ) clearSelection(); } );

我们可以看到,它做的工作主要是解锁选区,看来实际做工作的还不是setData啊,它算是一个setData的准备工作可能更合适些。

setData之afterSetData

// src/core/editable.js this.attachListener( editor, 'afterSetData', function() { this.setData( editor.getData( 1 ) ); }, this );

没错,这里又有个一个setData和getData。。。原来他们才是真正的setData和getData啊。

// src/core/editable.js /** * @see CKEDITOR.editor#setData */ setData: function( data, isSnapshot ) { if ( !isSnapshot ) data = this.editor.dataProcessor.toHtml( data ); this.setHtml( data ); this.fixInitialSelection(); // Editable is ready after first setData. if ( this.status == 'unloaded' ) this.status = 'ready'; this.editor.fire( 'dataReady' ); }, /** * @see CKEDITOR.editor#getData */ getData: function( isSnapshot ) { var data = this.getHtml(); if ( !isSnapshot ) data = this.editor.dataProcessor.toDataFormat( data ); return data; },

setHtml和getHtml本质就是原生node的innerHTML,所以setData和getData的过程其实就是 this.editor.dataProcessor.toHtml和this.editor.dataProcessor.toDataFormat的过程,这个两个方法哪来的?它们都源自dataProcessor,它是在编辑器初始化的时候赋值的。

dataProcessor

// src/core/editor.js // Various other core components that read editor configuration. function initComponents( editor ) { // Documented in dataprocessor.js. editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor ); // Set activeFilter directly to avoid firing event. editor.filter = editor.activeFilter = new CKEDITOR.filter( editor ); loadSkin( editor ); }

dataProcessor的两个具体方法如下

// src/core/dataProcessor.js toHtml: function( data, options, fixForBody, dontFilter ) { var editor = this.editor, context, filter, enterMode, protectedWhitespaces; // Typeof null == 'object', so check truthiness of options too. if ( options && typeof options == 'object' ) { context = options.context; fixForBody = options.fixForBody; dontFilter = options.dontFilter; filter = options.filter; enterMode = options.enterMode; protectedWhitespaces = options.protectedWhitespaces; } // Backward compatibility. Since CKEDITOR 4.3.0 every option was a separate argument. else { context = options; } // Fall back to the editable as context if not specified. if ( !context && context !== null ) context = editor.editable().getName(); return editor.fire( 'toHtml', { dataValue: data, context: context, fixForBody: fixForBody, dontFilter: dontFilter, filter: filter || editor.filter, enterMode: enterMode || editor.enterMode, protectedWhitespaces: protectedWhitespaces } ).dataValue; }, toDataFormat: function( html, options ) { var context, filter, enterMode; // Do not shorten this to `options && options.xxx`, because // falsy `options` will be passed instead of undefined. if ( options ) { context = options.context; filter = options.filter; enterMode = options.enterMode; } // Fall back to the editable as context if not specified. if ( !context && context !== null ) context = this.editor.editable().getName(); return this.editor.fire( 'toDataFormat', { dataValue: html, filter: filter || this.editor.filter, context: context, enterMode: enterMode || this.editor.enterMode } ).dataValue; },

这两个方法的具体实现被化成对两个(toHtml和toDataFormat)事件的处理逻辑了。

dataProcessor之toHtml

这两个事件有哪些回调呢,先看toHtml。

// src/core/dataProcessor.js editor.on( 'toHtml', function( evt ) { var evtData = evt.data, data = evtData.dataValue, fixBodyTag; // Before we start protecting markup, make sure there are no externally injected // protection keywords. data = removeReservedKeywords( data ); // The source data is already HTML, but we need to clean // it up and apply the filter. data = protectSource( data, editor ); // Protect content of textareas. (https://dev.ckeditor.com/ticket/9995) // Do this before protecting attributes to avoid breaking: // <textarea><img src="..." /></textarea> data = protectElements( data, protectTextareaRegex ); // Before anything, we must protect the URL attributes as the // browser may changing them when setting the innerHTML later in // the code. data = protectAttributes( data ); // Protect elements than can't be set inside a DIV. E.g. IE removes // style tags from innerHTML. (https://dev.ckeditor.com/ticket/3710) data = protectElements( data, protectElementsRegex ); // Certain elements has problem to go through DOM operation, protect // them by prefixing 'cke' namespace. (https://dev.ckeditor.com/ticket/3591) data = protectElementsNames( data ); // All none-IE browsers ignore self-closed custom elements, // protecting them into open-close. (https://dev.ckeditor.com/ticket/3591) data = protectSelfClosingElements( data ); // Compensate one leading line break after <pre> open as browsers // eat it up. (https://dev.ckeditor.com/ticket/5789) data = protectPreFormatted( data ); // There are attributes which may execute JavaScript code inside fixBin. // Encode them greedily. They will be unprotected right after getting HTML from fixBin. (https://dev.ckeditor.com/ticket/10) data = protectInsecureAttributes( data ); var fixBin = evtData.context || editor.editable().getName(), isPre; // Old IEs loose formats when load html into <pre>. if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && fixBin == 'pre' ) { fixBin = 'div'; data = '<pre>' data '</pre>'; isPre = 1; } // Call the browser to help us fixing a possibly invalid HTML // structure. var el = editor.document.createElement( fixBin ); // Add fake character to workaround IE comments bug. (https://dev.ckeditor.com/ticket/3801) el.setHtml( 'a' data ); data = el.getHtml().substr( 1 ); // Restore shortly protected attribute names. data = data.replace( new RegExp( 'data-cke-' CKEDITOR.rnd '-', 'ig' ), '' ); isPre && ( data = data.replace( /^<pre>|<\/pre>$/gi, '' ) ); // Unprotect "some" of the protected elements at this point. data = unprotectElementNames( data ); data = unprotectElements( data ); // Restore the comments that have been protected, in this way they // can be properly filtered. data = unprotectRealComments( data ); if ( evtData.fixForBody === false ) { fixBodyTag = false; } else { fixBodyTag = getFixBodyTag( evtData.enterMode, editor.config.autoParagraph ); } // Now use our parser to make further fixes to the structure, as // well as apply the filter. data = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, fixBodyTag ); // The empty root element needs to be fixed by adding 'p' or 'div' into it. // This avoids the need to create that element on the first focus (https://dev.ckeditor.com/ticket/12630). if ( fixBodyTag ) { fixEmptyRoot( data, fixBodyTag ); } evtData.dataValue = data; }, null, null, 5 ); // Filter incoming "data". // Add element filter before htmlDataProcessor.dataFilter when purifying input data to correct html. editor.on( 'toHtml', function( evt ) { if ( evt.data.filter.applyTo( evt.data.dataValue, true, evt.data.dontFilter, evt.data.enterMode ) ) editor.fire( 'dataFiltered' ); }, null, null, 6 ); editor.on( 'toHtml', function( evt ) { evt.data.dataValue.filterChildren( that.dataFilter, true ); }, null, null, 10 ); editor.on( 'toHtml', function( evt ) { var evtData = evt.data, data = evtData.dataValue, writer = new CKEDITOR.htmlParser.basicWriter(); data.writeChildrenHtml( writer ); data = writer.getHtml( true ); // Protect the real comments again. evtData.dataValue = protectRealComments( data ); }, null, null, 15 );

我们可以看到这些回调里面最多的几个单词就是protectfilter,它们主要也是做这些工作。

dataProcessor之toDataFormat

再看看toDataFormat的回调

// src/core/dataProcessor.js editor.on( 'toDataFormat', function( evt ) { var data = evt.data.dataValue; // https://dev.ckeditor.com/ticket/10854 - we need to strip leading blockless <br> which FF adds // automatically when editable contains only non-editable content. // We do that for every browser (so it's a constant behavior) and // not in BR mode, in which chance of valid leading blockless <br> is higher. if ( evt.data.enterMode != CKEDITOR.ENTER_BR ) data = data.replace( /^<br *\/?>/i, '' ); evt.data.dataValue = CKEDITOR.htmlParser.fragment.fromHtml( data, evt.data.context, getFixBodyTag( evt.data.enterMode, editor.config.autoParagraph ) ); }, null, null, 5 ); editor.on( 'toDataFormat', function( evt ) { evt.data.dataValue.filterChildren( that.htmlFilter, true ); }, null, null, 10 ); // Transform outcoming "data". // Add element filter after htmlDataProcessor.htmlFilter when preparing output data HTML. editor.on( 'toDataFormat', function( evt ) { evt.data.filter.applyTo( evt.data.dataValue, false, true ); }, null, null, 11 ); editor.on( 'toDataFormat', function( evt ) { var data = evt.data.dataValue, writer = that.writer; writer.reset(); data.writeChildrenHtml( writer ); data = writer.getHtml( true ); // Restore those non-HTML protected source. (https://dev.ckeditor.com/ticket/4475,https://dev.ckeditor.com/ticket/4880) data = unprotectRealComments( data ); data = unprotectSource( data, editor ); evt.data.dataValue = data; }, null, null, 15 );

总结

编辑器内容的设置和获取表面上是简单只是调用一个方法就完成了,但是其实内部的流程还是很长的,大致分为:

  1. 消息告知saveSnapshot
  2. 准备工作setData
  3. 处理流程dataProcessor
  4. 发送事件 toHtml
  5. 系统事件(优先级小于10)处理 protectfilter
  6. 系统事件(优先级大于10)处理,进行最后的兜底(插入或获取)逻辑 CKEDITOR.htmlParser.basicWriter
,