8
0
Просмотр исходного кода

Merge pull request #695 from ckeditor/t/693

Custom properties and getFillerOffset.
Piotrek Koszuliński 9 лет назад
Родитель
Сommit
8fb851b163

+ 35 - 27
packages/ckeditor5-engine/src/view/attributeelement.js

@@ -39,6 +39,14 @@ export default class AttributeElement extends Element {
 		 * @member {Number} module:engine/view/attributeelement~AttributeElement#priority
 		 */
 		this.priority = DEFAULT_PRIORITY;
+
+		/**
+		 * Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
+		 *
+		 * @method #getFillerOffset
+		 * @returns {Number|null} Block filler offset or `null` if block filler is not needed.
+		 */
+		this.getFillerOffset = getFillerOffset;
 	}
 
 	/**
@@ -68,40 +76,40 @@ export default class AttributeElement extends Element {
 	isSimilar( otherElement ) {
 		return super.isSimilar( otherElement ) && this.priority == otherElement.priority;
 	}
+}
 
-	/**
-	 * Returns block {@link module:engine/view/filler filler} offset or `null` if a block filler is not needed.
-	 *
-	 * @returns {Number|null} Block filler offset or `null` if block filler is not needed.
-	 */
-	getFillerOffset() {
-		// <b>foo</b> does not need filler.
-		if ( this.childCount ) {
-			return null;
-		}
+/**
+ * Default attribute priority.
+ *
+ * @member {Number} module:engine/view/attributeelement~AttributeElement.DEFAULT_PRIORITY
+ */
+AttributeElement.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
 
-		let element = this.parent;
+// Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
+//
+// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
+function getFillerOffset() {
+	/*jshint validthis:true */
 
-		// <p><b></b></p> needs filler -> <p><b><br></b></p>
-		while ( element instanceof AttributeElement ) {
-			if ( element.childCount > 1 ) {
-				return null;
-			}
+	// <b>foo</b> does not need filler.
+	if ( this.childCount ) {
+		return null;
+	}
 
-			element = element.parent;
-		}
+	let element = this.parent;
 
-		if ( !element || element.childCount > 1 ) {
+	// <p><b></b></p> needs filler -> <p><b><br></b></p>
+	while ( element instanceof AttributeElement ) {
+		if ( element.childCount > 1 ) {
 			return null;
 		}
 
-		return 0;
+		element = element.parent;
 	}
-}
 
-/**
- * Default attribute priority.
- *
- * @member {Number} module:engine/view/attributeelement~AttributeElement.DEFAULT_PRIORITY
- */
-AttributeElement.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
+	if ( !element || element.childCount > 1 ) {
+		return null;
+	}
+
+	return 0;
+}

+ 15 - 8
packages/ckeditor5-engine/src/view/containerelement.js

@@ -52,14 +52,21 @@ export default class ContainerElement extends Element {
 	 */
 	constructor( name, attrs, children ) {
 		super( name, attrs, children );
-	}
 
-	/**
-	 * Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
-	 *
-	 * @returns {Number|null} Block filler offset or `null` if block filler is not needed.
-	 */
-	getFillerOffset() {
-		return this.childCount === 0 ? 0 : null;
+		/**
+		 * Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
+		 *
+		 * @method #getFillerOffset
+		 * @returns {Number|null} Block filler offset or `null` if block filler is not needed.
+		 */
+		this.getFillerOffset = getFillerOffset;
 	}
 }
+
+// Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
+//
+// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
+function getFillerOffset() {
+	/*jshint validthis:true */
+	return this.childCount === 0 ? 0 : null;
+}

+ 63 - 0
packages/ckeditor5-engine/src/view/element.js

@@ -105,6 +105,15 @@ export default class Element extends Node {
 			parseInlineStyles( this._styles, this._attrs.get( 'style' ) );
 			this._attrs.delete( 'style' );
 		}
+
+		/**
+		 * Map of custom properties.
+		 * Custom properties can be added to element instance, will be cloned but not rendered into DOM.
+		 *
+		 * @protected
+		 * @memeber {Map} engine.view.Element#_customProperties.
+		 */
+		this._customProperties = new Map();
 	}
 
 	/**
@@ -151,6 +160,12 @@ export default class Element extends Node {
 		cloned._classes = new Set( this._classes );
 		cloned._styles = new Map( this._styles );
 
+		// Clone custom properties.
+		cloned._customProperties = new Map( this._customProperties );
+
+		// Clone filler offset method.
+		cloned.getFillerOffset = this.getFillerOffset;
+
 		return cloned;
 	}
 
@@ -597,6 +612,54 @@ export default class Element extends Node {
 
 		return null;
 	}
+
+	/**
+	 * Sets a custom property. Unlike attributes, custom properties are not rendered to the DOM,
+	 * so they can be used to add special data to elements.
+	 *
+	 * @param {String|Symbol} key
+	 * @param {*} value
+	 */
+	setCustomProperty( key, value ) {
+		this._customProperties.set( key, value );
+	}
+
+	/**
+	 * Returns the custom property value for the given key.
+	 *
+	 * @param {String|Symbol} key
+	 * @returns {*}
+	 */
+	getCustomProperty( key ) {
+		return this._customProperties.get( key );
+	}
+
+	/**
+	 * Removes the custom property stored under the given key.
+	 *
+	 * @param {String|Symbol} key
+	 * @returns {Boolean} Returns true if property was removed.
+	 */
+	removeCustomProperty( key ) {
+		return this._customProperties.delete( key );
+	}
+
+	/**
+	 * Returns an iterator which iterates over this element's custom properties.
+	 * Iterator provides [key, value] pair for each stored property.
+	 *
+	 * @returns {Iterable.<*>}
+	 */
+	*getCustomProperties() {
+		yield* this._customProperties.entries();
+	}
+
+	/**
+	 * Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
+	 *
+	 * @abstract
+	 * @method module:engine/view/element~Element#getFillerOffset
+	 */
 }
 
 // Parses inline styles and puts property - value pairs into styles map.

