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

Commit

Permalink
Merge pull request #890 from ckeditor/t/888
Browse files Browse the repository at this point in the history
Fix: Renderer will unbind DOM elements from view elements when removing them from DOM. Closes #888.
  • Loading branch information
Piotr Jasiun committed Mar 27, 2017
2 parents 5627993 + 5712c44 commit 86ea5b5
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/view/domconverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ export default class DomConverter {
this._viewToDomMapping.set( viewElement, domElement );
}

/**
* Unbinds given `domElement` from the view element it was bound to.
*
* @param {HTMLElement} domElement DOM element to unbind.
*/
unbindDomElement( domElement ) {
const viewElement = this._domToViewMapping.get( domElement );

this._domToViewMapping.delete( domElement );
this._viewToDomMapping.delete( viewElement );
}

/**
* Binds DOM and View document fragments, so it will be possible to get corresponding document fragments using
* {@link module:engine/view/domconverter~DomConverter#getCorrespondingViewDocumentFragment getCorrespondingViewDocumentFragment} and
Expand Down
9 changes: 9 additions & 0 deletions src/view/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,13 @@ export default class Renderer {
_updateChildren( viewElement, options ) {
const domConverter = this.domConverter;
const domElement = domConverter.getCorrespondingDom( viewElement );

if ( !domElement ) {
// If there is no `domElement` it means that it was already removed from DOM.
// There is no need to update it. It will be updated when re-inserted.
return;
}

const domDocument = domElement.ownerDocument;

const filler = options.inlineFillerPosition;
Expand All @@ -463,6 +470,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 ] );
remove( actualDomChildren[ i ] );
} else { // 'equal'
i++;
Expand Down
57 changes: 57 additions & 0 deletions tests/view/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,63 @@ describe( 'Renderer', () => {
expect( domRoot.childNodes[ 0 ].tagName ).to.equal( 'P' );
} );

it( 'should update removed item when it is reinserted', () => {
const viewFoo = new ViewText( 'foo' );
const viewP = new ViewElement( 'p', null, viewFoo );
const viewDiv = new ViewElement( 'div', null, viewP );

viewRoot.appendChildren( viewDiv );

renderer.markToSync( 'children', viewRoot );
renderer.render();

viewDiv.removeChildren( 0, 1 );
renderer.markToSync( 'children', viewDiv );
renderer.render();

viewP.removeChildren( 0, 1 );

viewDiv.appendChildren( viewP );
renderer.markToSync( 'children', viewDiv );
renderer.render();

expect( domRoot.childNodes.length ).to.equal( 1 );

const domDiv = domRoot.childNodes[ 0 ];

expect( domDiv.tagName ).to.equal( 'DIV' );
expect( domDiv.childNodes.length ).to.equal( 1 );

const domP = domDiv.childNodes[ 0 ];

expect( domP.tagName ).to.equal( 'P' );
expect( domP.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 );
const viewDiv = new ViewElement( 'div', null, viewP );

viewRoot.appendChildren( viewDiv );

renderer.markToSync( 'children', viewRoot );
renderer.render();

viewRoot.removeChildren( 0, 1 );
renderer.markToSync( 'children', viewRoot );

viewDiv.removeChildren( 0, 1 );
renderer.markToSync( 'children', viewDiv );

viewP.removeChildren( 0, 1 );
renderer.markToSync( 'children', viewP );

renderer.render();

expect( domRoot.childNodes.length ).to.equal( 0 );
} );

it( 'should not care about filler if there is no DOM', () => {
selectionEditable = null;

Expand Down

0 comments on commit 86ea5b5

Please sign in to comment.