| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- 'use strict';
- import {
- insertElement,
- setAttribute,
- removeAttribute,
- wrap,
- unwrap
- } from './model-to-view-converters.js';
- import { convertSelectionAttribute } from './model-selection-to-view-converters.js';
- import ViewAttributeElement from '../treeview/attributeelement.js';
- import ViewContainerElement from '../treeview/containerelement.js';
- /**
- * Provides chainable, high-level API to easily build basic model-to-view converters that are appended to given
- * dispatchers. In many cases, this is the API that should be used to specify how abstract model elements and
- * attributes should be represented in the view (and then later in DOM). Instances of this class are created by
- * {@link engine.treeController.BuildModelConverterFor}.
- *
- * If you need more complex converters, see {@link engine.treeController.ModelConversionDispatcher},
- * {@link engine.treeController.modelToView}, {@link engine.treeController.ModelConsumable}, {@link engine.treeController.Mapper}.
- *
- * Using this API it is possible to create three kinds of converters:
- *
- * 1. Model element to view element converter. This is a converter that takes the model element and represents it
- * in the view.
- *
- * BuildModelConverterFor( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' );
- * BuildModelConverterFor( dispatcher ).fromElement( 'image' ).toElement( 'img' );
- *
- * 2. Model attribute to view attribute converter. This is a converter that operates on model element attributes
- * and converts them to view element attributes. It is suitable for elements like `image` (`src`, `title` attributes).
- *
- * BuildModelConverterFor( dispatcher ).fromElement( 'image' ).toElement( 'img' );
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'src' ).toAttribute();
- *
- * 3. Model attribute to view element converter. This is a converter that takes model attributes and represents them
- * as view elements. Those view elements are wrapping view elements are node that correspond to model elements and
- * nodes which had converter attribute. It is suitable for attributes like `bold`, where `bold` attribute set on model
- * text nodes is converter to `strong` view element.
- *
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'bold' ).toElement( 'strong' );
- *
- * It is possible to provide various different parameters for {@link engine.treeController.ModelConverterBuilder#toElement}
- * and {@link engine.treeController.ModelConverterBuilder#toAttribute} methods. See their descriptions to learn more.
- *
- * It is also possible to {@link engine.treeController.ModelConverterBuilder#withPriority change default priority}
- * of created converters to decide which converter should be fired earlier and which later. This is useful if you provide
- * a general converter but want to provide different converter for a specific-case (i.e. given model element is converted
- * always to given view element, but if it has given attribute it is converter to other view element). For this,
- * use {@link engine.treeController.ModelConverterBuilder#withPriority withPriority} right after `from...` method.
- *
- * Note that `to...` methods are "terminators", which means that should be the last one used in building converter.
- *
- * You can use {@link engine.treeController.ViewConverterBuilder} to create "opposite" converters - from view to model.
- *
- * @memberOf engine.treeController
- */
- class ModelConverterBuilder {
- /**
- * Creates `ModelConverterBuilder` with given `dispatchers` registered to it.
- *
- * @param {Array.<engine.treeController.ModelConversionDispatcher>} dispatchers Dispatchers to which converters will
- * be attached.
- */
- constructor( dispatchers ) {
- /**
- * Dispatchers to which converters will be attached.
- *
- * @type {Array.<engine.treeController.ModelConversionDispatcher>}
- * @private
- */
- this._dispatchers = dispatchers;
- /**
- * Contains data about registered "from" query.
- *
- * @type {Object}
- * @private
- */
- this._from = null;
- }
- /**
- * Registers what model element should be converted.
- *
- * @chainable
- * @param {String} elementName Name of element to convert.
- * @returns {engine.treeController.ModelConverterBuilder}
- */
- fromElement( elementName ) {
- this._from = {
- type: 'element',
- name: elementName,
- priority: null
- };
- return this;
- }
- /**
- * Registers what model attribute should be converted.
- *
- * @chainable
- * @param {String} key Key of attribute to convert.
- * @returns {engine.treeController.ModelConverterBuilder}
- */
- fromAttribute( key ) {
- this._from = {
- type: 'attribute',
- key: key,
- priority: null
- };
- return this;
- }
- /**
- * Changes default priority for built converter. The lower the number, the earlier converter will be fired.
- * Default priority is `10`.
- *
- * **Note:** Keep in mind that event priority, that is set by this modifier, is used for attribute priority
- * when {@link engine.treeView.Writer} is used. This changes how model attributes converter to view elements are
- * ordered, i.e.: `<strong><em>foo</em></strong>` vs `<em><strong>foo</strong></em>`. Using priority you can also
- * prevent node merging, i.e.: `<span class="bold"><span class="theme">foo</span><span>` vs `<span class="bold theme">foo</span>`.
- * If you want to prevent merging, just set different priority for both converters.
- *
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'bold' ).withPriority( 2 ).toElement( 'strong' );
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'italic' ).withPriority( 3 ).toElement( 'em' );
- *
- * @chainable
- * @param {Number} priority Converter priority.
- * @returns {engine.treeController.ModelConverterBuilder}
- */
- withPriority( priority ) {
- this._from.priority = priority;
- return this;
- }
- /**
- * Registers what view element will be created by converter.
- *
- * Method accepts various ways of providing how the view element will be created. You can pass view element name as
- * `string`, view element instance which will be cloned and used, or creator function which returns view element that
- * will be used. Keep in mind that when you view element instance or creator function, it has to be/return a
- * proper type of view element: {@link engine.treeView.ViewContainerElement ViewContainerElement} if you convert
- * from element or {@link engine.treeView.ViewAttributeElement ViewAttributeElement} if you convert from attribute.
- *
- * BuildModelConverterFor( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' );
- *
- * BuildModelConverterFor( dispatcher ).fromElement( 'image' ).toElement( new ViewContainerElement( 'img' ) );
- *
- * BuildModelConverterFor( dispatcher )
- * .fromElement( 'header' )
- * .toElement( ( data ) => new ViewContainerElement( 'h' + data.item.getAttribute( 'level' ) ) );
- *
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'bold' ).toElement( new ViewAttributeElement( 'strong' ) );
- *
- * Creator function will be passed different values depending whether conversion is from element or from attribute:
- *
- * * from element: dispatcher's {@link engine.treeController.ModelConversionDispatcher#event:insert insert event} parameters
- * will be passed,
- * * from attribute: there is one parameter and it is attribute value.
- *
- * This method also registers model selection to view selection converter, if conversion is from attribute.
- *
- * This method creates the converter and adds it as a callback to a proper
- * {@link engine.treeController.ModelConversionDispatcher conversion dispatcher} event.
- *
- * @param {String|engine.treeView.ViewElement|Function} element Element created by converter.
- */
- toElement( element ) {
- const priority = this._from.priority === null ? 10 : this._from.priority;
- for ( let dispatcher of this._dispatchers ) {
- if ( this._from.type == 'element' ) {
- // From model element to view element -> insert element.
- element = typeof element == 'string' ? new ViewContainerElement( element ) : element;
- dispatcher.on( 'insert:' + this._from.name, insertElement( element ), null, priority );
- } else {
- // From model attribute to view element -> wrap and unwrap.
- element = typeof element == 'string' ? new ViewAttributeElement( element ) : element;
- dispatcher.on( 'addAttribute:' + this._from.key, wrap( element ), null, priority );
- dispatcher.on( 'changeAttribute:' + this._from.key, wrap( element ), null, priority );
- dispatcher.on( 'removeAttribute:' + this._from.key, unwrap( element ), null, priority );
- dispatcher.on( 'selectionAttribute:' + this._from.key, convertSelectionAttribute( element ), null, priority );
- }
- }
- }
- /**
- * Registers what view attribute will be created by converter. Keep in mind, that only model attribute to
- * view attribute conversion is supported.
- *
- * Method accepts various ways of providing how the view attribute will be created:
- *
- * * for no passed parameter, attribute key and value will be converted 1-to-1 to view attribute,
- * * if you pass one `string`, it will be used as new attribute key while attribute value will be copied,
- * * if you pass two `string`s, first one will be used as new attribute key and second one as new attribute value,
- * * if you pass a function, it is expected to return an object with `key` and `value` properties representing attribute key and value.
- * This function will be passed model attribute value and model attribute key as first two parameters and then
- * all dispatcher's {engine.treeController.ModelConversionDispatcher#event:changeAttribute changeAttribute event} parameters.
- *
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'class' ).toAttribute( '' );
- *
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'linkTitle' ).toAttribute( 'title' );
- *
- * BuildModelConverterFor( dispatcher ).fromAttribute( 'highlighted' ).toAttribute( 'style', 'background:yellow' );
- *
- * BuildModelConverterFor( dispatcher )
- * .fromAttribute( 'theme' )
- * .toAttribute( ( value ) => ( { key: 'class', value: value + '-theme' } ) );
- *
- * This method creates the converter and adds it as a callback to a proper
- * {@link engine.treeController.ModelConversionDispatcher conversion dispatcher} event.
- *
- * @param {String|Function} [keyOrCreator] Attribute key or a creator function.
- * @param {*} [value] Attribute value.
- */
- toAttribute( keyOrCreator, value ) {
- if ( this._from.type == 'element' ) {
- // Converting from model element to view attribute is unsupported.
- return;
- }
- let attributeCreator;
- if ( !keyOrCreator ) {
- // If `keyOrCreator` is not set, we assume default behavior which is 1:1 attribute re-write.
- // This is also a default behavior for `setAttribute` converter when no attribute creator is passed.
- attributeCreator = undefined;
- } else if ( typeof keyOrCreator == 'string' ) {
- // `keyOrCreator` is an attribute key.
- if ( value ) {
- // If value is set, create "dumb" creator that always returns the same object.
- attributeCreator = function() {
- return { key: keyOrCreator, value: value };
- };
- } else {
- // If value is not set, take it from the passed parameter.
- attributeCreator = function( value ) {
- return { key: keyOrCreator, value: value };
- };
- }
- } else {
- // `keyOrCreator` is an attribute creator function.
- attributeCreator = keyOrCreator;
- }
- for ( let dispatcher of this._dispatchers ) {
- dispatcher.on( 'addAttribute:' + this._from.key, setAttribute( attributeCreator ), null, this._from.priority || 10 );
- dispatcher.on( 'changeAttribute:' + this._from.key, setAttribute( attributeCreator ), null, this._from.priority || 10 );
- dispatcher.on( 'removeAttribute:' + this._from.key, removeAttribute( attributeCreator ), null, this._from.priority || 10 );
- }
- }
- }
- /**
- * Entry point for model-to-view converters builder. This chainable API makes it easy to create basic, most common
- * model-to-view converters and attach them to provided dispatchers. The method returns an instance of
- * {@link engine.treeController.ModelConverterBuilder}.
- *
- * @external engine.treeController.BuildModelConverterFor
- * @memberOf engine.treeController
- * @param {...engine.treeController.ModelConversionDispatcher} dispatchers One or more dispatchers which
- * the built converter will be attached to.
- */
- export default function BuildModelConverterFor( ...dispatchers ) {
- return new ModelConverterBuilder( dispatchers );
- }
|