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 #169 from ckeditor/t/168
Browse files Browse the repository at this point in the history
Other: Moved `ViewCollection#bindTo` method to `Collection` class in `ckeditor5-utils`. Closes #168.

BREAKING CHANGE: `ViewCollection#bindTo.as` is renamed to `Collection#bindTo.using` when mapping function is a parameter. See`Collection#bindTo` docs.
  • Loading branch information
Piotr Jasiun authored Mar 15, 2017
2 parents a19d6c4 + 920f094 commit 5b55987
Show file tree
Hide file tree
Showing 3 changed files with 1 addition and 261 deletions.
2 changes: 1 addition & 1 deletion src/dropdown/list/createlistdropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function createListDropdown( model, locale ) {

const listView = dropdownView.listView = new ListView( locale );

listView.items.bindTo( model.items ).as( itemModel => {
listView.items.bindTo( model.items ).using( itemModel => {
const item = new ListItemView( locale );

// Bind all attributes of the model to the item view.
Expand Down
125 changes: 0 additions & 125 deletions src/viewcollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import View from './view';

/**
* Collects {@link module:ui/view~View} instances.
Expand Down Expand Up @@ -70,15 +69,6 @@ export default class ViewCollection extends Collection {
* @member {HTMLElement}
*/
this._parentElement = null;

/**
* A helper mapping between bound collection items passed to {@link #bindTo}
* and view instances. Speeds up the view management.
*
* @protected
* @member {HTMLElement}
*/
this._boundItemsToViewsMap = new Map();
}

/**
Expand Down Expand Up @@ -150,121 +140,6 @@ export default class ViewCollection extends Collection {
this._parentElement = elementOrDocFragment;
}

/**
* Binds this collection to {@link module:utils/collection~Collection another collection}. For each item in the
* second collection there will be one view instance added to this collection.
*
* The process can be automatic:
*
* // This collection stores items.
* const items = new Collection( { idProperty: 'label' } );
*
* // This view collection will become a factory out of the collection of items.
* const views = new ViewCollection( locale );
*
* // Activate the binding – since now, this view collection works like a **factory**.
* // Each new item is passed to the FooView constructor like new FooView( locale, item ).
* views.bindTo( items ).as( FooView );
*
* // As new items arrive to the collection, each becomes an instance of FooView
* // in the view collection.
* items.add( new Model( { label: 'foo' } ) );
* items.add( new Model( { label: 'bar' } ) );
*
* console.log( views.length == 2 );
*
* // View collection is updated as the model is removed.
* items.remove( 0 );
* console.log( views.length == 1 );
*
* or the factory can be driven by a custom callback:
*
* // This collection stores any kind of data.
* const data = new Collection();
*
* // This view collection will become a custom factory for the data.
* const views = new ViewCollection( locale );
*
* // Activate the binding – the **factory** is driven by a custom callback.
* views.bindTo( data ).as( item => {
* if ( !item.foo ) {
* return null;
* } else if ( item.foo == 'bar' ) {
* return new BarView();
* } else {
* return new DifferentView();
* }
* } );
*
* // As new data arrive to the collection, each is handled individually by the callback.
* // This will produce BarView.
* data.add( { foo: 'bar' } );
*
* // And this one will become DifferentView.
* data.add( { foo: 'baz' } );
*
* // Also there will be no view for data lacking the `foo` property.
* data.add( {} );
*
* console.log( controllers.length == 2 );
*
* // View collection is also updated as the data is removed.
* data.remove( 0 );
* console.log( controllers.length == 1 );
*
* @param {module:utils/collection~Collection} collection A collection to be bound.
* @returns {module:ui/viewcollection~ViewCollection#bindTo#as}
*/
bindTo( collection ) {
return {
/**
* Determines the output view of the binding.
*
* @static
* @param {Function|module:ui/view~View} CallbackOrViewClass Specifies the constructor of the view to be used or
* a custom callback function which produces views.
*/
as: ( CallbackOrViewClass ) => {
let createView;

if ( CallbackOrViewClass.prototype instanceof View ) {
createView = ( item ) => {
const viewInstance = new CallbackOrViewClass( this.locale, item );

this._boundItemsToViewsMap.set( item, viewInstance );

return viewInstance;
};
} else {
createView = ( item ) => {
const viewInstance = CallbackOrViewClass( item );

this._boundItemsToViewsMap.set( item, viewInstance );

return viewInstance;
};
}

// Load the initial content of the collection.
for ( let item of collection ) {
this.add( createView( item ) );
}

// Synchronize views as new items are added to the collection.
this.listenTo( collection, 'add', ( evt, item, index ) => {
this.add( createView( item ), index );
} );

// Synchronize views as items are removed from the collection.
this.listenTo( collection, 'remove', ( evt, item ) => {
this.remove( this._boundItemsToViewsMap.get( item ) );

this._boundItemsToViewsMap.delete( item );
} );
}
};
}

/**
* Delegates selected events coming from within the collection to desired {@link module:utils/emittermixin~EmitterMixin}.
*
Expand Down
135 changes: 0 additions & 135 deletions tests/viewcollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
/* global document, setTimeout */

import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import View from '../src/view';
import ViewCollection from '../src/viewcollection';
Expand All @@ -25,7 +24,6 @@ describe( 'ViewCollection', () => {
expect( collection.locale ).to.be.undefined;
expect( collection.ready ).to.be.false;
expect( collection._parentElement ).to.be.null;
expect( collection._boundItemsToViewsMap ).to.be.instanceOf( Map );
expect( collection._idProperty ).to.equal( 'viewUid' );
} );

Expand Down Expand Up @@ -291,139 +289,6 @@ describe( 'ViewCollection', () => {
} );
} );

describe( 'bindTo()', () => {
class ViewClass extends View {
constructor( locale, data ) {
super( locale );

this.template = new Template( {
tag: 'b'
} );

this.data = data;
}
}

it( 'provides "as()" interface', () => {
const returned = collection.bindTo( {} );

expect( returned ).to.have.keys( 'as' );
expect( returned.as ).to.be.a( 'function' );
} );

describe( 'as()', () => {
it( 'does not chain', () => {
const returned = collection.bindTo( new Collection() ).as( ViewClass );

expect( returned ).to.be.undefined;
} );

it( 'binds collection as a view factory – initial content', () => {
const locale = {};
const items = new Collection();

items.add( { id: '1' } );
items.add( { id: '2' } );

collection = new ViewCollection( locale );
collection.bindTo( items ).as( ViewClass );

expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 1 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 0 ).locale ).to.equal( locale );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
} );

it( 'binds collection as a view factory – new content', () => {
const locale = {};
const items = new Collection();

collection = new ViewCollection( locale );
collection.bindTo( items ).as( ViewClass );

expect( collection ).to.have.length( 0 );

items.add( { id: '1' } );
items.add( { id: '2' } );

expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 1 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 0 ).locale ).to.equal( locale );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
} );

it( 'binds collection as a view factory – item removal', () => {
const locale = {};
const items = new Collection();

collection = new ViewCollection( locale );
collection.bindTo( items ).as( ViewClass );

expect( collection ).to.have.length( 0 );

items.add( { id: '1' } );
items.add( { id: '2' } );

expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 1 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 0 ).locale ).to.equal( locale );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );

items.remove( 1 );
expect( collection.get( 0 ).data ).to.equal( items.get( 0 ) );

items.remove( 0 );
expect( collection ).to.have.length( 0 );
} );

