Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

T/1198: ViewElementConfig with helper converters. #1205

Merged
merged 28 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2b4352a
Other: Initial implementation of unified converters from ViewElementD…
jodator Dec 5, 2017
ea95029
Other: Unify ViewElement converters for attribute and element.
jodator Dec 11, 2017
1e28cba
Other: Rename ViewElementDefinition attributes to plural names.
jodator Dec 11, 2017
61ea426
Tests: Add tests for Element.fromViewDefinition() method.
jodator Dec 11, 2017
b2f1044
Tests: Add tests for new conversion helpers.
jodator Dec 12, 2017
a54727d
Other: Rename AttributeElement conversion helpers.
jodator Dec 12, 2017
d7141cc
Other: Rename ContainerElement conversion helpers.
jodator Dec 12, 2017
7381a09
Other: Merge configuration defined converters into one file.
jodator Dec 14, 2017
291d889
Docs: Update module:engine/conversion/configurationdefinedconverters …
jodator Dec 14, 2017
1328204
Merge branch 'master' into t/1198
jodator Dec 14, 2017
f80abc2
Docs: Update configuration defined converters docs and method names.
jodator Dec 14, 2017
2ac20cc
Other: Revert changes in matcher.
jodator Dec 14, 2017
4604f04
Tests: Update tests description in configurationdefinedconverters.js.
jodator Dec 14, 2017
cda98ba
Merge branch 'master' into t/1198
jodator Dec 17, 2017
16cae1d
Changed: configuration defined converts should not alter definition a…
jodator Dec 19, 2017
67de247
Other: Rename configurationdefinedconverters to definition-based-conv…
jodator Dec 19, 2017
7d30fa1
Docs: Refine definition-based-converters documentation.
jodator Dec 19, 2017
86be1e7
Refactor: Rename `view.Element.fromViewDefinition()` to `view.Element…
jodator Dec 19, 2017
e2ce318
Docs: Update ViewElementDefinition description.
jodator Dec 19, 2017
a48f922
Refactor: Use singular class and styles in ViewElementDefinition.
jodator Dec 19, 2017
f6b5676
Other: Rename internal variables of definition-based-converters to av…
jodator Dec 21, 2017
b77b76f
Other: Move static method `createFromDefinition` from `view:Element` …
jodator Dec 21, 2017
42848f7
Docs: Fix documentation links.
jodator Dec 21, 2017
55f7555
Merge branch 'master' into t/1198
jodator Dec 21, 2017
5e4ebda
Changed: Pass all `ConverterDefinition`s to modelAttributeToViewAttri…
jodator Dec 21, 2017
211d748
Merge branch 'master' into t/1198
Reinmar Dec 21, 2017
548734e
API docs fixes.
Reinmar Dec 21, 2017
4760cce
Typo fixes.
jodator Dec 21, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 312 additions & 0 deletions src/conversion/definition-based-converters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module engine/conversion/definition-based-converters
*/

import AttributeElement from '../view/attributeelement';
import ViewContainerElement from '../view/containerelement';

import buildModelConverter from './buildmodelconverter';
import buildViewConverter from './buildviewconverter';

/**
* Helper for creating model to view converter from model's element
* to {@link module:engine/view/containerelement~ContainerElement}.
*
* By defining conversion as simple model element to view element conversion using simplified definition:
*
* modelElementToViewContainerElement( {
* model: 'heading1',
* view: 'h1',
* }, [ editor.editing.modelToView, editor.data.modelToView ] );
*
* Or defining full-flavored view object:
*
* modelElementToViewContainerElement( {
* model: 'heading1',
* view: {
* name: 'h1',
* class: [ 'header', 'article-header' ],
* attribute: {
* data-header: 'level-1',
* }
* },
* }, [ editor.editing.modelToView, editor.data.modelToView ] );
*
* Above will generate the following view element:
*
* <h1 class="header article-header" data-header="level-1">...</h1>
*
* @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
* @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
*/
export function modelElementToViewContainerElement( definition, dispatchers ) {
const { model: modelElement, targetView } = normalizeConverterDefinition( definition );

buildModelConverter()
.for( ...dispatchers )
.fromElement( modelElement )
.toElement( () => createViewElementFromDefinition( targetView, ViewContainerElement ) );
}

/**
* Helper for creating view to model element converter. It will convert also all matched view elements defined in
* `acceptAlso` property. The `model` property is used as model element name.
*
* Conversion from model to view might be defined as simple one to one conversion:
*
* viewToModelElement( { model: 'heading1', view: 'h1' }, [ dispatcher ] );
*
* As a full-flavored definition:
*
* viewToModelElement( {
* model: 'heading1',
* view: {
* name: 'p',
* attribute: {
* 'data-heading': 'true'
* },
* // You may need to use a high-priority listener to catch elements
* // which are handled by other (usually – more generic) converters too.
* priority: 'high'
* }
* }, [ editor.data.viewToModel ] );
*
* or with `acceptAlso` property to match many elements:
*
* viewToModelElement( {
* model: 'heading1',
* view: 'h1',
* acceptAlso: [
* { name: 'p', attribute: { 'data-heading': 'level1' }, priority: 'high' },
* { name: 'h2', class: 'heading-main' },
* { name: 'div', style: { 'font-weight': 'bold', font-size: '24px' } }
* ]
* }, [ editor.data.viewToModel ] );
*
* The above example will convert an existing view elements:
*
* <h1>A heading</h1>
* <h2 class="heading-main">Another heading</h2>
* <p data-heading="level1">Paragraph-like heading</p>
* <div style="font-size:24px; font-weigh:bold;">Another non-semantic header</div>
*
* into `heading1` model elements so in model it will be represented as:
*
* <heading1>A heading</heading1>
* <heading1>Another heading</heading1>
* <heading1>Paragraph-like heading</heading1>
* <heading1>Another non-semantic header</heading1>
*
* @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
* @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
*/
export function viewToModelElement( definition, dispatchers ) {
const { model: modelElement, sourceViews } = normalizeConverterDefinition( definition );

const converter = prepareViewConverter( dispatchers, sourceViews );

converter.toElement( modelElement );
}

/**
* Helper for creating model to view converter from model's attribute
* to {@link module:engine/view/attributeelement~AttributeElement}.
*
* By defining conversion as simple model element to view element conversion using simplified definition:
*
* modelAttributeToViewAttributeElement( 'bold', {
* model: 'true',
* view: 'strong',
* }, [ editor.editing.modelToView, editor.data.modelToView ] );
*
* Or defining full-flavored view object:
*
* modelAttributeToViewAttributeElement( 'fontSize', {
* model: 'big',
* view: {
* name: 'span',
* style: {
* 'font-size': '1.2em'
* }
* },
* }, [ editor.editing.modelToView, editor.data.modelToView ] );
*
* Above will generate the following view element for model's attribute `fontSize` with a `big` value set:
*
* <span style="font-size:1.2em;">...</span>
*
* @param {String} attributeName The name of the model attribute which should be converted.
* @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
* @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers
*/
export function modelAttributeToViewAttributeElement( attributeName, definition, dispatchers ) {
const { model: attributeValue, targetView } = normalizeConverterDefinition( definition );

buildModelConverter()
.for( ...dispatchers )
.fromAttribute( attributeName )
.toElement( value => {
if ( value != attributeValue ) {
return;
}

return createViewElementFromDefinition( targetView, AttributeElement );
} );
}

/**
* Helper for creating view to model converter from view to model attribute. It will convert also all matched view elements defined in
* `acceptAlso` property. The `model` property is used as model's attribute value to match.
*
* Conversion from model to view might be defined as simple one to one conversion:
*
* viewToModelAttribute( 'bold', { model: true, view: 'strong' }, [ dispatcher ] );
*
* As a full-flavored definition:
*
* viewToModelAttribute( 'fontSize', {
* model: 'big',
* view: {
* name: 'span',
* style: {
* 'font-size': '1.2em'
* }
* }
* }, [ editor.data.viewToModel ] );
*
* or with `acceptAlso` property to match many elements:
*
* viewToModelAttribute( 'fontSize', {
* model: 'big',
* view: {
* name: 'span',
* class: 'text-big'
* },
* acceptAlso: [
* { name: 'span', attribute: { 'data-size': 'big' } },
* { name: 'span', class: [ 'font', 'font-huge' ] },
* { name: 'span', style: { font-size: '18px' } }
* ]
* }, [ editor.data.viewToModel ] );
*
* The above example will convert an existing view elements:
*
* <p>
* An example text with some big elements:
* <span class="text-big>one</span>,
* <span data-size="big>two</span>,
* <span class="font font-huge>three</span>,
* <span style="font-size:18px>four</span>
* </p>
*
* into `fontSize` model attribute with 'big' value set so it will be represented:
*
* <paragraph>
* An example text with some big elements:
* <$text fontSize="big>one</$text>,
* <$text fontSize="big>two</$text>,
* <$text fontSize="big>three</$text>,
* <$text fontSize="big>four</$text>
* </paragraph>
*
* @param {String} attributeName Attribute name to which convert.
* @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration.
* @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
*/
export function viewToModelAttribute( attributeName, definition, dispatchers ) {
const { model: attributeValue, sourceViews } = normalizeConverterDefinition( definition );

const converter = prepareViewConverter( dispatchers, sourceViews );

converter.toAttribute( () => ( {
key: attributeName,
value: attributeValue
} ) );
}

// Normalize a {@link module:engine/conversion/definition-based-converters~ConverterDefinition}
// into internal object used when building converters.
//
// @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition An object that defines view to model
// and model to view conversion.
// @returns {Object}
function normalizeConverterDefinition( definition ) {
const model = definition.model;
const view = definition.view;

// View definition might be defined as a name of an element.
const targetView = typeof view == 'string' ? { name: view } : view;

const sourceViews = Array.from( definition.acceptsAlso ? definition.acceptsAlso : [] );

// Source views also accepts default view definition used in model-to-view conversion.
sourceViews.push( targetView );

return { model, targetView, sourceViews };
}

// Helper method for preparing a view converter from passed view definitions.
//
// @param {Array.<module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher>} dispatchers
// @param {Array.<module:engine/view/viewelementdefinition~ViewElementDefinition>} viewDefinitions
// @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder}
function prepareViewConverter( dispatchers, viewDefinitions ) {
const converter = buildViewConverter().for( ...dispatchers );

for ( const viewDefinition of viewDefinitions ) {
converter.from( Object.assign( {}, viewDefinition ) );

if ( viewDefinition.priority ) {
converter.withPriority( viewDefinition.priority );
}
}

return converter;
}

// Creates view element instance from provided viewElementDefinition and class.
//
// @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewElementDefinition
// @param {Function} ViewElementClass
// @returns {module:engine/view/element~Element}
function createViewElementFromDefinition( viewElementDefinition, ViewElementClass ) {
const element = new ViewElementClass( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attribute ) );

if ( viewElementDefinition.style ) {
element.setStyle( viewElementDefinition.style );
}

const classes = viewElementDefinition.class;

if ( classes ) {
element.addClass( ... typeof classes === 'string' ? [ classes ] : classes );
}

return element;
}

/**
* Defines conversion details. It is used in configuration based converters:
* - {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement}
* - {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement}
* - {@link module:engine/conversion/definition-based-converters~viewToModelAttribute}
* - {@link module:engine/conversion/definition-based-converters~viewToModelElement}
*
* @typedef {Object} ConverterDefinition
* @property {String} model Defines to model conversion. When using to element conversion
* ({@link module:engine/conversion/definition-based-converters~viewToModelElement}
* and {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement})
* it defines element name. When using to attribute conversion
* ({@link module:engine/conversion/definition-based-converters~viewToModelAttribute}
* and {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement})
* it defines attribute value to which it is converted.
* @property {module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used
* in view to model conversion pipeline.
* @property {Array.<module:engine/view/viewelementdefinition~ViewElementDefinition>} acceptAlso An array with all matched elements that
* view to model conversion should also accepts.
*/
44 changes: 44 additions & 0 deletions src/view/viewelementdefinition.jsdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module engine/view/viewelementdefinition
*/

/**
* An object defining view element used in {@link module:engine/conversion/definition-based-converters} as part of
* {@link module:engine/conversion/definition-based-converters~ConverterDefinition}.
*
* It describe a view element that is used
*
* const viewDefinition = {
* name: 'h1',
* class: [ 'foo', 'bar' ]
* };
*
* Above describes a view element:
*
* <h1 class="foo bar">...</h1>
*
* For elements without attributes you can use shorthand string version:
*
* const viewDefinition = 'p';
*
* which will be treated as:
*
* const viewDefinition = {
* name: 'p'
* };
*
* @typedef {String|Object} module:engine/view/viewelementdefinition~ViewElementDefinition
*
* @property {String} name View element name.
* @property {String|Array.<String>} [class] Class name or array of class names to match. Each name can be
* provided in a form of string.
* @property {Object} [style] Object with key-value pairs representing styles to match. Each object key
* represents style name. Value under that key must be a string.
* @property {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key
* represents attribute name. Value under that key must be a string.
*/
Loading