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

Commit 0aec182

Browse files
author
Piotr Jasiun
authored
Merge pull request #901 from ckeditor/t/888-2
Fix: view.Renderer will deeply unbind DOM elements when they are removed from DOM. Closes #888.
2 parents d8ee5fa + 8bc66f0 commit 0aec182

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

src/view/domconverter.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,22 @@ export default class DomConverter {
134134
}
135135

136136
/**
137-
* Unbinds given `domElement` from the view element it was bound to.
137+
* Unbinds given `domElement` from the view element it was bound to. Unbinding is deep, meaning that all children of
138+
* `domElement` will be unbound too.
138139
*
139140
* @param {HTMLElement} domElement DOM element to unbind.
140141
*/
141142
unbindDomElement( domElement ) {
142143
const viewElement = this._domToViewMapping.get( domElement );
143144

144-
this._domToViewMapping.delete( domElement );
145-
this._viewToDomMapping.delete( viewElement );
145+
if ( viewElement ) {
146+
this._domToViewMapping.delete( domElement );
147+
this._viewToDomMapping.delete( viewElement );
148+
149+
for ( let child of domElement.childNodes ) {
150+
this.unbindDomElement( child );
151+
}
152+
}
146153
}
147154

148155
/**

src/view/renderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ export default class Renderer {
472472
insertAt( domElement, i, expectedDomChildren[ i ] );
473473
i++;
474474
} else if ( action === 'delete' ) {
475-
// Whenever element is removed from DOM, unbind it.
475+
// Whenever element is removed from DOM, unbind it and all of its children.
476476
this.domConverter.unbindDomElement( actualDomChildren[ i ] );
477477
remove( actualDomChildren[ i ] );
478478
} else { // 'equal'

tests/view/renderer.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,45 @@ describe( 'Renderer', () => {
325325
expect( domP.childNodes.length ).to.equal( 0 );
326326
} );
327327

328+
it( 'should update removed item when it is reinserted #2', () => {
329+
// Prepare view: root -> div "outer" -> div "inner" -> p.
330+
const viewP = new ViewElement( 'p' );
331+
const viewDivInner = new ViewElement( 'div', null, viewP );
332+
const viewDivOuter = new ViewElement( 'div', null, viewDivInner );
333+
viewRoot.appendChildren( viewDivOuter );
334+
335+
// Render view tree to DOM.
336+
renderer.markToSync( 'children', viewRoot );
337+
renderer.render();
338+
339+
// Remove div "outer" from root and render it.
340+
viewDivOuter.remove();
341+
renderer.markToSync( 'children', viewRoot );
342+
renderer.render();
343+
344+
// Remove p from div "child" -- div "inner" won't be marked because it is in document fragment not view root.
345+
viewP.remove();
346+
// Add div "outer" back to root.
347+
viewRoot.appendChildren( viewDivOuter );
348+
renderer.markToSync( 'children', viewRoot );
349+
350+
// Render changes, view is: root -> div "outer" -> div "inner".
351+
renderer.render();
352+
353+
// Same is expected in DOM.
354+
expect( domRoot.childNodes.length ).to.equal( 1 );
355+
356+
const domDivOuter = domRoot.childNodes[ 0 ];
357+
expect( renderer.domConverter.viewToDom( viewDivOuter, domRoot.document ) ).to.equal( domDivOuter );
358+
expect( domDivOuter.tagName ).to.equal( 'DIV' );
359+
expect( domDivOuter.childNodes.length ).to.equal( 1 );
360+
361+
const domDivInner = domDivOuter.childNodes[ 0 ];
362+
expect( renderer.domConverter.viewToDom( viewDivInner, domRoot.document ) ).to.equal( domDivInner );
363+
expect( domDivInner.tagName ).to.equal( 'DIV' );
364+
expect( domDivInner.childNodes.length ).to.equal( 0 );
365+
} );
366+
328367
it( 'should not throw when trying to update children of view element that got removed and lost its binding', () => {
329368
const viewFoo = new ViewText( 'foo' );
330369
const viewP = new ViewElement( 'p', null, viewFoo );

0 commit comments

Comments
 (0)