s) as handled by this converter.
conversionApi.consumable.consume( viewInfoBoxTitle, { name: true } );
conversionApi.consumable.consume( viewInfoBoxContent, { name: true } );
// Let the editor handle children of
.
conversionApi.convertChildren( viewInfoBoxContent, modelElement );
// Finally, update the conversion's modelRange and modelCursor.
conversionApi.updateConversionResult( modelElement, data );
}
// 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}. We 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 bit are the downcast converters for the editing and data pipelines. We want to use the widget system to make the info box behave like an "object". The other aspect that we need to take care of is the fact that the view structure has more elements than the model structure. In this case, we could actually use one-way converters. However, we will showcase how an event-based converter would look.
See the {@link framework/guides/tutorials/implementing-a-block-widget Implementing a block widget} 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 need to end up in the inner
// we need to bind one with another overriding
// 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 all this 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 we 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() {
// ...
}
```