CKEditor5——模型理解(四:模型组成)

时间:2022-06-01 12:42:32
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、一个模型文档属性。比如<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的作用。

好了,今天分享了模型的相关属性和修改模型的方法原理,欢迎分享讨论。