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

'insertContent', 'deleteContent', 'modifySelection''insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation'今天我们来深入学习一下CK5的模型。

我们先看看model.js的源码:

export 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的作用。

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

版权声明:著作权归作者所有。

相关推荐

TypeScript:基本类型详解

布尔值(Boolean)TypeScript的布尔值和JavaScript一样有两个值:true和false。类型标识:boolean示例:let isDone: boolean = false; 数字(Number)和JavaScript一样,TypeScript的所有数字都是用浮点数表示,支持二进制,八进制,十进制以及十六进制的表示方式,类型标识:num

理解Java 9的open module(公开模块)

模块化是Java 9新增的一个很重要且影响代码结构的特性。分类根据外部代码在编译时和运行时对模块的访问权限不同分为:常规模块(normal module)和公开模块(open module)。编译时访问比较容易理解,即代码能否显式直接使用模块里的类型,没有权限访问,编译时报错。在运行时访问模块代码是指使用Java里的Core Reflection 

理解RxJava里Observables的相关类型

Observable — Operator — ObserverObservable,可观察者,它就好像扬声器一样,做一些处理后发射出一些值。Operator,操作符,它就像翻译员那样将数据从一种形式翻译/修改为另一种形式。Observer,观察者,它获得那些由可观察者发射出来的或者有操作符转译的值。它们的关系如图: RxJava可观察类型在RxJava的可观察类型如下

RxJs——操作型

我们在使用Observable的时候,常常会涉及到一些操作,比如map(),filter()等,为了理解原理,今天我们在前几节(初步理解RxJs , RxJs——创建型操作 )的基础上,用两种不同的方式实现以上两个函数,希望能给大家提供借鉴。map代码实现废话不说,先来实现mapmap(fn) { return new MyObservable(observer => {

CKEditor5——模型理解(一)

我们知道,CK5实现了一个MVC的架构,从今天开始,我们一步一步深入学习模型,视图,以及模型和视图之间的转换。今天我们开始模型的学习。首先,我们看模型的定义:The model is implemented by a DOM-like tree structure of elements and text nodes.模型由两类节点构成,分别是元素节点和文本节点,模型是一种类Dom树结构。我们知道

CKEditor5——模型理解(二:Node)

上一节我们理解了基本的CK5的模型基本信息,今天我们来学习一些模型的API。节点说明首先,需要理解的就是模型的节点。在这一点上,CK5的模型节点和dom的节点有点类似,也有一些不同。我会在文章中一一介绍。节点是模型树的基本结构。它是模型中不同节点类型的一种抽象。这里需要指出的一点是:如果一个节点从模型树中分离出来,你可以使用它的 API 来操作它。但是,非常重要的是,已经附加到模型树的节点只能通过

CKEditor5——模型理解(三:Element Text)

在上一节,我们学习了CK5中模型节点Node的API,今天我们学习另一个常用的API:Element。元素节点说明element表示模型的元素节点类型,它包含一个拥有名称和子节点的节点类型,继承自Node类。元素属性说明1、name,元素的名称举个例子哈,段落的名称是paragraph,代码块的名称是codeBlock等等。2、childCount, 子元素的数目这里指的是此元素节点包含的子元素的

CKEditor5——模型理解(五:Position, Range, Selection)

今天我们继续学习CK5中模型的一些知识,主要包括:Position, Range, Selection首先,我们需要知道:position表示模型树中的一个位置。模型的位置有两部分组成:root,path。即位置由其根和该根中的路径表示。位置基于偏移量,而不是索引。这意味着两个文本节点 foo 和 bar 之间的位置偏移为 3,而不是 1。由于模型中的位置由位置根和位置路径表示,因此可以创建不存在

CKEditor5——模型理解(六:Range)

上一节我们主要介绍了模型中的Position这个关键的类,今天我们开始学习Range这个类。简单来说的话,如果Position表示一个点的话,那么Range是不是可以理解为一条线段呢?这个线段有一个startPostion,endPosition以及线段的长度等属性,我们暂且这么认为,那么我们可以看看Range官方的文档。从文档中看到,Range类有五个属性:Range属性start:Positi

CKEditor5——模型理解(七:Selection)

昨天我们学习了Range的一些API使用,今天我们看看另一个重要的类Selection的API:Selection的作用是记录鼠标在文档上的选择区域,如果是单个用户在编辑一份文档的时候,选择应该就是一个Range,如果是多个用户在编辑一份文档的时候,那么选择的区域就应该是多个range。因此,我大胆的猜测,Selection中应该有Range数组。我们来看看吧。Selection属性anchor