事件系统
发射器 是可以触发事件的对象。它们还提供了一种监听其他发射器事件的方法。
发射器在整个编辑器架构中被广泛使用。它们是 可观察对象、引擎的视图观察器 和 转换 等机制的基础。
任何类都可以成为事件发射器。您只需要将 Emitter
混入其中即可
import { EmitterMixin, mix } from 'ckeditor5';
class AnyClass {
// Class's code.
// ...
}
mix( AnyClass, EmitterMixin );
# 监听事件
向事件添加回调很简单。您可以直接在发射器对象上监听并使用匿名函数
emitter.on( 'eventName', ( eventInfo, ...args ) => { /* ... */ } );
但是,如果您想能够删除事件监听器,则需要一个函数对象
emitter.off( 'eventName', handler );
还有另一种添加事件监听器的方法——使用 listenTo()
。这样,一个发射器就可以监听另一个发射器上的事件
foo.listenTo( bar, 'eventName', ( eventInfo, ...args ) => { /* ... */ } );
现在,您可以通过 stopListening()
轻松地将 foo
从 bar
中分离。
// Stop listening to a specific handler.
foo.stopListening( bar, 'eventName', handler );
// Stop listening to a specific event.
foo.stopListening( bar, 'eventName' );
// Stop listening to all events fired by a specific emitter.
foo.stopListening( bar );
// Stop listening to all events fired by all bound emitters.
foo.stopListening();
on()
和 off()
方法是 listenTo( this, /* ... */ )
和 stopListening( this, /* ... */ )
的简写(发射器绑定到自身)。
# 监听器优先级
默认情况下,所有监听器都绑定在 normal
优先级上,但您可以在注册监听器时指定优先级
this.on( 'eventName', () => { /* ... */ }, { priority: 'high' } );
this.listenTo( emitter, 'eventName', () => { /* ... */ }, { priority: 'high' } );
有 5 个命名优先级
最高
高
正常
低
最低
监听器按这些优先级的顺序触发(首先是 highest
,然后是 high
,等等)。对于在同一优先级上附加的多个监听器,它们按注册顺序触发。
注意:如果任何监听器 停止 事件,则不会调用任何其他监听器,包括那些在较低优先级上的监听器。
可以使用相对优先级 priorities.get( 'high' ) + 10
,但这强烈建议不要这样做。
# 停止事件和返回值
传递给事件处理程序的第一个参数始终是 EventInfo
的实例。在那里,您可以检查事件的 name
、事件的 source
发射器,并且您可以 stop()
事件以阻止进一步处理。
emitter.on( 'eventName', ( eventInfo, data ) => {
console.log( 'foo' );
eventInfo.stop();
} );
emitter.on( 'eventName', ( eventInfo, data ) => {
console.log( 'bar' ); // This won't be called.
} );
emitter.fire( 'eventName' ); // Logs "foo" only.
监听器可以设置 return
值。在所有回调处理完后,此值将由 fire()
返回。
emitter.on( 'eventName', ( eventInfo, data ) => {
eventInfo.return = 123;
} );
emitter.fire( 'eventName' ); // -> 123
# 监听命名空间事件
事件系统支持命名空间事件,以便您可以构建回调结构。您可以在事件名称中使用 :
来实现命名空间
this.fire( 'foo:bar:baz', data );
然后,监听器可以绑定到特定事件或整个命名空间
this.on( 'foo', () => { /* ... */ } );
this.on( 'foo:bar', () => { /* ... */ } );
this.on( 'foo:bar:baz', () => { /* ... */ } );
这样,您可以拥有更通用的事件,监听更广泛的事件(在本例中为 'foo'
),或更详细的回调,监听指定的事件('foo:bar'
或 'foo:bar:baz'
)。
这种机制例如在转换中使用,在那里,由于名为 'insert:<elementName>'
的事件,您可以监听特定元素的插入(如 'insert:p'
)或所有元素的插入('insert'
)。
注意:在同一优先级上注册的监听器将按注册顺序触发(无论监听整个命名空间还是特定事件)。
# 触发事件
将 Emitter
混入您的类后,您可以通过以下方式触发事件
this.fire( 'eventName', argA, argB, /* ... */ );
所有传递的参数都将在添加到事件的所有监听器中可用。
注意:大多数基类(如 Command
或 Plugin
)已经是 发射器 并触发它们自己的事件。
# 已停止事件
有时知道事件是否被任何监听器停止很有用。有一种替代方法可以仅为此目的触发事件
import { EventInfo } from 'ckeditor5';
// Prepare the event info...
const eventInfo = new EventInfo( this, 'eventName' );
// ...and fire the event.
this.fire( eventInfo, argA, argB, /* ... */ );
// Here you can check if the event was stopped.
if ( eventInfo.stop.called ) {
// The event was stopped.
}
请注意,EventInfo
在第一个参数中期望源对象作为事件的来源。
# 事件返回值
如果任何处理程序设置了 eventInfo.return
字段,则此值将在所有回调处理完后由 fire()
返回。
emitter.on( 'eventName', ( eventInfo, ...args ) => {
eventInfo.return = 123;
} );
const result = emitter.fire( 'eventName', argA, argB, /* ... */ );
console.log( result ); // -> 123
# 事件委托
Emitter
接口还提供 事件委托 机制,以便选择事件由另一个 Emitter
触发。
# 设置事件委托
将特定事件委托给另一个发射器
emitterA.delegate( 'foo' ).to( emitterB );
emitterA.delegate( 'foo', 'bar' ).to( emitterC );
您可以使用不同的名称委托事件
emitterA.delegate( 'foo' ).to( emitterB, 'bar' );
emitterA.delegate( 'foo' ).to( emitterB, name => `delegated:${ name }` );
也可以委托所有事件
emitterA.delegate( '*' ).to( emitterB );
注意:无论源发射器上的任何处理程序是否已停止委托事件,委托事件都将从目标发射器触发。
# 停止委托
您可以通过调用 stopDelegating()
方法来停止委托。它可以在不同的级别使用
// Stop delegating all events.
emitterA.stopDelegating();
// Stop delegating a specific event to all emitters.
emitterA.stopDelegating( 'foo' );
// Stop delegating a specific event to a specific emitter.
emitterA.stopDelegating( 'foo', emitterB );
# 委托事件信息
委托事件提供 path
,其中包含该事件在委托路径上遇到的发射器。
emitterA.delegate( 'foo' ).to( emitterB, 'bar' );
emitterB.delegate( 'bar' ).to( emitterC, 'baz' );
emitterA.on( 'foo', eventInfo => console.log( 'event', eventInfo.name, 'emitted by A; source:', eventInfo.source, 'path:', eventInfo.path ) );
emitterB.on( 'bar', eventInfo => console.log( 'event', eventInfo.name, 'emitted by B; source:', eventInfo.source, 'path:', eventInfo.path ) );
emitterC.on( 'baz', eventInfo => console.log( 'event', eventInfo.name, 'emitted by C; source:', eventInfo.source, 'path:', eventInfo.path ) );
emitterA.fire( 'foo' );
// Outputs:
// event "foo" emitted by A; source: emitterA; path: [ emitterA ]
// event "bar" emitted by B; source: emitterA; path: [ emitterA, emitterB ]
// event "baz" emitted by C; source: emitterA; path: [ emitterA, emitterB, emitterC ]
# 视图事件冒泡
view.Document
不仅仅是一个 Observable 和一个 发射器,它还实现了特殊的 BubblingEmitter
接口(由 BubblingEmitterMixin
实现)。它为在虚拟 DOM 树上冒泡事件提供了一种机制。
这与您从 DOM 树事件冒泡中了解到的冒泡不同。您不会在视图文档树中特定元素实例上注册监听器。相反,您可以为特定上下文注册处理程序。上下文可以是元素的名称,或虚拟上下文之一('$capture'
、'$text'
、'$root'
、'$document'
),或者用于匹配所需节点的回调。
# 监听冒泡事件
在视图元素名称上下文中注册的监听器
this.listenTo( view.document, 'enter', ( evt, data ) => {
// Listener's code.
// ...
}, { context: 'blockquote' } );
this.listenTo( view.document, 'enter', ( evt, data ) => {
// Listener's code.
// ...
}, { context: 'li' } );
在虚拟上下文中注册的监听器
this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
// Listener's code.
// ...
}, { context: '$text', priority: 'high' } );
this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
// Listener's code.
// ...
}, { context: '$root' } );
this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
// Listener's code.
// ...
}, { context: '$capture' } );
在自定义回调函数上下文中注册的监听器
import { isWidget } from 'ckeditor5';
this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
// Listener's code.
// ...
}, { context: isWidget } );
this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
// Listener's code.
// ...
}, { context: isWidget, priority: 'high' } );
注意:如果未指定 context
,则事件将绑定到 '$document'
上下文。
# 冒泡事件流
冒泡始终从虚拟 '$capture'
上下文开始。首先触发附加到此上下文的所有监听器(并按其优先级顺序)。
然后,真正的冒泡从选择位置(锚点或焦点,取决于哪个更深)开始。
如果在选择位置允许文本节点,则第一个上下文为'$text'
。然后,事件会通过所有元素冒泡到'$root'
,最后到'$document'
。
在所有上下文中,可以在所需优先级下注册监听器。如果监听器阻止了一个事件,则该事件不会针对剩余的上下文触发。
# 示例
假设给定的内容和选择
<blockquote>
<p>
Foo[]bar
</p>
</blockquote>
事件将针对以下上下文触发
'$capture'
'$text'
'p'
'blockquote'
'$root'
'$document'
假设给定的内容和选择(在小部件上)
<blockquote>
<p>
Foo
[<img />] // enhanced with toWidget()
bar
</p>
</blockquote>
事件将针对以下上下文触发
'$capture'
'img'
- widget(假设使用了自定义匹配器)
'p'
'blockquote'
'$root'
'$document'
一个更复杂的示例
<blockquote>
<figure class="table"> // enhanced with toWidget()
<table>
<tr>
<td>
<p>
foo[]bar
</p>
</td>
</tr>
</table>
</figure>
</blockquote>
将触发的事件
'$capture'
'$text'
'p'
'td'
'tr'
'table'
'figure'
- widget(假设使用了自定义匹配器)
'blockquote'
'$root'
'$document'
# BubblingEventInfo
在某些事件中,第一个参数不是标准的EventInfo
,而是BubblingEventInfo
。这是一个扩展,它提供了当前的eventPhase
和 currentTarget
.
目前,以下事件可以使用此信息
因此,上述示例中的事件将扩展以下eventPhase
数据
'$capture'
- 捕获'$text'
- 在目标上'p'
- 冒泡'td'
- 冒泡'tr'
- 冒泡'table'
- 冒泡'figure'
- 冒泡- widget - 冒泡
'blockquote'
- 冒泡'$root'
- 冒泡'$document'
- 冒泡
对于选择小部件的示例
<blockquote>
<p>
Foo
[<img />] // Enhanced with toWidget().
bar
</p>
</blockquote>
将触发的事件
'$capture'
- 捕获'img'
- 在目标上- widget - 在目标上(假设使用了自定义匹配器)
'p'
- 冒泡'blockquote'
- 冒泡'$root'
- 冒泡'$document'
- 冒泡
我们每天都在努力使我们的文档保持完整。您是否发现过时信息?是否缺少内容?请通过我们的问题跟踪器报告。
随着 42.0.0 版的发布,我们重写了大部分文档,以反映新的导入路径和功能。感谢您的反馈,帮助我们确保文档的准确性和完整性。