Mapper (engine/conversion)
@ckeditor/ckeditor5-engine/src/conversion/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.
Filtering
Properties
-
private
_deferredBindingRemovals : Map<Element, Element | DocumentFragment>
module:engine/conversion/mapper~Mapper#_deferredBindingRemovals
The map of removed view elements with their current root (used for deferred unbinding).
-
private
_elementToMarkerNames : Map<Element, Set<string>>
module:engine/conversion/mapper~Mapper#_elementToMarkerNames
-
private
_markerNameToElements : Map<string, Set<Element>>
module:engine/conversion/mapper~Mapper#_markerNameToElements
Model marker name to view elements mapping.
Keys are
String
s while values areSet
s with view elements. One marker (name) can be mapped to multiple elements. -
private
_modelToViewMapping : WeakMap<Element | DocumentFragment, Element | DocumentFragment>
module:engine/conversion/mapper~Mapper#_modelToViewMapping
Model element to view element mapping.
-
Stores 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).
-
private
_viewToModelLengthCallbacks : Map<string, ( Element ) => number>
module:engine/conversion/mapper~Mapper#_viewToModelLengthCallbacks
A map containing callbacks between view element names and functions evaluating length of view elements in model.
-
private
_viewToModelMapping : WeakMap<Element | DocumentFragment, Element | DocumentFragment>
module:engine/conversion/mapper~Mapper#_viewToModelMapping
View element to model element mapping.
Methods
-
constructor()
module:engine/conversion/mapper~Mapper#constructor
Creates an instance of the mapper.
-
bindElementToMarker( element, name ) → void
module:engine/conversion/mapper~Mapper#bindElementToMarker
Binds 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 : Element
Element to bind.
name : string
Marker name.
Returns
void
-
bindElements( modelElement, viewElement ) → void
module:engine/conversion/mapper~Mapper#bindElements
Marks 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 : Element | DocumentFragment
Model element.
viewElement : Element | DocumentFragment
View element.
Returns
void
-
clearBindings() → void
module:engine/conversion/mapper~Mapper#clearBindings
-
inherited
delegate( events ) → EmitterMixinDelegateChain
module:engine/conversion/mapper~Mapper#delegate
Delegates selected events to another
Emitter
. For instance:emitterA.delegate( 'eventX' ).to( emitterB ); emitterA.delegate( 'eventX', 'eventY' ).to( emitterC );
then
eventX
is delegated (fired by)emitterB
andemitterC
along withdata
:emitterA.fire( 'eventX', data );
and
eventY
is delegated (fired by)emitterC
along withdata
:emitterA.fire( 'eventY', data );
Parameters
events : Array<string>
Event names that will be delegated to another emitter.
Returns
-
findMappedViewAncestor( viewPosition ) → Element
module:engine/conversion/mapper~Mapper#findMappedViewAncestor
For the given
viewPosition
, finds and returns the closest ancestor of this position that has a mapping to the model.Parameters
viewPosition : Position
Position for which a mapped ancestor should be found.
Returns
-
findPositionIn( viewParent, expectedOffset ) → Position
module:engine/conversion/mapper~Mapper#findPositionIn
Finds the position in the view node (or in its children) with the expected model offset.
Example:
<p>fo<b>bar</b>bom</p> -> expected offset: 4 findPositionIn( p, 4 ): <p>|fo<b>bar</b>bom</p> -> expected offset: 4, actual offset: 0 <p>fo|<b>bar</b>bom</p> -> expected offset: 4, actual offset: 2 <p>fo<b>bar</b>|bom</p> -> expected offset: 4, actual offset: 5 -> we are too far findPositionIn( b, 4 - ( 5 - 3 ) ): <p>fo<b>|bar</b>bom</p> -> expected offset: 2, actual offset: 0 <p>fo<b>bar|</b>bom</p> -> expected offset: 2, actual offset: 3 -> we are too far findPositionIn( bar, 2 - ( 3 - 3 ) ): We are in the text node so we can simple find the offset. <p>fo<b>ba|r</b>bom</p> -> expected offset: 2, actual offset: 2 -> position found
Parameters
viewParent : Node | DocumentFragment
Tree view element in which we are looking for the position.
expectedOffset : number
Expected offset.
Returns
Position
Found position.
-
inherited
fire( eventOrInfo, args ) → GetEventInfo<TEvent>[ 'return' ]
module:engine/conversion/mapper~Mapper#fire
Fires an event, executing all callbacks registered for it.
The first parameter passed to callbacks is an
EventInfo
object, followed by the optionalargs
provided in thefire()
method call.Type parameters
Parameters
eventOrInfo : GetNameOrEventInfo<TEvent>
The name of the event or
EventInfo
object 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() → void
module:engine/conversion/mapper~Mapper#flushDeferredBindings
Unbinds 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#flushUnboundMarkerNames
Returns 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 ) → number
module:engine/conversion/mapper~Mapper#getModelLength
Gets 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 (viewNode
is 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.
Parameters
viewNode : Node | DocumentFragment
View node.
Returns
number
Length of the node in the tree model.
- if a length mapping callback is provided for the given
-
inherited
listenTo( emitter, event, callback, [ options ] ) → void
module:engine/conversion/mapper~Mapper#listenTo:BASE_EMITTER
Registers 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' );
An event callback can stop the event and set the return value of the
fire
method.Type parameters
Parameters
emitter : Emitter
The 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<Element>
module:engine/conversion/mapper~Mapper#markerNameToElements
Gets all view elements bound to the given marker name.
Parameters
name : string
Marker name.
Returns
null | Set<Element>
View elements bound with the given marker name or
null
if no elements are bound to the given marker name.
-
Stops executing the callback on the given event. Shorthand for
this.stopListening( this, event, callback )
.Parameters
event : string
The name of the event.
callback : Function
The function to stop being called.
Returns
void
-
Registers 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
-
Registers a callback function to be executed on the next time the event is fired only. This is similar to calling
on
followed byoff
in 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 ) → void
module:engine/conversion/mapper~Mapper#registerViewToModelLength
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 );
Parameters
viewElementName : string
Name of view element for which callback is registered.
lengthCallback : ( Element ) => number
Function return a length of view element instance in model.
Returns
void
-
inherited
stopDelegating( [ event ], [ emitter ] ) → void
module:engine/conversion/mapper~Mapper#stopDelegating
Stops 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 ] : string
The 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 ofevent
to all emitters.
Returns
void
-
inherited
stopListening( [ emitter ], [ event ], [ callback ] ) → void
module:engine/conversion/mapper~Mapper#stopListening:BASE_STOP
Stops 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 ] : Emitter
The 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 | DocumentFragment
module:engine/conversion/mapper~Mapper#toModelElement:DOCUMENT_FRAGMENT
Gets the corresponding model document fragment.
Parameters
viewDocumentFragment : DocumentFragment
View document fragment.
Returns
undefined | DocumentFragment
Corresponding model document fragment or
undefined
if not found.
-
toModelElement( viewElement ) → undefined | Element
module:engine/conversion/mapper~Mapper#toModelElement:ELEMENT
-
toModelPosition( viewPosition ) → Position
module:engine/conversion/mapper~Mapper#toModelPosition
-
toModelRange( viewRange ) → Range
module:engine/conversion/mapper~Mapper#toModelRange
-
toViewElement( modelDocumentFragment ) → undefined | DocumentFragment
module:engine/conversion/mapper~Mapper#toViewElement:DOCUMENT_FRAGMENT
Gets the corresponding view document fragment.
Parameters
modelDocumentFragment : DocumentFragment
Model document fragment.
Returns
undefined | DocumentFragment
Corresponding view document fragment or
undefined
if not found.
-
toViewElement( modelElement ) → undefined | Element
module:engine/conversion/mapper~Mapper#toViewElement:ELEMENT
-
toViewPosition( modelPosition, options = { [options.isPhantom] } ) → Position
module:engine/conversion/mapper~Mapper#toViewPosition
Gets the corresponding view position.
Parameters
modelPosition : Position
Model position.
options : object
Additional options for position mapping process.
Properties[ options.isPhantom ] : boolean
Should be set to
true
if 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
Position
Corresponding view position.
Fires
-
toViewRange( modelRange ) → Range
module:engine/conversion/mapper~Mapper#toViewRange
-
unbindElementFromMarkerName( element, name ) → void
module:engine/conversion/mapper~Mapper#unbindElementFromMarkerName
Unbinds an element from given marker name.
Parameters
element : Element
Element to unbind.
name : string
Marker name.
Returns
void
-
unbindModelElement( modelElement ) → void
module:engine/conversion/mapper~Mapper#unbindModelElement
Unbinds 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 : Element
Model element to unbind.
Returns
void
-
unbindViewElement( viewElement, options = { [options.defer] } ) → void
module:engine/conversion/mapper~Mapper#unbindViewElement
Unbinds 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 : Element
View element to unbind.
options : object
The options object.
Properties[ options.defer ] : boolean
Controls whether the binding should be removed immediately or deferred until a
flushDeferredBindings()
call.
Defaults to
{}
Returns
void
-
private
_moveViewPositionToTextNode( viewPosition ) → Position
module:engine/conversion/mapper~Mapper#_moveViewPositionToTextNode
Because 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 node
Parameters
viewPosition : Position
Position potentially next to the text node.
Returns
Position
Position in the text node if possible.
-
private
_toModelOffset( viewParent, viewOffset, viewBlock ) → number
module:engine/conversion/mapper~Mapper#_toModelOffset
Calculates model offset based on the view position and the block element.
Example:
<p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, p ) -> 5
Is 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 ) -> 2
Parameters
viewParent : Element
Position parent.
viewOffset : number
Position offset.
viewBlock : Element
Block used as a base to calculate offset.
Returns
number
Offset in the model.
Events
-
modelToViewPosition( eventInfo, <anonymous> )
module:engine/conversion/mapper~Mapper#event:modelToViewPosition
Fired 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
viewPosition
value in thedata
object 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(); } } );
Note: 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.isPhantom
flag:// 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(); } );
Note: the default mapping callback is provided with a
low
priority setting and does not cancel the event, so it is possible to attach a custom callback after a default callback and also usedata.viewPosition
calculated by the default callback (for example to fix it).Note: the default mapping callback will not fire if
data.viewPosition
is 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 : EventInfo
An object containing information about the fired event.
<anonymous> : MapperModelToViewPositionEventData
-
viewToModelPosition( eventInfo, <anonymous> )
module:engine/conversion/mapper~Mapper#event:viewToModelPosition
Fired 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(); } } );
Note: the default mapping callback is provided with a
low
priority setting and does not cancel the event, so it is possible to attach a custom callback after the default callback and also usedata.modelPosition
calculated by the default callback (for example to fix it).Note: the default mapping callback will not fire if
data.modelPosition
is 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 : EventInfo
An object containing information about the fired event.
<anonymous> : MapperViewToModelPositionEventData
Every day, we work hard to keep our documentation complete. Have you spotted outdated information? Is something missing? Please report it via our issue tracker.
With the release of version 42.0.0, we have rewritten much of our documentation to reflect the new import paths and features. We appreciate your feedback to help us ensure its accuracy and completeness.