Sign up (with export icon)

Downcast helpers – model to view conversion

Contribute to this guide Show the table of contents

This article lists all editor helpers available in the downcast conversion.

Element to element conversion helper

Copy link

Converting a model element to a view element is the most common case of conversion. It is used to create view elements like <p> or <h1>, that we call “container elements.”

When using the elementToElement() helper, a single model element will be converted to a single view element. The children of this model element need to have their own converters defined and the engine will recursively convert them and insert into the created view element.

Basic element to element conversion

Copy link

If you want to convert a model element to a simple view element without any additional attributes, simply provide their names through the converter, as shown in this example:

editor.conversion
    .for( 'downcast' )
    .elementToElement( {
        model: 'paragraphSeparator',
        view: 'hr'
} );
Copy code

Using view element definition

Copy link

Sometimes you may need to output a view element that has certain attributes, like a class name. To achieve this, you can provide an element definition in the view property:

editor.conversion
    .for( 'downcast' )
    .elementToElement( {
        model: 'fancyParagraph',
        view: {
            name: 'p',
            classes: 'fancy'
        }
    } );
Copy code

Check out the ViewElementObjectDefinition documentation for more details.

Creating a view element using a callback

Copy link

An alternative way to write a converter from the previous section using a callback would look as follows:

editor.conversion
    .for( 'downcast' )
    .elementToElement( {
        model: 'fancyParagraph',
        view: ( modelElement, { writer } ) => {
            return writer.createContainerElement(
                'p', { class: 'fancy' }
            );
        }
    } );
Copy code

Here, the second parameter of the view callback is the DowncastConversionApi object. It contains many properties and methods that can be useful when writing a more complex converters.

The callback should return a single container element. This element should not contain any children except UI elements. If you want to create a richer structure, use the elementToStructure() method.

Handling model elements with attributes

Copy link

If the view element does not only depend on the model element itself but also on its attributes, you need to specify these attributes in the model property.

editor.conversion
    .for( 'downcast' )
    .elementToElement( {
        model: {
            name: 'heading',
            attributes: [ 'level' ]
        },
        view: ( modelElement, { writer } ) => {
            return writer.createContainerElement(
                'h' + modelElement.getAttribute( 'level' )
            );
        }
    } );
Copy code
Note

If you forget to specify these attributes, the converter will still work for the insertion of the model element part but it will not handle any changes of the attribute value.

Changing converter priority

Copy link

In case there are other converters with overlapping model patterns already present, you can prioritize your converter to override these. To do that, use the converterPriority property:

editor.conversion
    .for( 'downcast' )
    .elementToElement( {
        model: 'userComment',
        view: 'div'
    } );

editor.conversion
    .for( 'downcast' )
    .elementToElement( {
        model: 'userComment',
        view: 'article',
        converterPriority: 'high'
    } );
Copy code

In the example above, the first converter has no explicitly set priority hence it assumes default priority, which is normal. The second one overrides it by setting the priority to high. Using both of these converters at once will result in the <userComment> element being converted to an <article> element.

This solution may also be handy if you want your converter to act as a fallback when other converters for a given element are not present (for example, a plugin has not been loaded). It can be easily achieved by setting the converterProperty to low.

Element to structure conversion helper

Copy link

Convert a single model element to multiple view elements (a structure of view elements).

Handling empty model elements

Copy link

To convert a single model element horizontalLine to the following structure:

<div class="horizontal-line">
    <hr />
</div>
Copy code

you can use a converter similar to this one:

editor.conversion
    .for( 'downcast' )
    .elementToStructure( {
        model: 'horizontalLine',
        view: ( modelElement, { writer } ) => {
            return writer.createContainerElement( 'div', { class: 'horizontal-line' }, [
                writer.createEmptyElement( 'hr' )
            ] );
        }
} );
Copy code

Note that in this example we create two elements, which is not possible by using the previously mentioned elementToElement() helper.

Note

For editor users, the best way to interact with complex structures is to act as independent entities and stay intact, for instance, when copied, pasted, and edited. CKEditor 5 allows that through the widget API. If you want to learn how to use it on top of elementToStructure(), be sure to check out the Implementing a block widget tutorial.

Handling model element’s children

Copy link

The example above uses an empty model element. If your model element may contain children, you need to specify where in the view these children should be placed. To do that, use the writer.createSlot() helper.

editor.conversion
    .for( 'downcast' )
    .elementToStructure( {
        model: 'wrappedParagraph',
        view: ( modelElement, conversionApi ) => {
            const { writer } = conversionApi;
            const paragraphViewElement = writer.createContainerElement( 'p', {}, [
                writer.createSlot()
            ] );

            return writer.createContainerElement( 'div', { class: 'wrapper' }, [
                paragraphViewElement
            ] );
        }
    } );
Copy code
Note

For editor users, the best way to interact with complex structures is to act as independent entities and stay intact, for instance, when copied, pasted, and edited. CKEditor 5 allows that through the widget API. If you want to learn how to use it on top of elementToStructure(), be sure to check out the Implementing a block widget tutorial.

Attribute to element conversion helper

Copy link

