Position (engine/model)
@ckeditor/ckeditor5-engine/src/model/position
Represents a position in the model tree.
A position is represented by its root and
a path in that root.
You can create position instances via its constructor or the createPosition*() factory methods of
Model and Writer.
Note: Position is based on offsets, not indexes. This means that a position between two text nodes
foo and bar has offset 3, not 1. See path for more information.
Since a position in the model is represented by a position root and position path it is possible to create positions placed in non-existing places. This requirement is important for operational transformation algorithms.
Also, operations kept in the document history are storing positions (and ranges) which were correct when those operations were applied, but may not be correct after the document has changed.
When changes are applied to the model, it may also happen that position parent
will change even if position path has not changed. Keep in mind, that if a position leads to non-existing element,
parent and some other properties and methods will throw errors.
In most cases, position with wrong path is caused by an error in code, but it is sometimes needed, as described above.
Filtering
Properties
-
Is
trueif position is at the end of its parent,falseotherwise. -
Is
trueif position is at the beginning of its parent,falseotherwise. -
Node directly after this position. Returns
nullif this position is at the end of its parent, or if it is in a text node. -
Node directly before this position. Returns
nullif this position is at the start of its parent, or if it is in a text node. -
offset : numbermodule:engine/model/position~Position#offset -
Parent element of this position.
Keep in mind that
parentvalue is calculated when the property is accessed. If position path leads to a non-existing element,parentproperty will throw error.Also it is a good idea to cache
parentproperty if it is used frequently in an algorithm (i.e. in a long loop). -
Position of the node in the tree. Path contains offsets, not indexes.
Position can be placed before, after or in a node if that node has
offsetSizegreater than1. Items in position path are starting offsets of position ancestors, starting from direct root children, down to the position offset in it's parent.ROOT |- P before: [ 0 ] after: [ 1 ] |- UL before: [ 1 ] after: [ 2 ] |- LI before: [ 1, 0 ] after: [ 1, 1 ] | |- foo before: [ 1, 0, 0 ] after: [ 1, 0, 3 ] |- LI before: [ 1, 1 ] after: [ 1, 2 ] |- bar before: [ 1, 1, 0 ] after: [ 1, 1, 3 ]fooandbarare representing text nodes. Since text nodes has offset size greater than1you can place position offset between their start and end:ROOT |- P |- UL |- LI | |- f^o|o ^ has path: [ 1, 0, 1 ] | has path: [ 1, 0, 2 ] |- LI |- b^a|r ^ has path: [ 1, 1, 1 ] | has path: [ 1, 1, 2 ] -
Root of the position path.
-
stickiness : PositionStickinessmodule:engine/model/position~Position#stickinessPosition stickiness. See
PositionStickiness. -
Returns text node instance in which this position is placed or
nullif this position is not in a text node.
Methods
-
constructor( root, path, stickiness )module:engine/model/position~Position#constructorCreates a position.
Parameters
root : Element | DocumentFragmentRoot of the position.
path : readonly Array<number>Position path. See
path.stickiness : PositionStickinessPosition stickiness. See
PositionStickiness.Defaults to
'toNone'
-
clone() → thismodule:engine/model/position~Position#clone -
compareWith( otherPosition ) → PositionRelationmodule:engine/model/position~Position#compareWithChecks whether this position is before or after given position.
This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
otherPosition : Position
Returns
-
findAncestor( parentName ) → null | Elementmodule:engine/model/position~Position#findAncestorReturns the parent element of the given name. Returns null if the position is not inside the desired parent.
Parameters
parentName : stringThe name of the parent element to find.
Returns
null | Element
-
getAncestors() → Array<Element | DocumentFragment>module:engine/model/position~Position#getAncestorsReturns ancestors array of this position, that is this position's parent and its ancestors.
Returns
Array<Element | DocumentFragment>Array with ancestors.
-
getCommonAncestor( position ) → null | Element | DocumentFragmentmodule:engine/model/position~Position#getCommonAncestorReturns an
ElementorDocumentFragmentwhich is a common ancestor of both positions. The roots of these two positions must be identical.Parameters
position : PositionThe second position.
Returns
null | Element | DocumentFragment
-
getCommonPath( position ) → Array<number>module:engine/model/position~Position#getCommonPathReturns the slice of two position paths which is identical. The roots of these two paths must be identical.
This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
position : PositionThe second position.
Returns
Array<number>The common path.
-
getLastMatchingPosition( skip, options ) → Positionmodule:engine/model/position~Position#getLastMatchingPositionGets the farthest position which matches the callback using TreeWalker.
For example:
getLastMatchingPosition( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph> getLastMatchingPosition( value => value.type == 'text', { direction: 'backward' } ); // <paragraph>foo[]</paragraph> -> <paragraph>[]foo</paragraph> getLastMatchingPosition( value => false ); // Do not move the position.Parameters
skip : ( value: TreeWalkerValue ) => booleanCallback function. Gets
TreeWalkerValueand should returntrueif the value should be skipped orfalseif not.options : TreeWalkerOptionsObject with configuration options. See
TreeWalker.Defaults to
{}
Returns
PositionThe position after the last item which matches the
skipcallback test.
-
getParentPath() → Array<number>module:engine/model/position~Position#getParentPathReturns a path to this position's parent. Parent path is equal to position path but without the last item.
This method is safe to use it on non-existing positions (for example during operational transformation).
Returns
Array<number>Path to the parent.
-
getShiftedBy( shift ) → Positionmodule:engine/model/position~Position#getShiftedByReturns a new instance of
Position, that has same parent but it's offset is shifted byshiftvalue (can be a negative value).This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
shift : numberOffset shift. Can be a negative value.
Returns
PositionShifted position.
-
getTransformedByOperation( operation ) → Positionmodule:engine/model/position~Position#getTransformedByOperationReturns a copy of this position that is transformed by given
operation.The new position's parameters are updated accordingly to the effect of the
operation.For example, if
nnodes are inserted before the position, the returned positionoffsetwill be increased byn. If the position was in a merged element, it will be accordingly moved to the new element, etc.This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
operation : OperationOperation to transform by.
Returns
PositionTransformed position.
-
hasSameParentAs( position ) → booleanmodule:engine/model/position~Position#hasSameParentAsChecks if two positions are in the same parent.
This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
position : PositionPosition to compare with.
Returns
booleantrueif positions have the same parent,falseotherwise.
-
inherited
is( type ) → this is Element | RootElementmodule:engine/model/position~Position#is:ELEMENTChecks whether the object is of type
Elementor its subclass.element.is( 'element' ); // -> true element.is( 'node' ); // -> true element.is( 'model:element' ); // -> true element.is( 'model:node' ); // -> true element.is( 'view:element' ); // -> false element.is( 'documentSelection' ); // -> falseAssuming that the object being checked is an element, you can also check its name:
element.is( 'element', 'imageBlock' ); // -> true if this is an <imageBlock> element text.is( 'element', 'imageBlock' ); -> falseParameters
type : 'element' | 'model:element'
Returns
this is Element | RootElement
-
Checks whether the object is of type
RootElement.rootElement.is( 'rootElement' ); // -> true rootElement.is( 'element' ); // -> true rootElement.is( 'node' ); // -> true rootElement.is( 'model:rootElement' ); // -> true rootElement.is( 'model:element' ); // -> true rootElement.is( 'model:node' ); // -> true rootElement.is( 'view:element' ); // -> false rootElement.is( 'documentFragment' ); // -> falseAssuming that the object being checked is an element, you can also check its name:
rootElement.is( 'rootElement', '$root' ); // -> same as aboveParameters
type : 'rootElement' | 'model:rootElement'
Returns
this is RootElement
-
inherited
is( type ) → this is Position | LivePositionmodule:engine/model/position~Position#is:POSITIONChecks whether the object is of type
Positionor its subclass.position.is( 'position' ); // -> true position.is( 'model:position' ); // -> true position.is( 'view:position' ); // -> false position.is( 'documentSelection' ); // -> falseParameters
type : 'position' | 'model:position'
Returns
this is Position | LivePosition
-
Checks whether the object is of type
Rangeor its subclass.range.is( 'range' ); // -> true range.is( 'model:range' ); // -> true range.is( 'view:range' ); // -> false range.is( 'documentSelection' ); // -> falseParameters
type : 'range' | 'model:range'
Returns
-
Checks whether the object is of type
LivePosition.livePosition.is( 'position' ); // -> true livePosition.is( 'model:position' ); // -> true livePosition.is( 'liveposition' ); // -> true livePosition.is( 'model:livePosition' ); // -> true livePosition.is( 'view:position' ); // -> false livePosition.is( 'documentSelection' ); // -> falseParameters
type : 'livePosition' | 'model:livePosition'
Returns
this is LivePosition
-
Checks whether the object is of type
Text.text.is( '$text' ); // -> true text.is( 'node' ); // -> true text.is( 'model:$text' ); // -> true text.is( 'model:node' ); // -> true text.is( 'view:$text' ); // -> false text.is( 'documentSelection' ); // -> falseNote: Until version 20.0.0 this method wasn't accepting
'$text'type. The legacy'text'type is still accepted for backward compatibility.Parameters
type : '$text' | 'model:$text'
Returns
this is Text
-
inherited
is( type ) → this is DocumentFragmentmodule:engine/model/position~Position#is:DOCUMENT_FRAGMENTChecks whether the object is of type
DocumentFragment.docFrag.is( 'documentFragment' ); // -> true docFrag.is( 'model:documentFragment' ); // -> true docFrag.is( 'view:documentFragment' ); // -> false docFrag.is( 'element' ); // -> false docFrag.is( 'node' ); // -> falseParameters
type : 'documentFragment' | 'model:documentFragment'
Returns
this is DocumentFragment
-
Checks whether the object is of type
TextProxy.textProxy.is( '$textProxy' ); // -> true textProxy.is( 'model:$textProxy' ); // -> true textProxy.is( 'view:$textProxy' ); // -> false textProxy.is( 'range' ); // -> falseNote: Until version 20.0.0 this method wasn't accepting
'$textProxy'type. The legacy'textProxt'type is still accepted for backward compatibility.Parameters
type : '$textProxy' | 'model:$textProxy'
Returns
this is TextProxy
-
Checks whether the object is of type
RootElementand has the specifiedname.rootElement.is( 'rootElement', '$root' );Type parameters
N : extends string
Parameters
type : 'rootElement' | 'model:rootElement'name : N
Returns
boolean
-
Checks whether the object is of type
Elementor its subclass and has the specifiedname.element.is( 'element', 'imageBlock' ); // -> true if this is an <imageBlock> element text.is( 'element', 'imageBlock' ); -> falseType parameters
N : extends string
Parameters
type : 'element' | 'model:element'name : N
Returns
boolean
-
inherited
is( type ) → this is DocumentSelectionmodule:engine/model/position~Position#is:DOCUMENT_SELECTIONChecks whether the object is of type
DocumentSelection.selection.is( 'selection' ); // -> true selection.is( 'documentSelection' ); // -> true selection.is( 'model:selection' ); // -> true selection.is( 'model:documentSelection' ); // -> true selection.is( 'view:selection' ); // -> false selection.is( 'element' ); // -> false selection.is( 'node' ); // -> falseParameters
type : 'documentSelection' | 'model:documentSelection'
Returns
this is DocumentSelection
-
inherited
is( type ) → this is Selection | DocumentSelectionmodule:engine/model/position~Position#is:SELECTIONChecks whether the object is of type
SelectionorDocumentSelection.selection.is( 'selection' ); // -> true selection.is( 'model:selection' ); // -> true selection.is( 'view:selection' ); // -> false selection.is( 'range' ); // -> falseParameters
type : 'selection' | 'model:selection'
Returns
this is Selection | DocumentSelection
-
Checks whether the object is of type
LiveRange.liveRange.is( 'range' ); // -> true liveRange.is( 'model:range' ); // -> true liveRange.is( 'liveRange' ); // -> true liveRange.is( 'model:liveRange' ); // -> true liveRange.is( 'view:range' ); // -> false liveRange.is( 'documentSelection' ); // -> falseParameters
type : 'liveRange' | 'model:liveRange'
Returns
this is LiveRange
-
inherited
is( type ) → this is Node | Text | Element | RootElementmodule:engine/model/position~Position#is:NODEChecks whether the object is of type
Nodeor its subclass.This method is useful when processing model objects that are of unknown type. For example, a function may return a
DocumentFragmentor aNodethat can be either a text node or an element. This method can be used to check what kind of object is returned.someObject.is( 'element' ); // -> true if this is an element someObject.is( 'node' ); // -> true if this is a node (a text node or an element) someObject.is( 'documentFragment' ); // -> true if this is a document fragmentSince this method is also available on a range of view objects, you can prefix the type of the object with
model:orview:to check, for example, if this is the model's or view's element:modelElement.is( 'model:element' ); // -> true modelElement.is( 'view:element' ); // -> falseBy using this method it is also possible to check a name of an element:
imageElement.is( 'element', 'imageBlock' ); // -> true imageElement.is( 'element', 'imageBlock' ); // -> same as above imageElement.is( 'model:element', 'imageBlock' ); // -> same as above, but more preciseParameters
type : 'node' | 'model:node'
Returns
this is Node | Text | Element | RootElement
-
isAfter( otherPosition ) → booleanmodule:engine/model/position~Position#isAfterChecks whether this position is after given position.
This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
otherPosition : PositionPosition to compare with.
Returns
booleanTrue if this position is after given position.
Related:
-
isBefore( otherPosition ) → booleanmodule:engine/model/position~Position#isBeforeChecks whether this position is before given position.
Note: watch out when using negation of the value returned by this method, because the negation will also be
trueif positions are in different roots and you might not expect this. You should probably usea.isAfter( b ) || a.isEqual( b )or!a.isBefore( p ) && a.root == b.rootin most scenarios. If your condition uses multipleisAfterandisBeforechecks, build them so they do not use negated values, i.e.:if ( a.isBefore( b ) && c.isAfter( d ) ) { // do A. } else { // do B. }or, if you have only one if-branch:
if ( !( a.isBefore( b ) && c.isAfter( d ) ) { // do B. }rather than:
if ( !a.isBefore( b ) || && !c.isAfter( d ) ) { // do B. } else { // do A. }This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
otherPosition : PositionPosition to compare with.
Returns
booleanTrue if this position is before given position.
-
isEqual( otherPosition ) → booleanmodule:engine/model/position~Position#isEqualChecks whether this position is equal to given position.
This method is safe to use it on non-existing positions (for example during operational transformation).
Parameters
otherPosition : PositionPosition to compare with.
Returns
booleanTrue if positions are same.
-
isTouching( otherPosition ) → booleanmodule:engine/model/position~Position#isTouchingChecks whether this position is touching given position. Positions touch when there are no text nodes or empty nodes in a range between them. Technically, those positions are not equal but in many cases they are very similar or even indistinguishable.
Parameters
otherPosition : PositionPosition to compare with.
Returns
booleanTrue if positions touch.
-
isValid() → booleanmodule:engine/model/position~Position#isValidChecks whether the position is valid in current model tree, that is whether it points to an existing place in the model.
Returns
boolean
-
toJSON() → unknownmodule:engine/model/position~Position#toJSON -
internal
_getCombined( source, target ) → Positionmodule:engine/model/position~Position#_getCombinedReturns a new position that is a combination of this position and given positions.
The combined position is a copy of this position transformed by moving a range starting at
sourceposition to thetargetposition. It is expected that this position is inside the moved range.Example:
let original = model.createPositionFromPath( root, [ 2, 3, 1 ] ); let source = model.createPositionFromPath( root, [ 2, 2 ] ); let target = model.createPositionFromPath( otherRoot, [ 1, 1, 3 ] ); original._getCombined( source, target ); // path is [ 1, 1, 4, 1 ], root is `otherRoot`Explanation:
We have a position
[ 2, 3, 1 ]and move some nodes from[ 2, 2 ]to[ 1, 1, 3 ]. The original position was inside moved nodes and now should point to the new place. The moved nodes will be after positions[ 1, 1, 3 ],[ 1, 1, 4 ],[ 1, 1, 5 ]. Since our position was in the second moved node, the transformed position will be in a sub-tree of a node at[ 1, 1, 4 ]. Looking at original path, we took care of[ 2, 3 ]part of it. Now we have to add the rest of the original path to the transformed path. Finally, the transformed position will point to[ 1, 1, 4, 1 ].Parameters
source : PositionBeginning of the moved range.
target : PositionPosition where the range is moved.
Returns
PositionCombined position.
-
internal
_getTransformedByDeletion( deletePosition, howMany ) → null | Positionmodule:engine/model/position~Position#_getTransformedByDeletionReturns a copy of this position that is updated by removing
howManynodes starting fromdeletePosition. It may happen that this position is in a removed node. If that is the case,nullis returned instead.Parameters
deletePosition : PositionPosition before the first removed node.
howMany : numberHow many nodes are removed.
Returns
null | PositionTransformed position or
null.
-
internal
_getTransformedByInsertOperation( operation ) → Positionmodule:engine/model/position~Position#_getTransformedByInsertOperationReturns a copy of this position transformed by an insert operation.
Parameters
operation : InsertOperation
Returns
-
internal
_getTransformedByInsertion( insertPosition, howMany ) → Positionmodule:engine/model/position~Position#_getTransformedByInsertion -
internal
_getTransformedByMergeOperation( operation ) → Positionmodule:engine/model/position~Position#_getTransformedByMergeOperationReturns a copy of this position transformed by merge operation.
Parameters
operation : MergeOperation
Returns
-
internal
_getTransformedByMove( sourcePosition, targetPosition, howMany ) → Positionmodule:engine/model/position~Position#_getTransformedByMoveReturns a copy of this position that is updated by moving
howManynodes fromsourcePositiontotargetPosition.Parameters
sourcePosition : PositionPosition before the first element to move.
targetPosition : PositionPosition where moved elements will be inserted.
howMany : numberHow many consecutive nodes to move, starting from
sourcePosition.
Returns
PositionTransformed position.
-
internal
_getTransformedByMoveOperation( operation ) → Positionmodule:engine/model/position~Position#_getTransformedByMoveOperationReturns a copy of this position transformed by a move operation.
Parameters
operation : MoveOperation
Returns
-
internal
_getTransformedBySplitOperation( operation ) → Positionmodule:engine/model/position~Position#_getTransformedBySplitOperationReturns a copy of this position transformed by a split operation.
Parameters
operation : SplitOperation
Returns
Static methods
-
internal static
_createAfter( item, [ stickiness ] ) → Positionmodule:engine/model/position~Position._createAfterCreates a new position, after given model item.
Parameters
item : DocumentFragment | ItemItem after which the position should be placed.
[ stickiness ] : PositionStickinessPosition stickiness.
Returns
-
internal static
_createAt( itemOrPosition, [ offset ], stickiness ) → Positionmodule:engine/model/position~Position._createAtCreates position at the given location. The location can be specified as:
- a position,
- parent element and offset (offset defaults to
0), - parent element and
'end'(sets position at the end of that element), - model item and
'before'or'after'(sets position before or after given model item).
This method is a shortcut to other factory methods such as:
Parameters
itemOrPosition : DocumentFragment | Position | Item[ offset ] : PositionOffsetOffset or one of the flags. Used only when the first parameter is a model item.
stickiness : PositionStickinessPosition stickiness. Used only when the first parameter is a model item.
Defaults to
'toNone'
Returns
-
internal static
_createBefore( item, [ stickiness ] ) → Positionmodule:engine/model/position~Position._createBeforeCreates a new position, before the given model item.
Parameters
item : DocumentFragment | ItemItem before which the position should be placed.
[ stickiness ] : PositionStickinessPosition stickiness.
Returns
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.