+ 15 - 9
packages/ckeditor5-engine/src/view/emptyelement.js

@@ -29,6 +29,14 @@ export default class EmptyElement extends Element {
 		if ( arguments.length > 2 ) {
 			throwCannotAdd();
 		}
+
+		/**
+		 * Returns `null` because filler is not needed for EmptyElements.
+		 *
+		 * @method #getFillerOffset
+		 * @returns {null} Always returns null.
+		 */
+		this.getFillerOffset = getFillerOffset;
 	}
 
 	/**
@@ -65,15 +73,6 @@ export default class EmptyElement extends Element {
 	insertChildren() {
 		throwCannotAdd();
 	}
-
-	/**
-	 * Returns `null` because block filler is not needed.
-	 *
-	 * @returns {null}
-	 */
-	getFillerOffset() {
-		return null;
-	}
 }
 
 function throwCannotAdd() {
@@ -84,3 +83,10 @@ function throwCannotAdd() {
 	 */
 	throw new CKEditorError( 'view-emptyelement-cannot-add: Cannot add child nodes to EmptyElement instance.' );
 }
+
+// Returns `null` because block filler is not needed for EmptyElements.
+//
+// @returns {null}
+function getFillerOffset() {
+	return null;
+}

+ 73 - 0
packages/ckeditor5-engine/tests/view/element.js

@@ -157,6 +157,30 @@ describe( 'Element', () => {
 			expect( clone._styles.has( 'font-size' ) ).to.be.true;
 			expect( clone._styles.get( 'font-size' ) ).to.equal( '12px' );
 		} );
+
+		it( 'should clone custom properties', () => {
+			const el = new Element( 'p' );
+			const symbol = Symbol( 'custom' );
+			el.setCustomProperty( 'foo', 'bar' );
+			el.setCustomProperty( symbol, 'baz' );
+
+			const cloned = el.clone();
+
+			expect( cloned.getCustomProperty( 'foo' ) ).to.equal( 'bar' );
+			expect( cloned.getCustomProperty( symbol ) ).to.equal( 'baz' );
+		} );
+
+		it( 'should clone getFillerOffset', () => {
+			const el = new Element( 'p' );
+			const fm = () => 'foo bar';
+
+			expect( el.getFillerOffset ).to.be.undefined;
+			el.getFillerOffset = fm;
+
+			const cloned = el.clone();
+
+			expect( cloned.getFillerOffset ).to.equal( fm );
+		} );
 	} );
 
 	describe( 'isSimilar', () => {
@@ -817,4 +841,53 @@ describe( 'Element', () => {
 			} ) ).to.be.null;
 		} );
 	} );
+
+	describe( 'custom properties', () => {
+		it( 'should allow to set and get custom properties', () => {
+			const el = new Element( 'p' );
+			el.setCustomProperty( 'foo', 'bar' );
+
+			expect( el.getCustomProperty( 'foo' ) ).to.equal( 'bar' );
+		} );
+
+		it( 'should allow to add symbol property', () => {
+			const el = new Element( 'p' );
+			const symbol = Symbol( 'custom' );
+			el.setCustomProperty( symbol, 'bar' );
+
+			expect( el.getCustomProperty( symbol ) ).to.equal( 'bar' );
+		} );
+
+		it( 'should allow to remove custom property', () => {
+			const el = new Element( 'foo' );
+			const symbol = Symbol( 'quix' );
+			el.setCustomProperty( 'bar', 'baz' );
+			el.setCustomProperty( symbol, 'test' );
+
+			expect( el.getCustomProperty( 'bar' ) ).to.equal( 'baz' );
+			expect( el.getCustomProperty( symbol ) ).to.equal( 'test' );
+
+			el.removeCustomProperty( 'bar' );
+			el.removeCustomProperty( symbol );
+
+			expect( el.getCustomProperty( 'bar' ) ).to.be.undefined;
+			expect( el.getCustomProperty( symbol ) ).to.be.undefined;
+		} );
+
+		it( 'should allow to iterate over custom properties', () => {
+			const el = new Element( 'p' );
+			el.setCustomProperty( 'foo', 1 );
+			el.setCustomProperty( 'bar', 2 );
+			el.setCustomProperty( 'baz', 3 );
+
+			const properties = [ ...el.getCustomProperties() ];
+
+			expect( properties[ 0 ][ 0 ] ).to.equal( 'foo' );
+			expect( properties[ 0 ][ 1 ] ).to.equal( 1 );
+			expect( properties[ 1 ][ 0 ] ).to.equal( 'bar' );
+			expect( properties[ 1 ][ 1 ] ).to.equal( 2 );
+			expect( properties[ 2 ][ 0 ] ).to.equal( 'baz' );
+			expect( properties[ 2 ][ 1 ] ).to.equal( 3 );
+		} );
+	} );
 } );