3

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

 2 years ago
source link: https://www.daozhao.com/10324.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK