道招

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 );

没错,这里又有个一个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.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;
},

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

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
更新时间:
上一篇:下一篇:

相关文章

关注道招网公众帐号
联系博主