'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、一个模型文档属性。比如<root><paragraph></paragraph></root>。主要是ck的模型数据
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的作用。
好了,今天分享了模型的相关属性和修改模型的方法原理,欢迎分享讨论。