道招

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
更新时间:
上一篇:docker容器配置文件出错后重启容器失败解决方案下一篇:CKEditor系列(六)改造原编辑器默认样式dom结构效果对比

相关文章

CKEditor富文本编辑器职责分离

背景 CKEditor富文本 编辑器 (生产版本1.1.1及以前)里面包含富文本基础插件及当前最新的邮件特定的业务插件(签名、快捷回复、邀评、默认样式、选择颜色、插入图片、粘贴模式) O端 阅读更多…

2021年的一点工作总结(二)富文本编辑器

邮件项目的核心功能就是编辑邮件了,所以文本的编辑特别容易被用户吐槽了。用户报障的时候一个万能的吐槽点“没有xxx功能,不支持xxx,没有Outlook好用”。 其实作为一个web产品,如果需要更加公 阅读更多…

CKEditor系列(六)改造原编辑器默认样式dom结构效果对比

熟悉的朋友应该知道之前是用的wangEditor,近期才迁移到CKEditor,很早的时候项目就支持一个叫“默认样式”的功能,需求就是部分BU希望能够统一邮件对外发送的样式,比如统一使用“宋体,黑色 阅读更多…

富文本编辑器CKEditor4迁移方案

之前写过 《富文本编辑器wangEditor迁移CKEditor前后效果对比》 ,结合大家的反馈后进行了调整。 增加了具体案例的展示CKEditor插件和事件系统,重新整理成迁移方案。 一、背景 阅读更多…

CKEditor系列(四)支持动态多语言i18n

多语言文件结构 先看下CKEditor4的多语言文件长什么样子 //src/lang/zh-cn.js CKEDITOR.lang[ &#039;zh-cn&#039; ] = { 阅读更多…

CKEditor系列(七)编辑器工具栏根据宽度自动折叠

刚才看了看上一篇写CKEditor的文章是在今年的一月份,现在轮到我们的设计师对编辑器下手了。我们回顾下现在的编辑器长什么样子。 需求 我们客户端默认窗口尺寸下,会出现排,并且第二排 阅读更多…

关注道招网公众帐号
友情链接
消息推送
道招网关注互联网,分享IT资讯,前沿科技、编程技术,是否允许文章更新后推送通知消息。
允许
不用了