龙空技术网

CKEditor系列(五)编辑器内容的设置和获取过程

锅巴瓜子 136

前言:

今天兄弟们对“ckeditor编辑html”都比较重视,姐妹们都需要知道一些“ckeditor编辑html”的相关文章。那么小编也在网络上汇集了一些有关“ckeditor编辑html””的相关知识,希望同学们能喜欢,朋友们一起来了解一下吧!

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

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

// src/core/editor.jssetData: 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过程实际是分三步

判断是否需要saveSnapshot判断是否需要触发setData事件判断是否需要触发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.jseditor.on( 'setData', function() {    // Invalidate locked selection when unloading DOM.    // (,  and )    editor.unlockSelection();    // Webkit's selection will mess up after the data loading.    if ( CKEDITOR.env.webkit )        clearSelection();} );

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

setData之afterSetData

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

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

// 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;},

setHtmlgetHtml本质就是原生node的innerHTML,所以setDatagetData的过程其实就是 this.editor.dataProcessor.toHtmlthis.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.jstoHtml: 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;},

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

dataProcessor之toHtml

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

// src/core/dataProcessor.jseditor.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. ()    // 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. ()    data = protectElements( data, protectElementsRegex );    // Certain elements has problem to go through DOM operation, protect    // them by prefixing 'cke' namespace. ()    data = protectElementsNames( data );    // All none-IE browsers ignore self-closed custom elements,    // protecting them into open-close. ()    data = protectSelfClosingElements( data );    // Compensate one leading line break after <pre> open as browsers    // eat it up. ()    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. ()    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. ()    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 ().    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.jseditor.on( 'toDataFormat', function( evt ) {    var data = evt.data.dataValue;    //  - 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. ()    data = unprotectRealComments( data );    data = unprotectSource( data, editor );    evt.data.dataValue = data;}, null, null, 15 );
总结

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

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

标签: #ckeditor编辑html #ckeditor4编辑器api