The attribute to element conversion is used to create formatting view elements like <b> or <span style="font-family: ..."> (that we call attribute elements). In this case, we do not convert a model element but a text node’s attribute. It is important to note that text formatting, such as bold or font size, should be represented in the model as text node attributes.

Note

In general, the model does not implement a concept of “inline elements” (in the sense in which they are defined by CSS). The only scenarios in which inline elements can be used, are self-contained objects such as soft breaks (<br>) or inline images.

Basic text attribute to model conversion

Copy link
editor.conversion
    .for( 'downcast' )
    .attributeToElement( {
        model: 'bold',
        view: 'strong'
    } );
Copy code

A model text node "CKEditor&nbsp;5" with a bold attribute will become a <strong>"CKEditor&nbsp;5"</strong> in the view.

Using view element definition

Copy link

You might want to output a view element that has more attributes, like a class name. To achieve that, you can provide an element definition in the view property:

editor.conversion
    .for( 'downcast' )
    .attributeToElement( {
        model: 'invert',
        view: {
            name: 'span',
            classes: [ 'font-light', 'bg-dark' ]
        }
    } );
Copy code

Check out the ViewElementObjectDefinition documentation for more details.

Creating a view element using a callback

Copy link

You can also generate the view element by a callback. This method is useful when the view element depends on the value of the model attribute.

editor.conversion
    .for( 'downcast' )
    .attributeToElement( {
        model: 'bold',
        view: ( modelAttributeValue, conversionApi ) => {
            const { writer } = conversionApi;

            return writer.createAttributeElement( 'span', {
                style: 'font-weight:' + modelAttributeValue
            } );
        }
    } );
Copy code

The second parameter of the view callback is the DowncastConversionApi object. It contains many properties and methods that can be useful when writing more complex converters.

Changing converter priority

Copy link

In case there are other converters already present, you can prioritize your converter to override the existing ones. To do that, use the converterPriority property:

editor.conversion
    .for( 'downcast' )
    .attributeToElement( {
        model: 'bold',
        view: 'strong'
    } );

editor.conversion
    .for( 'downcast' )
    .attributeToElement( {
        model: 'bold',
        view: 'b',
        converterPriority: 'high'
    } );
Copy code

In the example above, the first converter has no explicitly set priority hence it assumes default priority, which is normal. The second one overrides it by setting the priority to high. Using both of these converters at once will result in the bold attribute being converted to a <b> element.

Attribute to attribute conversion helper

Copy link

The attributeToAttribute() helper allows registering a converter that handles a specific attribute and converts it to an attribute of a view element.

Usually, when registering converters for elements (for example, by using elementToElement() or elementToStructure()), you will want to handle their attributes while handling the element itself.

The attributeToAttribute() helper comes in handy when for some reason you cannot cover a specific attribute inside the elementToElement() helper. For instance, when you are extending someone else’s plugin.

Note

This type of converter helper only works if there is already an element converter provided. Trying to convert to an attribute while there is no receiving view element will cause an error.

Basic attribute to attribute conversion

Copy link

This conversion results in adding an attribute to a view node, basing on an attribute from a model node. For example, <imageInline src='foo.jpg'></imageInline> is converted to <img src='foo.jpg'></img>.

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: 'source',
        view: 'src'
    } );
Copy code

Converting specific model element and attribute

Copy link

The converter in the example above will convert all the source model attributes in the document. You can limit its scope by providing the model element name. You achieve it with the following code:

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: {
            name: 'imageInline',
            key: 'source'
        },
        view: 'src'
    } );
Copy code

This updated converter will only convert the source model attributes present on the imageInline model element.

Creating a custom view element from a selected list of model values

Copy link

Once you provide the array in the model.values property, the view property is expected to be an object with keys matching these values. This is best explained using the example below:

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: {
            name: 'styled',
            values: [ 'dark', 'light' ]
        },
        view: {
            dark: {
                key: 'class',
                value: [ 'styled', 'styled-dark' ]
            },
            light: {
                key: 'class',
                value: [ 'styled', 'styled-light' ]
            }
        }
    } );
Copy code

Creating a view attribute with a custom value based on the model value

Copy link

The value of the view attribute can be modified in the converter. Below is a simple mapper that sets the class attribute based on the model attribute value:

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: 'styled',
        view: modelAttributeValue => ( {
            key: 'class',
            value: 'styled-' + modelAttributeValue
        } )
    } );
Copy code

It is worth noting that providing a style property in this manner requires the returned value to be an object:

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: 'lineHeight',
        view: modelAttributeValue => ( {
            key: 'style',
            value: {
                'line-height': modelAttributeValue,
                'border-bottom': '1px dotted #ba2'
            }
        } )
    } );
Copy code

Changing converter priority

Copy link

You can override the existing converters by specifying higher priority, like in the example below:

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: 'source',
        view: 'href'
    } );

editor.conversion
    .for( 'downcast' )
    .attributeToAttribute( {
        model: 'source',
        view: 'src',
        converterPriority: 'high'
    } );
Copy code

First converter has the default priority, normal. The second converter will be called earlier because of its higher priority, thus the source model attribute will get converted to src view attribute instead of href.

Further reading

Copy link

Check out the dedicated guide with a full list of complementary upcast conversion helpers.