Mapper
Maps elements, positions and markers between the view and the model.
The instance of the Mapper used for the editing pipeline is available in editor.editing.mapper.
Mapper uses bound elements to find corresponding elements and positions, so, to get proper results, all model elements should be bound.
To map the complex model to/from view relations, you may provide custom callbacks for the modelToViewPosition event and viewToModelPosition event that are fired whenever a position mapping request occurs. Those events are fired by the toViewPosition and toModelPosition methods. Mapper adds its own default callbacks with 'lowest' priority. To override default Mapper mapping, add custom callback with higher priority and stop the event.
Properties
_cache : MapperCacheprivatemodule:engine/conversion/mapper~Mapper#_cacheManages dynamic cache for the
Mapperto improve the performance._deferredBindingRemovals : Map<ViewElement, ViewElement | ViewDocumentFragment>privatemodule:engine/conversion/mapper~Mapper#_deferredBindingRemovalsThe map of removed view elements with their current root (used for deferred unbinding).
_elementToMarkerNames : Map<ViewElement, Set<string>>privatemodule:engine/conversion/mapper~Mapper#_elementToMarkerNamesView element to model marker names mapping.
This is reverse to
_markerNameToElementsmap._markerNameToElements : Map<string, Set<ViewElement>>privatemodule:engine/conversion/mapper~Mapper#_markerNameToElementsModel marker name to view elements mapping.
Keys are
Strings while values areSets with view elements. One marker (name) can be mapped to multiple elements._modelToViewMapping : WeakMap<ModelElement | ModelDocumentFragment, ViewElement | ViewDocumentFragment>privatemodule:engine/conversion/mapper~Mapper#_modelToViewMappingModel element to view element mapping.
_unboundMarkerNames : Set<string>privatemodule:engine/conversion/mapper~Mapper#_unboundMarkerNamesStores marker names of markers which have changed due to unbinding a view element (so it is assumed that the view element has been removed, moved or renamed).
_viewToModelLengthCallbacks : Map<string, ( element: ViewElement ) => number>privatemodule:engine/conversion/mapper~Mapper#_viewToModelLengthCallbacksA map containing callbacks between view element names and functions evaluating length of view elements in model.
_viewToModelMapping : WeakMap<ViewElement | ViewDocumentFragment, ModelElement | ModelDocumentFragment>privatemodule:engine/conversion/mapper~Mapper#_viewToModelMappingView element to model element mapping.
Methods
module:engine/conversion/mapper~Mapper#constructorCreates an instance of the mapper.
bindElementToMarker( element, name ) → voidmodule:engine/conversion/mapper~Mapper#bindElementToMarkerBinds the given marker name with the given view element. The element will be added to the current set of elements bound with the given marker name.
Parameters
element : ViewElementElement to bind.
name : stringMarker name.
Returns
void
bindElements( modelElement, viewElement ) → voidmodule:engine/conversion/mapper~Mapper#bindElementsMarks model and view elements as corresponding. Corresponding elements can be retrieved by using the toModelElement and toViewElement methods. The information that elements are bound is also used to translate positions.
Parameters
modelElement : ModelElement | ModelDocumentFragmentModel element.
viewElement : ViewElement | ViewDocumentFragmentView element.
Returns
void
clearBindings() → voidmodule:engine/conversion/mapper~Mapper#clearBindingsdelegate( events ) → EmitterMixinDelegateChaininheritedmodule:engine/conversion/mapper~Mapper#delegateDelegates selected events to another
Emitter. For instance:emitterA.delegate( 'eventX' ).to( emitterB ); emitterA.delegate( 'eventX', 'eventY' ).to( emitterC );Copy codethen
eventXis delegated (fired by)emitterBandemitterCalong withdata:emitterA.fire( 'eventX', data );Copy codeand
eventYis delegated (fired by)emitterCalong withdata:emitterA.fire( 'eventY', data );Copy codeParameters
events : Array<string>Event names that will be delegated to another emitter.
Returns
findMappedViewAncestor( viewPosition ) → ViewElementmodule:engine/conversion/mapper~Mapper#findMappedViewAncestorFor the given
viewPosition, finds and returns the closest ancestor of this position that has a mapping to the model.Parameters
viewPosition : ViewPositionPosition for which a mapped ancestor should be found.
Returns
findPositionIn( viewContainer, modelOffset ) → ViewPositionmodule:engine/conversion/mapper~Mapper#findPositionInFinds the position in a view element or view document fragment node (or in its children) with the expected model offset.
If the passed
viewContaineris bound to model,Mapperwill use caching mechanism to improve performance.Parameters
viewContainer : ViewElement | ViewDocumentFragmentTree view element in which we are looking for the position.
modelOffset : numberExpected offset.
Returns
ViewPositionFound position.
fire( eventOrInfo, args ) → GetEventInfo<TEvent>[ 'return' ]inheritedmodule:engine/conversion/mapper~Mapper#fireFires an event, executing all callbacks registered for it.
The first parameter passed to callbacks is an
EventInfoobject, followed by the optionalargsprovided in thefire()method call.Type parameters
Parameters
eventOrInfo : GetNameOrEventInfo<TEvent>The name of the event or
EventInfoobject if event is delegated.args : TEvent[ 'args' ]Additional arguments to be passed to the callbacks.
Returns
GetEventInfo<TEvent>[ 'return' ]By default the method returns
undefined. However, the return value can be changed by listeners through modification of theevt.return's property (the event info is the first param of every callback).
flushDeferredBindings() → voidmodule:engine/conversion/mapper~Mapper#flushDeferredBindingsUnbinds all deferred binding removals of view elements that in the meantime were not re-attached to some root or document fragment.
See:
unbindViewElement().Returns
void
flushUnboundMarkerNames() → Array<string>module:engine/conversion/mapper~Mapper#flushUnboundMarkerNamesReturns all marker names of markers which have changed due to unbinding a view element (so it is assumed that the view element has been removed, moved or renamed) since the last flush. After returning, the marker names list is cleared.
Returns
Array<string>
getModelLength( viewNode ) → numbermodule:engine/conversion/mapper~Mapper#getModelLengthGets the length of the view element in the model.
The length is calculated as follows:
- if a length mapping callback is provided for the given
viewNode, it is used to evaluate the model length (viewNodeis used as first and only parameter passed to the callback), - length of a text node is equal to the length of its data,
- length of a ui element is equal to 0,
- length of a mapped element is equal to 1,
- length of a non-mapped element is equal to the length of its children.
Examples:
foo -> 3 // Text length is equal to its data length. <p>foo</p> -> 1 // Length of an element which is mapped is by default equal to 1. <b>foo</b> -> 3 // Length of an element which is not mapped is a length of its children. <div><p>x</p><p>y</p></div> -> 2 // Assuming that <div> is not mapped and <p> are mapped.Copy codeParameters
viewNode : ViewNode | ViewDocumentFragmentView node.
Returns
numberLength of the node in the tree model.
- if a length mapping callback is provided for the given
listenTo( emitter, event, callback, [ options ] ) → voidinheritedmodule:engine/conversion/mapper~Mapper#listenTo:BASE_EMITTERRegisters a callback function to be executed when an event is fired in a specific (emitter) object.
Events can be grouped in namespaces using
:. When namespaced event is fired, it additionally fires all callbacks for that namespace.// myEmitter.on( ... ) is a shorthand for myEmitter.listenTo( myEmitter, ... ). myEmitter.on( 'myGroup', genericCallback ); myEmitter.on( 'myGroup:myEvent', specificCallback ); // genericCallback is fired. myEmitter.fire( 'myGroup' ); // both genericCallback and specificCallback are fired. myEmitter.fire( 'myGroup:myEvent' ); // genericCallback is fired even though there are no callbacks for "foo". myEmitter.fire( 'myGroup:foo' );Copy codeAn event callback can stop the event and set the return value of the
firemethod.Type parameters
Parameters
emitter : EmitterThe object that fires the event.
event : TEvent[ 'name' ]The name of the event.
callback : GetCallback<TEvent>The function to be called on event.
[ options ] : GetCallbackOptions<TEvent>Additional options.
Returns
void
markerNameToElements( name ) → null | Set<ViewElement>module:engine/conversion/mapper~Mapper#markerNameToElementsGets all view elements bound to the given marker name.
Parameters
name : stringMarker name.
Returns
null | Set<ViewElement>View elements bound with the given marker name or
nullif no elements are bound to the given marker name.
off( event, callback ) → voidinheritedmodule:engine/conversion/mapper~Mapper#offStops executing the callback on the given event. Shorthand for
this.stopListening( this, event, callback ).Parameters
event : stringThe name of the event.
callback : FunctionThe function to stop being called.
Returns
void
on( event, callback, [ options ] ) → voidinheritedmodule:engine/conversion/mapper~Mapper#onRegisters a callback function to be executed when an event is fired.
Shorthand for
this.listenTo( this, event, callback, options )(it makes the emitter listen on itself).Type parameters
Parameters
event : TEvent[ 'name' ]The name of the event.
callback : GetCallback<TEvent>The function to be called on event.
[ options ] : GetCallbackOptions<TEvent>Additional options.
Returns
void
once( event, callback, [ options ] ) → voidinheritedmodule:engine/conversion/mapper~Mapper#onceRegisters a callback function to be executed on the next time the event is fired only. This is similar to calling
onfollowed byoffin the callback.Type parameters
Parameters
event : TEvent[ 'name' ]The name of the event.
callback : GetCallback<TEvent>The function to be called on event.
[ options ] : GetCallbackOptions<TEvent>Additional options.
Returns
void
registerViewToModelLength( viewElementName, lengthCallback ) → voiddeprecatedmodule:engine/conversion/mapper~Mapper#registerViewToModelLengthThis method is deprecated and will be removed in one of the future CKEditor 5 releases.
Using this method will turn off
Mappercaching system and may degrade performance when operating on bigger documents.Registers a callback that evaluates the length in the model of a view element with the given name.
The callback is fired with one argument, which is a view element instance. The callback is expected to return a number representing the length of the view element in the model.
// List item in view may contain nested list, which have other list items. In model though, // the lists are represented by flat structure. Because of those differences, length of list view element // may be greater than one. In the callback it's checked how many nested list items are in evaluated list item. function getViewListItemLength( element ) { let length = 1; for ( let child of element.getChildren() ) { if ( child.name == 'ul' || child.name == 'ol' ) { for ( let item of child.getChildren() ) { length += getViewListItemLength( item ); } } } return length; } mapper.registerViewToModelLength( 'li', getViewListItemLength );Copy codeParameters
viewElementName : stringName of view element for which callback is registered.
lengthCallback : ( element: ViewElement ) => numberFunction return a length of view element instance in model.
Returns
void
stopDelegating( [ event ], [ emitter ] ) → voidinheritedmodule:engine/conversion/mapper~Mapper#stopDelegatingStops delegating events. It can be used at different levels:
- To stop delegating all events.
- To stop delegating a specific event to all emitters.
- To stop delegating a specific event to a specific emitter.
Parameters
[ event ] : stringThe name of the event to stop delegating. If omitted, stops it all delegations.
[ emitter ] : Emitter(requires
event) The object to stop delegating a particular event to. If omitted, stops delegation ofeventto all emitters.
Returns
void
stopListening( [ emitter ], [ event ], [ callback ] ) → voidinheritedmodule:engine/conversion/mapper~Mapper#stopListening:BASE_STOPStops listening for events. It can be used at different levels:
- To stop listening to a specific callback.
- To stop listening to a specific event.
- To stop listening to all events fired by a specific object.
- To stop listening to all events fired by all objects.
Parameters
[ emitter ] : EmitterThe object to stop listening to. If omitted, stops it for all objects.
[ event ] : string(Requires the
emitter) The name of the event to stop listening to. If omitted, stops it for all events fromemitter.[ callback ] : Function(Requires the
event) The function to be removed from the call list for the givenevent.
Returns
void
toModelElement( viewDocumentFragment ) → undefined | ModelDocumentFragmentmodule:engine/conversion/mapper~Mapper#toModelElement:DOCUMENT_FRAGMENTGets the corresponding model document fragment.
Parameters
viewDocumentFragment : ViewDocumentFragmentView document fragment.
Returns
undefined | ModelDocumentFragmentCorresponding model document fragment or
undefinedif not found.
toModelElement( viewElement ) → undefined | ModelElementmodule:engine/conversion/mapper~Mapper#toModelElement:ELEMENTGets the corresponding model element.
Note:
ViewUIElementdoes not have corresponding element in model.Parameters
viewElement : ViewElementView element.
Returns
undefined | ModelElementCorresponding model element or
undefinedif not found.
toModelPosition( viewPosition ) → ModelPositionmodule:engine/conversion/mapper~Mapper#toModelPositionGets the corresponding model position.
Parameters
viewPosition : ViewPositionView position.
Returns
ModelPositionCorresponding model position.
Fires
toModelRange( viewRange ) → ModelRangemodule:engine/conversion/mapper~Mapper#toModelRangeGets the corresponding model range.
Parameters
viewRange : ViewRangeView range.
Returns
ModelRangeCorresponding model range.
toViewElement( modelDocumentFragment ) → undefined | ViewDocumentFragmentmodule:engine/conversion/mapper~Mapper#toViewElement:DOCUMENT_FRAGMENTGets the corresponding view document fragment.
Parameters
modelDocumentFragment : ModelDocumentFragmentModel document fragment.
Returns
undefined | ViewDocumentFragmentCorresponding view document fragment or
undefinedif not found.
toViewElement( modelElement ) → undefined | ViewElementmodule:engine/conversion/mapper~Mapper#toViewElement:ELEMENTGets the corresponding view element.
Parameters
modelElement : ModelElementModel element.
Returns
undefined | ViewElementCorresponding view element or
undefinedif not found.
toViewPosition( modelPosition, options = { [options.isPhantom] } ) → ViewPositionmodule:engine/conversion/mapper~Mapper#toViewPositionGets the corresponding view position.
Parameters
modelPosition : ModelPositionModel position.
options : objectAdditional options for position mapping process.
Properties[ options.isPhantom ] : booleanShould be set to
trueif the model position to map is pointing to a place in model tree which no longer exists. For example, it could be an end of a removed model range.
Defaults to
{}
Returns
ViewPositionCorresponding view position.
Fires
toViewRange( modelRange ) → ViewRangemodule:engine/conversion/mapper~Mapper#toViewRangeGets the corresponding view range.
Parameters
modelRange : ModelRangeModel range.
Returns
ViewRangeCorresponding view range.
unbindElementFromMarkerName( element, name ) → voidmodule:engine/conversion/mapper~Mapper#unbindElementFromMarkerNameUnbinds an element from given marker name.
Parameters
element : ViewElementElement to unbind.
name : stringMarker name.
Returns
void
unbindModelElement( modelElement ) → voidmodule:engine/conversion/mapper~Mapper#unbindModelElementUnbinds the given model element from the map.
Note: the model-to-view binding will be removed, if it existed. However, the corresponding view-to-model binding will be removed only if the view element is still bound to the passed
modelElement.This behavior lets for re-binding view element to another model element without fear of losing the new binding when the previously bound model element is unbound.
Parameters
modelElement : ModelElementModel element to unbind.
Returns
void
unbindViewElement( viewElement, options = { [options.defer] } ) → voidmodule:engine/conversion/mapper~Mapper#unbindViewElementUnbinds the given view element from the map.
Note: view-to-model binding will be removed, if it existed. However, corresponding model-to-view binding will be removed only if model element is still bound to the passed
viewElement.This behavior allows for re-binding model element to another view element without fear of losing the new binding when the previously bound view element is unbound.
Parameters
viewElement : ViewElementView element to unbind.
options : objectThe options object.
Properties[ options.defer ] : booleanControls whether the binding should be removed immediately or deferred until a
flushDeferredBindings()call.
Defaults to
{}
Returns
void
_findPositionStartingFrom( startViewPosition, startModelOffset, targetModelOffset, viewContainer, useCache ) → ViewPositionprivatemodule:engine/conversion/mapper~Mapper#_findPositionStartingFromPerforms most of the logic for
Mapper#findPositionIn().It allows to start looking for the requested model offset from a given starting position, to enable caching. Using the cache, you can set the starting point and skip all the calculations that were already previously done.
This method uses recursion to find positions inside deep structures. Example:
<p>fo<b>bar</b>bom</p> -> target offset: 4 <p>|fo<b>bar</b>bom</p> -> target offset: 4, traversed offset: 0 <p>fo|<b>bar</b>bom</p> -> target offset: 4, traversed offset: 2 <p>fo<b>bar</b>|bom</p> -> target offset: 4, traversed offset: 5 -> we are too far, look recursively in <b>. <p>fo<b>|bar</b>bom</p> -> target offset: 4, traversed offset: 2 <p>fo<b>bar|</b>bom</p> -> target offset: 4, traversed offset: 5 -> we are too far, look inside "bar". <p>fo<b>ba|r</b>bom</p> -> target offset: 4, traversed offset: 2 -> position is inside text node at offset 4-2 = 2.Copy codeParameters
startViewPosition : ViewPositionView position to start looking from.
startModelOffset : numberModel offset related to
startViewPosition.targetModelOffset : numberTarget model offset to find.
viewContainer : ViewElement | ViewDocumentFragmentMapped ancestor of
startViewPosition.startModelOffsetis the offset inside a model element or model document fragment mapped toviewContainer.useCache : booleanWhether
Mappershould cache positions while traversing the view tree looking forexpectedModelOffset.
Returns
ViewPositionView position mapped to
targetModelOffset.
_getModelLengthAndCache( viewNode, viewContainer, modelOffset ) → numberprivatemodule:engine/conversion/mapper~Mapper#_getModelLengthAndCacheGets the length of the view element in the model and updates cache values after each view item it visits.
See also
getModelLength.Parameters
viewNode : ViewNodeView node.
viewContainer : ViewElement | ViewDocumentFragmentAncestor of
viewNodethat is a mapped view element.modelOffset : numberModel offset at which the
viewNodestarts.
Returns
numberLength of the node in the tree model.
_moveViewPositionToTextNode( viewPosition ) → ViewPositionprivatemodule:engine/conversion/mapper~Mapper#_moveViewPositionToTextNodeBecause we prefer positions in the text nodes over positions next to text nodes, if the view position was next to a text node, it moves it into the text node instead.
<p>[]<b>foo</b></p> -> <p>[]<b>foo</b></p> // do not touch if position is not directly next to text <p>foo[]<b>foo</b></p> -> <p>foo{}<b>foo</b></p> // move to text node <p><b>[]foo</b></p> -> <p><b>{}foo</b></p> // move to text nodeCopy codeParameters
viewPosition : ViewPositionPosition potentially next to the text node.
Returns
ViewPositionPosition in the text node if possible.
_toModelOffset( viewParent, viewOffset, viewBlock ) → numberprivatemodule:engine/conversion/mapper~Mapper#_toModelOffsetCalculates model offset based on the view position and the block element.
Example:
<p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, p ) -> 5Copy codeIs a sum of:
<p>foo|<b>bar</b></p> // _toModelOffset( p, 3, p ) -> 3 <p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, b ) -> 2Copy codeParameters
viewParent : ViewElementPosition parent.
viewOffset : numberPosition offset.
viewBlock : ViewElement | ViewDocumentFragmentBlock used as a base to calculate offset.
Returns
numberOffset in the model.
Events
modelToViewPosition( eventInfo, <anonymous> )module:engine/conversion/mapper~Mapper#event:modelToViewPositionFired for each model-to-view position mapping request. The purpose of this event is to enable custom model-to-view position mapping. Callbacks added to this event take model position and are expected to calculate the view position. The calculated view position should be added as a
viewPositionvalue in thedataobject that is passed as one of parameters to the event callback.// Assume that "captionedImage" model element is converted to <img> and following <span> elements in view, // and the model element is bound to <img> element. Force mapping model positions inside "captionedImage" to that // <span> element. mapper.on( 'modelToViewPosition', ( evt, data ) => { const positionParent = modelPosition.parent; if ( positionParent.name == 'captionedImage' ) { const viewImg = data.mapper.toViewElement( positionParent ); const viewCaption = viewImg.nextSibling; // The <span> element. data.viewPosition = new ViewPosition( viewCaption, modelPosition.offset ); // Stop the event if other callbacks should not modify calculated value. evt.stop(); } } );Copy codeNote: keep in mind that sometimes a "phantom" model position is being converted. A "phantom" model position is a position that points to a nonexistent place in model. Such a position might still be valid for conversion, though (it would point to a correct place in the view when converted). One example of such a situation is when a range is removed from the model, there may be a need to map the range's end (which is no longer a valid model position). To handle such situations, check the
data.isPhantomflag:// Assume that there is a "customElement" model element and whenever the position is before it, // we want to move it to the inside of the view element bound to "customElement". mapper.on( 'modelToViewPosition', ( evt, data ) => { if ( data.isPhantom ) { return; } // Below line might crash for phantom position that does not exist in model. const sibling = data.modelPosition.nodeBefore; // Check if this is the element we are interested in. if ( !sibling.is( 'element', 'customElement' ) ) { return; } const viewElement = data.mapper.toViewElement( sibling ); data.viewPosition = new ViewPosition( sibling, 0 ); evt.stop(); } );Copy codeNote: the default mapping callback is provided with a
lowpriority setting and does not cancel the event, so it is possible to attach a custom callback after a default callback and also usedata.viewPositioncalculated by the default callback (for example to fix it).Note: the default mapping callback will not fire if
data.viewPositionis already set.Note: these callbacks are called very often. For efficiency reasons, it is advised to use them only when position mapping between the given model and view elements is unsolvable by using just elements mapping and default algorithm. Also, the condition that checks if a special case scenario happened should be as simple as possible.
Parameters
eventInfo : EventInfoAn object containing information about the fired event.
<anonymous> : MapperModelToViewPositionEventData
viewToModelPosition( eventInfo, <anonymous> )module:engine/conversion/mapper~Mapper#event:viewToModelPositionFired for each view-to-model position mapping request. See event-modelToViewPosition.
// See example in `modelToViewPosition` event description. // This custom mapping will map positions from <span> element next to <img> to the "captionedImage" element. mapper.on( 'viewToModelPosition', ( evt, data ) => { const positionParent = viewPosition.parent; if ( positionParent.hasClass( 'image-caption' ) ) { const viewImg = positionParent.previousSibling; const modelImg = data.mapper.toModelElement( viewImg ); data.modelPosition = new ModelPosition( modelImg, viewPosition.offset ); evt.stop(); } } );Copy codeNote: the default mapping callback is provided with a
lowpriority setting and does not cancel the event, so it is possible to attach a custom callback after the default callback and also usedata.modelPositioncalculated by the default callback (for example to fix it).Note: the default mapping callback will not fire if
data.modelPositionis already set.Note: these callbacks are called very often. For efficiency reasons, it is advised to use them only when position mapping between the given model and view elements is unsolvable by using just elements mapping and default algorithm. Also, the condition that checks if special case scenario happened should be as simple as possible.
Parameters
eventInfo : EventInfoAn object containing information about the fired event.
<anonymous> : MapperViewToModelPositionEventData