Explorar el Código

Multiple fixes in model to view converters in engine.treeController.modelToView.

Szymon Cofalik hace 9 años
padre
commit
6658d9d02e

+ 71 - 83
packages/ckeditor5-engine/src/treecontroller/model-to-view-converters.js

@@ -28,10 +28,10 @@ import ViewText from '../treeview/text.js';
  * The converter automatically consumes corresponding value from consumables list, stops the event (see
  * {@link engine.treeController.ModelConversionDispatcher}) and bind model and view elements.
  *
- *		modelDispatcher.on( 'insert:element:p', insertElement( new ViewElement( 'p' ) ) );
+ *		modelDispatcher.on( 'insert:paragraph', insertElement( new ViewElement( 'p' ) ) );
  *
  *		modelDispatcher.on(
- *			'insert:element:myElem',
+ *			'insert:myElem',
  *			insertElement( ( data, consumable, conversionApi ) => {
  *				let myElem = new ViewElement( 'myElem', { myAttr: true }, new ViewText( 'myText' ) );
  *
@@ -56,7 +56,7 @@ export function insertElement( elementCreator ) {
 			elementCreator.clone( true ) :
 			elementCreator( data, consumable, conversionApi );
 
-		conversionApi.mapper.bind( data.item, viewElement );
+		conversionApi.mapper.bindElements( data.item, viewElement );
 		conversionApi.writer.insert( viewPosition, viewElement );
 
 		evt.stop();
@@ -66,10 +66,10 @@ export function insertElement( elementCreator ) {
 /**
  * Function factory, creates a default model-to-view converter for text insertion changes.
  *
- * The converter automatically consumes corresponding value from consumables list, stops the event (see
- * {@link engine.treeController.ModelConversionDispatcher}) and bind model and view elements.
+ * The converter automatically consumes corresponding value from consumables list and stops the event (see
+ * {@link engine.treeController.ModelConversionDispatcher}).
  *
- *		modelDispatcher.on( 'insert:text', insertText() );
+ *		modelDispatcher.on( 'insert:$text', insertText() );
  *
  * @external engine.treeController.modelToView
  * @function engine.treeController.modelToView.insertText
@@ -79,8 +79,8 @@ export function insertText() {
 	return ( evt, data, consumable, conversionApi ) => {
 		consumable.consume( data.item, 'insert' );
 
-		const viewPosition = conversionApi.mapper.toViewPosition( data.position );
-		const viewText = new ViewText( data.item.data );
+		const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
+		const viewText = new ViewText( data.item.text );
 
 		conversionApi.writer.insert( viewPosition, viewText );
 
@@ -90,112 +90,94 @@ export function insertText() {
 
 /**
  * Function factory, creates a converter that converts set/change attribute changes from the model to the view. Attributes
- * from model are converted to the attributes in the view. You may provide a custom function to generate a set of attributes
- * that will be added/changed. If not provided, model attributes will be converted to view elements attributes on 1-to-1 basis.
+ * from model are converted to the view element attributes in the view. You may provide a custom function to generate a
+ * key-value attribute pair to add/change. If not provided, model attributes will be converted to view elements attributes
+ * on 1-to-1 basis.
  *
- * The converter automatically consumes corresponding value from consumables list, stops the event (see
- * {@link engine.treeController.ModelConversionDispatcher}) and bind model and view elements.
+ * **Note:** Provided attribute creator should always return the same `key` for given attribute from the model.
+ *
+ * The converter automatically consumes corresponding value from consumables list and stops the event (see
+ * {@link engine.treeController.ModelConversionDispatcher}).
  *
  *		modelDispatcher.on( 'addAttribute:customAttr:myElem', setAttribute( ( data ) => {
- *			let attributes = {};
+ *			// Change attribute key from `customAttr` to `class` in view.
+ *			const key = 'class';
+ *			let value = data.attributeNewValue;
  *
- *			if ( data.item.hasAttribute( 'otherCustomAttr' ) ) {
- *				// do something with attributes variable ...
- *			} else {
- *				// do something else with attributes variable ...
+ *			// Force attribute value to 'empty' if the model element is empty.
+ *			if ( data.item.getChildCount() === 0 ) {
+ *				value = 'empty';
  *			}
  *
- *			return attributes;
+ *			// Return key-value pair.
+ *			return { key, value };
  *		} ) );
  *
  * @external engine.treeController.modelToView
  * @function engine.treeController.modelToView.setAttribute
- * @param {Function} [attributesCreator] Function returning a `string`, `number` or an `object` which values are `string`s
- * or `number`s. If `object` is returned, it's keys are used as attributes keys and values as attributes values. The function
+ * @param {Function} [attributeCreator] Function returning an object with two properties: `key` and `value`, which
+ * represents attribute key and attribute value to be set on a {@link engine.treeView.Element view element}. The function
  * is passed all the parameters of the {@link engine.treeController.ModelConversionDispatcher.addAttribute}
  * or {@link engine.treeController.ModelConversionDispatcher.changeAttribute) event.
  * @returns {Function} Set/change attribute converter.
  */
-export function setAttribute( attributesCreator ) {
-	return ( evt, data, consumable, conversionApi ) => {
-		let attributes;
-
-		if ( !attributesCreator ) {
-			attributes = data.item.getAttribute( data.attributeKey );
-		} else {
-			attributes = attributesCreator( data, consumable, conversionApi );
-		}
+export function setAttribute( attributeCreator ) {
+	attributeCreator = attributeCreator || ( ( data ) => ( { key: data.attributeKey, value: data.attributeNewValue } ) );
 
-		if ( attributes ) {
-			consumable.consume( data.item, eventNameToConsumableType( evt.name ) );
-
-			const viewElement = conversionApi.mapper.toViewElement( data.item );
+	return ( evt, data, consumable, conversionApi ) => {
+		const { key, value } = attributeCreator( data, consumable, conversionApi );
 
-			if ( typeof attributes === 'string' || typeof attributes === 'number' ) {
-				viewElement.setAttribute( data.attributeKey, attributes );
-			} else {
-				for ( let attributeKey in attributes ) {
-					viewElement.setAttribute( attributeKey, attributes[ attributeKey ] );
-				}
-			}
+		consumable.consume( data.item, eventNameToConsumableType( evt.name ) );
+		conversionApi.mapper.toViewElement( data.item ).setAttribute( key, value );
 
-			evt.stop();
-		}
+		evt.stop();
 	};
 }
 
 /**
- * Function factory, creates a converter that converts remove attribute changes from the model to the view. This converter
- * assumes, that attributes from model were converted to the attributes in the view. You may provide a custom function to
- * generate a set of attributes that will be removed. If not provided, model attributes will be removed from view elements on 1-to-1 basis.
+ * Function factory, creates a converter that converts remove attribute changes from the model to the view. Removes attributes
+ * that were converted to the view element attributes in the view. You may provide a custom function to generate a
+ * key-value attribute pair to remove. If not provided, model attributes will be removed from view elements on 1-to-1 basis.
  *
- * The converter automatically consumes corresponding value from consumables list, stops the event (see
- * {@link engine.treeController.ModelConversionDispatcher}) and bind model and view elements.
+ * **Note:** Provided attribute creator should always return the same `key` for given attribute from the model.
+ *
+ * **Note:** You can use the same attribute creator as in {@link engine.treeController.modelToView.setAttribute}.
+ *
+ * The converter automatically consumes corresponding value from consumables list and stops the event (see
+ * {@link engine.treeController.ModelConversionDispatcher}).
  *
  *		modelDispatcher.on( 'removeAttribute:customAttr:myElem', removeAttribute( ( data ) => {
- *			let attributes = {};
+ *			// Change attribute key from `customAttr` to `class` in view.
+ *			const key = 'class';
+ *			let value = data.attributeNewValue;
  *
- *			if ( data.item.hasAttribute( 'otherCustomAttr' ) ) {
- *				// do something with attributes variable ...
- *			} else {
- *				// do something else with attributes variable ...
+ *			// Force attribute value to 'empty' if the model element is empty.
+ *			if ( data.item.getChildCount() === 0 ) {
+ *				value = 'empty';
  *			}
  *
- *			return attributes;
+ *			// Return key-value pair.
+ *			return { key, value };
  *		} ) );
  *
  * @external engine.treeController.modelToView
  * @function engine.treeController.modelToView.removeAttribute
- * @param {Function} [attributesCreator] Function returning a `string` or an `array` of `string`s. If `array` is returned,
- * it's values are used as attributes keys to remove. The function is passed all the parameters of the
- * {@link engine.treeController.ModelConversionDispatcher.removeAttribute} event.
+ * @param {Function} [attributeCreator] Function returning an object with two properties: `key` and `value`, which
+ * represents attribute key and attribute value to be removed from {@link engine.treeView.Element view element}. The function
+ * is passed all the parameters of the {@link engine.treeController.ModelConversionDispatcher.addAttribute}
+ * or {@link engine.treeController.ModelConversionDispatcher.changeAttribute) event.
  * @returns {Function} Remove attribute converter.
  */
-export function removeAttribute( attributesCreator ) {
-	return ( evt, data, consumable, conversionApi ) => {
-		let attributeKeys;
+export function removeAttribute( attributeCreator ) {
+	attributeCreator = attributeCreator || ( ( data ) => ( { key: data.attributeKey } ) );
 
-		if ( !attributesCreator ) {
-			attributeKeys = data.attributeKey;
-		} else {
-			attributeKeys = attributesCreator( data, consumable, conversionApi );
-		}
-
-		if ( attributeKeys ) {
-			consumable.consume( data.item, eventNameToConsumableType( evt.name ) );
-
-			const viewElement = conversionApi.mapper.toViewElement( data.item );
+	return ( evt, data, consumable, conversionApi ) => {
+		const { key } = attributeCreator( data, consumable, conversionApi );
 
-			if ( typeof attributeKeys === 'string' ) {
-				viewElement.removeAttribute( attributeKeys );
-			} else {
-				for ( let attributeKey of attributeKeys ) {
-					viewElement.removeAttribute( attributeKey );
-				}
-			}
+		consumable.consume( data.item, eventNameToConsumableType( evt.name ) );
+		conversionApi.mapper.toViewElement( data.item ).removeAttribute( key );
 
-			evt.stop();
-		}
+		evt.stop();
 	};
 }
 
@@ -216,7 +198,7 @@ export function removeAttribute( attributesCreator ) {
  * function returns a {@link engine.treeView.Element}. The result of the function will be the wrapping element.
  *
  * The converter automatically consumes corresponding value from consumables list, stops the event (see
- * {@link engine.treeController.ModelConversionDispatcher}) and bind model and view elements.
+ * {@link engine.treeController.ModelConversionDispatcher}).
  *
  *		modelDispatcher.on( 'addAttribute:bold', wrap( new ViewElement( 'strong' ) ) );
  *
@@ -234,7 +216,7 @@ export function wrap( elementCreator ) {
 
 		const viewElement = ( elementCreator instanceof ViewElement ) ?
 			elementCreator.clone( true ) :
-			elementCreator( data, consumable, conversionApi );
+			elementCreator( data.attributeNewValue, data, consumable, conversionApi );
 
 		conversionApi.writer.wrap( viewRange, viewElement, evt.priority );
 
@@ -272,7 +254,7 @@ export function unwrap( elementCreator ) {
 		const viewRange = conversionApi.mapper.toViewRange( data.range );
 		const viewNode = ( elementCreator instanceof ViewElement ) ?
 			elementCreator.clone( true ) :
-			elementCreator( data, consumable, conversionApi );
+			elementCreator( data.attributeOldValue, data, consumable, conversionApi );
 
 		conversionApi.writer.unwrap( viewRange, viewNode );
 
@@ -302,7 +284,7 @@ export function move() {
 		const sourceModelRange = ModelRange.createFromPositionAndShift( data.sourcePosition, length );
 
 		const sourceViewRange = conversionApi.mapper.toViewRange( sourceModelRange );
-		const targetViewPosition = conversionApi.mapper.toViewRange( data.range.start );
+		const targetViewPosition = conversionApi.mapper.toViewPosition( data.range.start );
 
 		conversionApi.writer.move( sourceViewRange, targetViewPosition );
 	};
@@ -334,7 +316,13 @@ export function remove() {
 	};
 }
 
-function eventNameToConsumableType( evtName ) {
+/**
+ * Returns the consumable type that is to be consumed in an event, basing on that event name.
+ *
+ * @param {String} evtName Event name.
+ * @returns {String} Consumable type.
+ */
+export function eventNameToConsumableType( evtName ) {
 	const parts = evtName.split( ':' );
 
 	return parts[ 0 ] + ':' + parts[ 1 ];