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

Commit cfa7d0e

Browse files
authored
Merge pull request #225 from ckeditor/t/ckeditor5-ui/344
Feature: Introduce `bind().toMany()` binding chain in `ObservableMixin`. Closes #224.
2 parents dc6b226 + b755087 commit cfa7d0e

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed

src/observablemixin.js

+45-2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ const ObservableMixin = {
136136

137137
// @typedef {Object} BindChain
138138
// @property {Function} to See {@link ~ObservableMixin#_bindTo}.
139+
// @property {Function} toMany See {@link ~ObservableMixin#_bindToMany}.
139140
// @property {module:utils/observablemixin~Observable} _observable The observable which initializes the binding.
140141
// @property {Array} _bindProperties Array of `_observable` properties to be bound.
141142
// @property {Array} _to Array of `to()` observable–properties (`{ observable: toObservable, properties: ...toProperties }`).
@@ -144,6 +145,7 @@ const ObservableMixin = {
144145
// initiated in this binding chain.
145146
return {
146147
to: bindTo,
148+
toMany: bindToMany,
147149

148150
_observable: this,
149151
_bindProperties: bindProperties,
@@ -414,6 +416,40 @@ function bindTo( ...args ) {
414416
} );
415417
}
416418

419+
// Binds to an attribute in a set of iterable observables.
420+
//
421+
// @private
422+
// @param {Iterable.<Observable>} observables
423+
// @param {String} attribute
424+
// @param {Function} callback
425+
function bindToMany( observables, attribute, callback ) {
426+
if ( this._bindings.size > 1 ) {
427+
/**
428+
* Binding one attribute to many observables only possible with one attribute.
429+
*
430+
* @error observable-bind-to-many-not-one-binding
431+
*/
432+
throw new CKEditorError( 'observable-bind-to-many-not-one-binding: Cannot bind multiple properties with toMany().' );
433+
}
434+
435+
this.to(
436+
// Bind to #attribute of each observable...
437+
...getBindingTargets( observables, attribute ),
438+
// ...using given callback to parse attribute values.
439+
callback
440+
);
441+
}
442+
443+
// Returns an array of binding components for
444+
// {@link Observable#bind} from a set of iterable observables.
445+
//
446+
// @param {Iterable.<Observable>} observables
447+
// @param {String} attribute
448+
// @returns {Array.<String>}
449+
function getBindingTargets( observables, attribute ) {
450+
return Array.prototype.concat( ...observables.map( observable => [ observable, attribute ] ) );
451+
}
452+
417453
// Check if all entries of the array are of `String` type.
418454
//
419455
// @private
@@ -660,14 +696,21 @@ function attachBindToListeners( observable, toBindings ) {
660696
*
661697
* **Note**: To release the binding use {@link module:utils/observablemixin~Observable#unbind}.
662698
*
699+
* Using `bind().to()` chain:
700+
*
663701
* A.bind( 'a' ).to( B );
664702
* A.bind( 'a' ).to( B, 'b' );
665703
* A.bind( 'a', 'b' ).to( B, 'c', 'd' );
666704
* A.bind( 'a' ).to( B, 'b', C, 'd', ( b, d ) => b + d );
667705
*
706+
* It is also possible to bind to the same property in a observables collection using `bind().toMany()` chain:
707+
*
708+
* A.bind( 'a' ).toMany( [ B, C, D ], 'x', ( a, b, c ) => a + b + c );
709+
* A.bind( 'a' ).toMany( [ B, C, D ], 'x', ( ...x ) => x.every( x => x ) );
710+
*
668711
* @method #bind
669712
* @param {...String} bindProperties Observable properties that will be bound to another observable(s).
670-
* @returns {Object} The bind chain with the `to()` method.
713+
* @returns {Object} The bind chain with the `to()` and `toMany()` methods.
671714
*/
672715

673716
/**
@@ -709,7 +752,7 @@ function attachBindToListeners( observable, toBindings ) {
709752
*
710753
*
711754
* Note: we used a high priority listener here to execute this callback before the one which
712-
* calls the orignal method (which used the default priority).
755+
* calls the original method (which used the default priority).
713756
*
714757
* It's also possible to change the return value:
715758
*

tests/observablemixin.js

+57
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,63 @@ describe( 'Observable', () => {
730730
.to.have.members( [ 'color', 'year', 'color', 'year' ] );
731731
} );
732732
} );
733+
734+
describe( 'toMany()', () => {
735+
let Wheel;
736+
737+
beforeEach( () => {
738+
Wheel = class extends Observable {
739+
};
740+
} );
741+
742+
it( 'should not chain', () => {
743+
expect(
744+
car.bind( 'color' ).toMany( [ new Observable( { color: 'red' } ) ], 'color', () => {} )
745+
).to.be.undefined;
746+
} );
747+
748+
it( 'should throw when binding multiple properties', () => {
749+
let vehicle = new Car();
750+
751+
expect( () => {
752+
vehicle.bind( 'color', 'year' ).toMany( [ car ], 'foo', () => {} );
753+
} ).to.throw( CKEditorError, /observable-bind-to-many-not-one-binding/ );
754+
755+
expect( () => {
756+
vehicle = new Car();
757+
758+
vehicle.bind( 'color', 'year' ).to( car, car, () => {} );
759+
} ).to.throw( CKEditorError, /observable-bind-to-extra-callback/ );
760+
} );
761+
762+
it( 'binds observable property to collection property using callback', () => {
763+
const wheels = [
764+
new Wheel( { isTyrePressureOK: true } ),
765+
new Wheel( { isTyrePressureOK: true } ),
766+
new Wheel( { isTyrePressureOK: true } ),
767+
new Wheel( { isTyrePressureOK: true } )
768+
];
769+
770+
car.bind( 'showTyrePressureWarning' ).toMany( wheels, 'isTyrePressureOK', ( ...areEnabled ) => {
771+
// Every tyre must have OK pressure.
772+
return !areEnabled.every( isTyrePressureOK => isTyrePressureOK );
773+
} );
774+
775+
expect( car.showTyrePressureWarning ).to.be.false;
776+
777+
wheels[ 0 ].isTyrePressureOK = false;
778+
779+
expect( car.showTyrePressureWarning ).to.be.true;
780+
781+
wheels[ 0 ].isTyrePressureOK = true;
782+
783+
expect( car.showTyrePressureWarning ).to.be.false;
784+
785+
wheels[ 1 ].isTyrePressureOK = false;
786+
787+
expect( car.showTyrePressureWarning ).to.be.true;
788+
} );
789+
} );
733790
} );
734791

735792
describe( 'unbind()', () => {

0 commit comments

Comments
 (0)