CKEditor5——模型理解(四:模型组成)
'insertContent', 'deleteContent', 'modifySelection''insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation'今天我们来深入学习一下CK5的模型。
我们先看看model.js的源码:
xport default class Model { constructor() { this.markers = new MarkerCollection(); this.document = new Document( this ); this.schema = new Schema(); this._pendingChanges = []; this._currentWriter = null; [ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation' ] .forEach( methodName => this.decorate( methodName ) ); this.on( 'applyOperation', ( evt, args ) => { const operation = args[ 0 ]; operation._validate(); }, { priority: 'highest' } ); // Register some default abstract entities. this.schema.register( '$root', { isLimit: true } ); this.schema.register( '$block', { allowIn: '$root', isBlock: true } ); this.schema.register( '$text', { allowIn: '$block', isInline: true, isContent: true } ); this.schema.register( '$clipboardHolder', { allowContentOf: '$root', allowChildren: '$text', isLimit: true } ); this.schema.register( '$documentFragment', { allowContentOf: '$root', allowChildren: '$text', isLimit: true } ); this.schema.register( '$marker' ); this.schema.addChildCheck( ( context, childDefinition ) => { if ( childDefinition.name === '$marker' ) { return true; } } ); injectSelectionPostFixer( this ); this.document.registerPostFixer( autoParagraphEmptyRoots ); } }
从上面的源码,我们可以看出。模型部分包含的功能比较多,主要的有一下几点:
1、一个存储marker的集合
2、一个模型文档属性。比如
3、一个存储schema的属性,并且定义一些基本元素,比如$root,$block,$text,$clipboardHolder,$documentFragment,$marker
4、一个存储模型变化操作的回调函数。
5、一个用于操作修改模型的writer
6、装饰一些可以在外部监听的方法:比如'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation'等
7、注册了一个模型后处理器。
8、绑定了一个监听applyOperation事件的监听函数,用于验证操作的合法性。
在这里,我们重点分析一下:model.change(callback)这个方法:
change( callback ) { try { if ( this._pendingChanges.length === 0 ) { this._pendingChanges.push( { batch: new Batch(), callback } ); return this._runPendingChanges()[ 0 ]; } else { return callback( this._currentWriter ); } } catch ( err ) { CKEditorError.rethrowUnexpectedError( err, this ); } } _runPendingChanges() { const ret = []; this.fire( '_beforeChanges' ); while ( this._pendingChanges.length ) { const currentBatch = this._pendingChanges[ 0 ].batch; this._currentWriter = new Writer( this, currentBatch ); const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter ); ret.push( callbackReturnValue ); this.document._handleChangeBlock( this._currentWriter ); this._pendingChanges.shift(); this._currentWriter = null; } this.fire( '_afterChanges' ); return ret; } //举个例子 model.change( writer => { writer.insertText( 'foo', paragraph, 'end' ); // foo. model.change( writer => { writer.insertText( 'bar', paragraph, 'end' ); // foobar. } ); writer.insertText( 'bom', paragraph, 'end' ); // foobarbom. } );
可以看到:在例子中,外层调用的时候,this._pendingChanges为空,这个时候会执行
this._pendingChanges.push( { batch: new Batch(), callback } ); return this._runPendingChanges()[ 0 ];
这时,会创建一个叫做Batch的对象,同时运行一个叫做_runPendingChanges()的函数。
这个函数的逻辑就是创建一个模型writer来处理模型文档块改变的业务逻辑。同时还触发了两个事件_beforeChanges和_afterChanges,注意,这个writer的batch属性是最外层调用时候创建的,因此内层的函数调用时候使用的这个writer将共享这个Batch,因此,外层和内层实际上是用一个Batch。当调用结束以后,这个this._pendingChanges会被移除掉。
我们再看看另一个方法:enqueueChange([ batchOrType ], callback)
enqueueChange( batchOrType, callback ) { try { if ( typeof batchOrType === 'string' ) { batchOrType = new Batch( batchOrType ); } else if ( typeof batchOrType == 'function' ) { callback = batchOrType; batchOrType = new Batch(); } this._pendingChanges.push( { batch: batchOrType, callback } ); if ( this._pendingChanges.length == 1 ) { this._runPendingChanges(); } } catch ( err ) { // @if CK_DEBUG // throw err; /* istanbul ignore next */ CKEditorError.rethrowUnexpectedError( err, this ); } }
可以看到,这个方法多了一个参数就是batchOrType,这个参数可能是string,或者Batch类型,当属于没有参数,或者参数类型为string时,我用下面的例子说明
model.change( writer => { console.log( 1 ); model.enqueueChange( writer => { console.log( 2 ); } ); console.log( 3 ); } ); // Will log: 1, 3, 2.
从上面的代码可以看出,只有在this._pendingChanges == 1时,才会执行enqueueChanges()的回调函数,实际上上面的逻辑就是最后一个执行,所以以上代码会最后打印出2。
当这个参数是从最外层的调用而来的时候,此时这个回调函数将共享这个batch,实际上就是起到了自己将定义的操作放到某个Batch的作用。
好了,今天分享了模型的相关属性和修改模型的方法原理,欢迎分享讨论。