s) as handled by this converter.
conversionApi.consumable.consume( viewInfoBoxTitle, { name: true } );
conversionApi.consumable.consume( viewInfoBoxContent, { name: true } );
// Let the editor handle the children of
.
conversionApi.convertChildren( viewInfoBoxContent, modelElement );
// Finally, update the conversion's modelRange and modelCursor.
conversionApi.updateConversionResult( modelElement, data );
}
// A helper function to read the type from the view classes.
function getTypeFromViewElement( viewElement ) {
if ( viewElement.hasClass( 'info-box-info' ) ) {
return 'Info';
}
if ( viewElement.hasClass( 'info-box-warning' ) ) {
return 'Warning';
}
return 'None';
}
```
This upcast converter callback can now be plugged by adding a listener to the {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#element `UpcastDispatcher#element` event}. You will listen to `element:div` to ensure that the callback is called only for `
` elements.
```js
editor.conversion.for( 'upcast' )
.add( dispatcher => dispatcher.on( 'element:div', upcastConverter ) );
```
### Event-based downcast converter
The missing bits are the downcast converters for the editing and data pipelines.
You will want to use the widget system to make the info box behave like an "object". Another aspect that you need to take care of is the fact that the view structure has more elements than the model structure. In this case, you could actually use one-way converters. However, this tutorial will showcase how an event-based converter would look.
See the {@link framework/guides/tutorials/implementing-a-block-widget Implementing a block widget} guide to learn about the widget system.
The remaining downcast converters:
```js
function editingDowncastConverter( event, data, conversionApi ) {
let { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi );
// Decorate view items as a widget and widget editable area.
infoBox = toWidget( infoBox, conversionApi.writer, { label: 'info box widget' } );
infoBoxContent = toWidgetEditable( infoBoxContent, conversionApi.writer );
insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent );
}
function dataDowncastConverter( event, data, conversionApi ) {
const { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi );
insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent );
}
function createViewElements( data, conversionApi ) {
const type = data.item.getAttribute( 'infoBoxType' );
const infoBox = conversionApi.writer.createContainerElement( 'div', {
class: `info-box info-box-${ type.toLowerCase() }`
} );
const infoBoxContent = conversionApi.writer.createEditableElement( 'div', {
class: 'info-box-content'
} );
const infoBoxTitle = conversionApi.writer.createUIElement( 'div',
{ class: 'info-box-title' },
function( domDocument ) {
const domElement = this.toDomElement( domDocument );
domElement.innerText = type;
return domElement;
} );
return { infoBox, infoBoxContent, infoBoxTitle };
}
function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent ) {
conversionApi.consumable.consume( data.item, 'insert' );
conversionApi.writer.insert(
conversionApi.writer.createPositionAt( infoBox, 0 ),
infoBoxTitle
);
conversionApi.writer.insert(
conversionApi.writer.createPositionAt( infoBox, 1 ),
infoBoxContent
);
// The default mapping between the model
and its view representation.
conversionApi.mapper.bindElements( data.item, infoBox );
// However, since the model content needs to end up in the inner
// , you need to bind one with another overriding
// a part of the default binding.
conversionApi.mapper.bindElements( data.item, infoBoxContent );
conversionApi.writer.insert(
conversionApi.mapper.toViewPosition( data.range.start ),
infoBox
);
}
```
These two converters need to be plugged as listeners to the {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#insert `DowncastDispatcher#insert` event}:
```js
editor.conversion.for( 'editingDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) );
editor.conversion.for( 'dataDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
```
### Updated plugin code
The updated `InfoBox` plugin that glues the event-based converters together:
```js
class InfoBox {
constructor( editor ) {
// Schema definition.
editor.model.schema.register( 'infoBox', {
allowWhere: '$block',
allowContentOf: '$root',
isObject: true,
allowAttributes: [ 'infoBoxType' ]
} );
// Upcast converter.
editor.conversion.for( 'upcast' )
.add( dispatcher => dispatcher.on( 'element:div', upcastConverter ) );
// The downcast conversion must be split as you need a widget in the editing pipeline.
editor.conversion.for( 'editingDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) );
editor.conversion.for( 'dataDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
}
}
function upcastConverter() {
// ...
}
function editingDowncastConverter() {
// ...
}
function dataDowncastConverter() {
// ...
}
```