VisualEditor/API/Data Model/Surface

A surface contains a document, selection and history of transactions that have been processed over time. When working with a surface, a surface fragment object is used to abstract away the complexity of getting information about a surface and making changes to it while keeping everything in sync. Interactions with this API are similar to working with a jQuery selection in many ways, which is done intentionally to make it easier to learn.

Data Model
It's important to understand the data model that VisualEditor uses during editing. Parsoid converts Wikitext into HTML because HTML is easier to work with, especially inside a web browser, than Wikitext. However, HTML is still not ideal for transactional editing – where each change is differential to a previous state – and needs to be converted into a linear data structure. The structure used by VisualEditor is often referred to as the linear data model. It's essentially an HTML token stream with the exception that elements are not used for text formatting. Instead, annotations are composed onto formatted characters, allowing arbitrary slices of data to be as self-descriptive as possible with minimal processing. Upon saving a page, the linear data model is converted back to HTML and then subsequently Wikitext.

Conversion process
To better understand how conversion works, let's take a look at how a simple page looks at each stage in the process.

If the page contains this wikitext: Hello world  !

Parsoid produces this HTML: &lt;p&gt; Hello &lt;b&gt;World&lt;/b&gt; ! &lt;/p&gt;

VisualEditor will convert that into this data structure: // bold === { type:’textStyle/bold’ } [    { type: ’paragraph’ } , ’H’ , ’e’ , ’l’ , ’l’ , ’o’ , ’ ’,    [ ’W’, { ’{type:”textStyle/bold”}’: bold  }, [ bold ] ] , [ ’o’, { ’{type:”textStyle/bold”}’: bold }, [ bold ] ] , [ ’r’, { ’{type:”textStyle/bold”}’: bold }, [ bold ] ] , [ ’l’, { ’{type:”textStyle/bold”}’: bold }, [ bold ] ] , [ ’d’, { ’{type:”textStyle/bold”}’: bold }, [ bold ] ] , ’!’,    { type: ’/paragraph’ } ]

Which, more succinctly, can be represented like this:

Selection
In the user interface, a user describes where in the document they want to change by selecting. Although rendered very differently, a blinking cursor is just a zero-length selection. This selection mechanism is used throughout VisualEditor, and especially within the document through the use of ranges. A range is simply a pair of 2 offsets, from and to. Because selection is sometimes drawn from a high offset to a low offset (opposite of the reading direction) it's common to work with the start and end values of a range, which are always in ascending order respectively.

Working with ranges
Many functions take ranges as arguments, while others provide ranges as return values. In the case that you need to create a new range, the ve.Range class can be instantiated with from and to arguments, each an offset within a document.

A common way that a range object is obtained is when getting the current selection.

You can also modify the selection of a surface by providing a range object as an argument. When the selection is changed on the model, the user interface responds by selecting the corresponding content in the DOM.

Ranges that cover at least one offset are visually described using a pair of facing square brackets:

Zero length ranges are represented using a single pipe.

Chaining
Once you have a fragment, you can get information about the surface using a getter function, get another fragment based on the one you have, or make changes to the document. The latter two of these classes of operations are chainable.

Null fragments
Sometimes getting a new fragment based on the one you have results in an invalid fragment, such as asking to expand the range to the nearest paragraph while in a pre-formatted node. In this case a null fragment is returned, from which all getters return empty values, only null fragments can be created and changes to the document are ignored.

Ranges
Each fragment's range will automatically be updated to remain relevant any time a document is modified. This is done internally by using the ve.dm.Transaction.translateRange feature.

ve.dm.SurfaceFragment( surface, range )
Selected portion of a surface.
 * surface {ve.dm.Surface}
 * Target surface


 * range {ve.Range} [optional]
 * Range within target document, current selection used by default


 * isNull
 * Responds to transactions being processed on the document
 * Returns  {Boolean}  Fragment is a null fragment


 * adjustRange( start, end )
 * Gets a new fragment with an adjusted position
 * Returns  {ve.dm.SurfaceFragment}  Adjusted fragment


 * start {Number} [optional]
 * Adjustment for start position


 * end {Number} [optional]
 * Adjustment for end position