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

Commit

Permalink
Merge a787b46 into 0a41687
Browse files Browse the repository at this point in the history
  • Loading branch information
f1ames committed Oct 16, 2018
2 parents 0a41687 + a787b46 commit ceafdf3
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 10 deletions.
3 changes: 3 additions & 0 deletions src/view/documentfragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export default class DocumentFragment {
/**
* Creates new DocumentFragment instance.
*
* **Note:** Constructor of this class shouldn't be used directly in the code. Use the
* {@link module:engine/view/upcastwriter~UpcastWriter#createDocumentFragment} method instead.
*
* @protected
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children] List of nodes to be inserted into
* created document fragment.
Expand Down
5 changes: 3 additions & 2 deletions src/view/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ export default class Element extends Node {
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement} for inline element,
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement} for block element,
* {@link module:engine/view/downcastwriter~DowncastWriter#createEditableElement} for editable element,
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement} for empty element or
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} for UI element instead.
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement} for empty element,
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} for UI element instead or
* {@link module:engine/view/upcastwriter~UpcastWriter#createElement} for general element creation.
*
* @protected
* @param {String} name Node name.
Expand Down
3 changes: 2 additions & 1 deletion src/view/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export default class Text extends Node {
* Creates a tree view text node.
*
* **Note:** Constructor of this class shouldn't be used directly in the code.
* Use the {@link module:engine/view/downcastwriter~DowncastWriter#createText} method instead.
* Use the {@link module:engine/view/downcastwriter~DowncastWriter#createText} or
* {@link module:engine/view/upcastwriter~UpcastWriter#createText} method instead.
*
* @protected
* @param {String} data Text.
Expand Down
45 changes: 45 additions & 0 deletions src/view/upcastwriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
* @module module:engine/view/upcastwriter
*/

import DocumentFragment from './documentfragment';
import Element from './element';
import Text from './text';
import { isPlainObject } from 'lodash-es';

/**
Expand All @@ -17,6 +19,49 @@ import { isPlainObject } from 'lodash-es';
* see {@link module:engine/view/downcastwriter~DowncastWriter writer}.
*/
export default class UpcastWriter {
/**
* Creates new {@link module:engine/view/documentfragment~DocumentFragment}.
*
* @see module:engine/view/documentfragment~DocumentFragment#constructor
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* List of nodes to be inserted into created document fragment.
* @returns {module:engine/view/documentfragment~DocumentFragment} Created document fragment.
*/
createDocumentFragment( children ) {
return new DocumentFragment( children );
}

/**
* Creates new {@link module:engine/view/element~Element}.
*
* Attributes can be passed in various formats:
*
* new Element( 'div', { 'class': 'editor', 'contentEditable': 'true' } ); // object
* new Element( 'div', [ [ 'class', 'editor' ], [ 'contentEditable', 'true' ] ] ); // map-like iterator
* new Element( 'div', mapOfAttributes ); // map
*
* @see module:engine/view/element~Element#constructor
* @param {String} name Node name.
* @param {Object|Iterable} [attrs] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* List of nodes to be inserted into created element.
* @returns {module:engine/view/element~Element} Created element.
*/
createElement( name, attrs, children ) {
return new Element( name, attrs, children );
}

/**
* Creates new {@link module:engine/view/text~Text}.
*
* @see module:engine/view/text~Text#constructor
* @param {String} data Text
* @returns {module:engine/view/text~Text} Created text.
*/
createText( data ) {
return new Text( data );
}

/**
* Clones provided element.
*
Expand Down
18 changes: 11 additions & 7 deletions src/view/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,16 +308,19 @@ export default class View {
* Change method is the primary way of changing the view. You should use it to modify any node in the view tree.
* It makes sure that after all changes are made view is rendered to DOM. It prevents situations when DOM is updated
* when view state is not yet correct. It allows to nest calls one inside another and still perform single rendering
* after all changes are applied.
* after all changes are applied. It also returns the result of its callback.
*
* view.change( writer => {
* writer.insert( position1, writer.createText( 'foo' ) );
* const text = view.change( writer => {
* const newText = writer.createText( 'foo' );
* writer.insert( position1, newText );
*
* view.change( writer => {
* writer.insert( position2, writer.createText( 'bar' ) );
* } );
*
* writer.remove( range );
*
* return newText;
* } );
*
* Change block is executed immediately.
Expand All @@ -329,6 +332,7 @@ export default class View {
* change block is used after rendering to DOM has started.
*
* @param {Function} callback Callback function which may modify the view.
* @returns {*} Value returned by the callback.
*/
change( callback ) {
if ( this._renderingInProgress || this._postFixersInProgress ) {
Expand All @@ -350,15 +354,13 @@ export default class View {

// Recursive call to view.change() method - execute listener immediately.
if ( this._ongoingChange ) {
callback( this._writer );

return;
return callback( this._writer );
}

// This lock will assure that all recursive calls to view.change() will end up in same block - one "render"
// event for all nested calls.
this._ongoingChange = true;
callback( this._writer );
const callbackResult = callback( this._writer );
this._ongoingChange = false;

// This lock is used by editing controller to render changes from outer most model.change() once. As plugins might call
Expand All @@ -370,6 +372,8 @@ export default class View {

this.fire( 'render' );
}

return callbackResult;
}

/**
Expand Down
67 changes: 67 additions & 0 deletions tests/view/upcastwriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* For licensing, see LICENSE.md.
*/

import DocumentFragment from '../../src/view/documentfragment';
import Element from '../../src/view/element';
import Text from '../../src/view/text';
import UpcastWriter from '../../src/view/upcastwriter';
import HtmlDataProcessor from '../../src/dataprocessor/htmldataprocessor';

Expand All @@ -29,6 +31,71 @@ describe( 'UpcastWriter', () => {
view = dataprocessor.toView( html );
} );

describe( 'createDocumentFragment', () => {
it( 'should create empty document fragment', () => {
const df = writer.createDocumentFragment();

expect( df ).to.instanceOf( DocumentFragment );
expect( df.childCount ).to.equal( 0 );
} );

it( 'should create document fragment with children', () => {
const df = writer.createDocumentFragment( [ view.getChild( 0 ), view.getChild( 1 ) ] );

expect( df ).to.instanceOf( DocumentFragment );
expect( df.childCount ).to.equal( 2 );
} );
} );

describe( 'createElement', () => {
it( 'should create empty element', () => {
const el = writer.createElement( 'p' );

expect( el ).to.instanceOf( Element );
expect( el.name ).to.equal( 'p' );
expect( Array.from( el.getAttributes() ).length ).to.equal( 0 );
expect( el.childCount ).to.equal( 0 );
} );

it( 'should create element with attributes', () => {
const el = writer.createElement( 'a', { 'class': 'editor', 'contentEditable': 'true' } );

expect( el ).to.instanceOf( Element );
expect( el.name ).to.equal( 'a' );
expect( Array.from( el.getAttributes() ).length ).to.equal( 2 );
expect( el.childCount ).to.equal( 0 );
} );

it( 'should create element with children', () => {
const el = writer.createElement( 'div', null, [ view.getChild( 0 ) ] );

expect( el ).to.instanceOf( Element );
expect( el.name ).to.equal( 'div' );
expect( Array.from( el.getAttributes() ).length ).to.equal( 0 );
expect( el.childCount ).to.equal( 1 );
} );

it( 'should create element with attributes and children', () => {
const el = writer.createElement( 'blockquote',
{ 'class': 'editor', 'contentEditable': 'true' },
view.getChild( 2 ) );

expect( el ).to.instanceOf( Element );
expect( el.name ).to.equal( 'blockquote' );
expect( Array.from( el.getAttributes() ).length ).to.equal( 2 );
expect( el.childCount ).to.equal( 1 );
} );
} );

describe( 'createText', () => {
it( 'should create text', () => {
const text = writer.createText( 'FooBar' );

expect( text ).to.instanceOf( Text );
expect( text.data ).to.equal( 'FooBar' );
} );
} );

describe( 'clone', () => {
it( 'should clone simple element', () => {
const el = view.getChild( 0 );
Expand Down
28 changes: 28 additions & 0 deletions tests/view/view/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,34 @@ describe( 'view', () => {

sinon.assert.callOrder( changeSpy, postFixer1, postFixer2, eventSpy );
} );

it( 'should return result of the callback', () => {
const result = view.change( () => {
return 'FooBar';
} );

expect( result ).to.equal( 'FooBar' );
} );

it( 'should return result of the callback with nested change block', () => {
let result2 = false;
let result3 = false;

const result1 = view.change( () => {
return view.change( () => {
result2 = view.change( () => {
return true;
} );
result3 = view.change( () => {} );

return 42;
} );
} );

expect( result1 ).to.equal( 42 );
expect( result2 ).to.equal( true );
expect( result3 ).to.undefined;
} );
} );

describe( 'destroy()', () => {
Expand Down

0 comments on commit ceafdf3

Please sign in to comment.