it( 'binds collection as a view factory – custom factory (arrow function)', () => {
const locale = {};
const items = new Collection();

collection = new ViewCollection( locale );
collection.bindTo( items ).as( ( item ) => {
return new ViewClass( locale, item );
} );

expect( collection ).to.have.length( 0 );

items.add( { id: '1' } );
items.add( { id: '2' } );

expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 1 ) ).to.be.instanceOf( ViewClass );
expect( collection.get( 0 ).locale ).to.equal( locale );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
} );

// https://github.com/ckeditor/ckeditor5-ui/issues/113
it( 'binds collection as a view factory – custom factory (normal function)', () => {
const locale = { locale: true };
const items = new Collection();

collection = new ViewCollection( locale );
collection.bindTo( items ).as( function( item ) {
return new ViewClass( locale, item );
} );

items.add( { id: '1' } );

expect( collection ).to.have.length( 1 );

const view = collection.get( 0 );

// Wrong args will be passed to the callback if it's treated as the view constructor.
expect( view ).to.be.instanceOf( ViewClass );
expect( view.locale ).to.equal( locale );
expect( view.data ).to.equal( items.get( 0 ) );
} );
} );
} );

describe( 'delegate()', () => {
it( 'should throw when event names are not strings', () => {
expect( () => {
Expand Down

0 comments on commit 5b55987

Please sign in to comment.