Ver código fonte

Changed: view.Renderer will unbind DOM elements deeply when they are removed from DOM.

Szymon Cofalik 8 anos atrás
pai
commit
5a4a1315bb

+ 15 - 2
packages/ckeditor5-engine/src/view/renderer.js

@@ -472,8 +472,8 @@ export default class Renderer {
 				insertAt( domElement, i, expectedDomChildren[ i ] );
 				i++;
 			} else if ( action === 'delete' ) {
-				// Whenever element is removed from DOM, unbind it.
-				this.domConverter.unbindDomElement( actualDomChildren[ i ] );
+				// Whenever element is removed from DOM, unbind it and all of its children.
+				unbindDeep( actualDomChildren[ i ], this.domConverter );
 				remove( actualDomChildren[ i ] );
 			} else { // 'equal'
 				i++;
@@ -687,3 +687,16 @@ function trimSelection( selection ) {
 
 	return newSelection;
 }
+
+// Unbind given `domElement` and all of its children from view using given `domConverter`.
+//
+// @private
+// @param {HTMLElement} domElement
+// @param {module:engine/view/domconverter~DomConverter}
+function unbindDeep( domElement, domConverter ) {
+	domConverter.unbindDomElement( domElement );
+
+	for ( let child of domElement.childNodes ) {
+		unbindDeep( child, domConverter );
+	}
+}

+ 39 - 0
packages/ckeditor5-engine/tests/view/renderer.js

@@ -325,6 +325,45 @@ describe( 'Renderer', () => {
 			expect( domP.childNodes.length ).to.equal( 0 );
 		} );
 
+		it( 'should update removed item when it is reinserted #2', () => {
+			// Prepare view: root -> div "outer" -> div "inner" -> p.
+			const viewP = new ViewElement( 'p' );
+			const viewDivInner = new ViewElement( 'div', null, viewP );
+			const viewDivOuter = new ViewElement( 'div', null, viewDivInner );
+			viewRoot.appendChildren( viewDivOuter );
+
+			// Render view tree to DOM.
+			renderer.markToSync( 'children', viewRoot );
+			renderer.render();
+
+			// Remove div "outer" from root and render it.
+			viewDivOuter.remove();
+			renderer.markToSync( 'children', viewRoot );
+			renderer.render();
+
+			// Remove p from div "child" -- div "inner" won't be marked because it is in document fragment not view root.
+			viewP.remove();
+			// Add div "outer" back to root.
+			viewRoot.appendChildren( viewDivOuter );
+			renderer.markToSync( 'children', viewRoot );
+
+			// Render changes, view is: root -> div "outer" -> div "inner".
+			renderer.render();
+
+			// Same is expected in DOM.
+			expect( domRoot.childNodes.length ).to.equal( 1 );
+
+			const domDivOuter = domRoot.childNodes[ 0 ];
+			expect( renderer.domConverter.viewToDom( viewDivOuter, domRoot.document ) ).to.equal( domDivOuter );
+			expect( domDivOuter.tagName ).to.equal( 'DIV' );
+			expect( domDivOuter.childNodes.length ).to.equal( 1 );
+
+			const domDivInner = domDivOuter.childNodes[ 0 ];
+			expect( renderer.domConverter.viewToDom( viewDivInner, domRoot.document ) ).to.equal( domDivInner );
+			expect( domDivInner.tagName ).to.equal( 'DIV' );
+			expect( domDivInner.childNodes.length ).to.equal( 0 );
+		} );
+
 		it( 'should not throw when trying to update children of view element that got removed and lost its binding', () => {
 			const viewFoo = new ViewText( 'foo' );
 			const viewP = new ViewElement( 'p', null, viewFoo );