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

Commit bfe8744

Browse files
committed
Feature: View collection can now also be initialized though construction.
Ported changes from ckeditor/ckeditor5-utils#309 (1aac49db1ca9ea914f9fc44f00e295474faf3ed2). Note this breaks BodyCollection integration, so there's more work to be done.
1 parent e4ead2f commit bfe8744

File tree

5 files changed

+95
-80
lines changed

5 files changed

+95
-80
lines changed

src/view.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,11 @@ export default class View {
276276
* // <p><child#element></p>
277277
* view.items.add( child );
278278
*
279+
* @param {Array.<module:ui/view~View>} [views] Initial views of the collection.
279280
* @returns {module:ui/viewcollection~ViewCollection} A new collection of view instances.
280281
*/
281-
createCollection() {
282-
const collection = new ViewCollection();
282+
createCollection( views ) {
283+
const collection = new ViewCollection( views );
283284

284285
this._viewCollections.add( collection );
285286

src/viewcollection.js

+33-18
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,18 @@ export default class ViewCollection extends Collection {
5151
/**
5252
* Creates a new instance of the {@link module:ui/viewcollection~ViewCollection}.
5353
*
54-
* @param {module:utils/locale~Locale} [locale] The {@link module:core/editor/editor~Editor editor's locale} instance.
54+
* @param {Array.<module:ui/view~View>} [initialItems] The initial items of the collection.
5555
*/
56-
constructor( locale ) {
57-
super( {
56+
constructor( initialItems = [] ) {
57+
super( initialItems, {
5858
// An #id Number attribute should be legal and not break the `ViewCollection` instance.
5959
// https://github.com/ckeditor/ckeditor5-ui/issues/93
6060
idProperty: 'viewUid'
6161
} );
6262

6363
// Handle {@link module:ui/view~View#element} in DOM when a new view is added to the collection.
6464
this.on( 'add', ( evt, view, index ) => {
65-
if ( !view.isRendered ) {
66-
view.render();
67-
}
68-
69-
if ( view.element && this._parentElement ) {
70-
this._parentElement.insertBefore( view.element, this._parentElement.children[ index ] );
71-
}
65+
this._renderViewIntoCollectionParent( view, index );
7266
} );
7367

7468
// Handle {@link module:ui/view~View#element} in DOM when a view is removed from the collection.
@@ -78,14 +72,6 @@ export default class ViewCollection extends Collection {
7872
}
7973
} );
8074

81-
/**
82-
* The {@link module:core/editor/editor~Editor#locale editor's locale} instance.
83-
* See the view {@link module:ui/view~View#locale locale} property.
84-
*
85-
* @member {module:utils/locale~Locale}
86-
*/
87-
this.locale = locale;
88-
8975
/**
9076
* A parent element within which child views are rendered and managed in DOM.
9177
*
@@ -112,6 +98,11 @@ export default class ViewCollection extends Collection {
11298
*/
11399
setParent( elementOrDocFragment ) {
114100
this._parentElement = elementOrDocFragment;
101+
102+
// Take care of the initial collection items passed to the constructor.
103+
for ( const view of this ) {
104+
this._renderViewIntoCollectionParent( view );
105+
}
115106
}
116107

117108
/**
@@ -194,6 +185,30 @@ export default class ViewCollection extends Collection {
194185
};
195186
}
196187

188+
/**
189+
* This method {@link module:ui/view~View#render renders} a new view added to the collection.
190+
*
191+
* If the {@link #_parentElement parent element} of the collection is set, this method also adds
192+
* the view's {@link module:ui/view~View#element} as a child of the parent in DOM at a specified index.
193+
*
194+
* **Note**: If index is not specified, the view's element is pushed as the last child
195+
* of the parent element.
196+
*
197+
* @private
198+
* @param {module:ui/view~View} view A new view added to the collection.
199+
* @param {Number} [index] An index the view holds in the collection. When not specified,
200+
* the view is added at the end.
201+
*/
202+
_renderViewIntoCollectionParent( view, index ) {
203+
if ( !view.isRendered ) {
204+
view.render();
205+
}
206+
207+
if ( view.element && this._parentElement ) {
208+
this._parentElement.insertBefore( view.element, this._parentElement.children[ index ] );
209+
}
210+
}
211+
197212
/**
198213
* Removes a child view from the collection. If the {@link #setParent parent element} of the
199214
* collection has been set, the {@link module:ui/view~View#element element} of the view is also removed

tests/focuscycler.js

+19-56
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,21 @@ describe( 'FocusCycler', () => {
1717
testUtils.createSinonSandbox();
1818

1919
beforeEach( () => {
20-
focusables = new ViewCollection();
20+
testUtils.sinon.stub( global.window, 'getComputedStyle' );
21+
focusables = new ViewCollection( [
22+
nonFocusable(),
23+
focusable(),
24+
focusable(),
25+
focusable(),
26+
nonFocusable()
27+
] );
2128
focusTracker = {
2229
focusedElement: null
2330
};
2431
cycler = new FocusCycler( {
2532
focusables,
2633
focusTracker
2734
} );
28-
29-
testUtils.sinon.stub( global.window, 'getComputedStyle' );
30-
31-
focusables.add( nonFocusable() );
32-
focusables.add( focusable() );
33-
focusables.add( focusable() );
34-
focusables.add( focusable() );
35-
focusables.add( nonFocusable() );
3635
} );
3736

3837
describe( 'constructor()', () => {
@@ -60,12 +59,9 @@ describe( 'FocusCycler', () => {
6059
} );
6160

6261
it( 'returns null when no focusable items', () => {
63-
focusables = new ViewCollection();
62+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
6463
cycler = new FocusCycler( { focusables, focusTracker } );
6564

66-
focusables.add( nonFocusable() );
67-
focusables.add( nonFocusable() );
68-
6965
expect( cycler.first ).to.be.null;
7066
} );
7167

@@ -83,12 +79,9 @@ describe( 'FocusCycler', () => {
8379
} );
8480

8581
it( 'returns null when no focusable items', () => {
86-
focusables = new ViewCollection();
82+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
8783
cycler = new FocusCycler( { focusables, focusTracker } );
8884

89-
focusables.add( nonFocusable() );
90-
focusables.add( nonFocusable() );
91-
9285
expect( cycler.last ).to.be.null;
9386
} );
9487

@@ -126,23 +119,16 @@ describe( 'FocusCycler', () => {
126119
} );
127120

128121
it( 'returns null when no focusable items', () => {
129-
focusables = new ViewCollection();
122+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
130123
cycler = new FocusCycler( { focusables, focusTracker } );
131124

132-
focusables.add( nonFocusable() );
133-
focusables.add( nonFocusable() );
134-
135125
expect( cycler.next ).to.be.null;
136126
} );
137127

138128
it( 'returns null if the only focusable in focusables', () => {
139-
focusables = new ViewCollection();
129+
focusables = new ViewCollection( [ nonFocusable(), focusable(), nonFocusable() ] );
140130
cycler = new FocusCycler( { focusables, focusTracker } );
141131

142-
focusables.add( nonFocusable() );
143-
focusables.add( focusable() );
144-
focusables.add( nonFocusable() );
145-
146132
focusTracker.focusedElement = focusables.get( 1 ).element;
147133

148134
expect( cycler.first ).to.equal( focusables.get( 1 ) );
@@ -176,23 +162,16 @@ describe( 'FocusCycler', () => {
176162
} );
177163

178164
it( 'returns null when no focusable items', () => {
179-
focusables = new ViewCollection();
165+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
180166
cycler = new FocusCycler( { focusables, focusTracker } );
181167

182-
focusables.add( nonFocusable() );
183-
focusables.add( nonFocusable() );
184-
185168
expect( cycler.previous ).to.be.null;
186169
} );
187170

188171
it( 'returns null if the only focusable in focusables', () => {
189-
focusables = new ViewCollection();
172+
focusables = new ViewCollection( [ nonFocusable(), focusable(), nonFocusable() ] );
190173
cycler = new FocusCycler( { focusables, focusTracker } );
191174

192-
focusables.add( nonFocusable() );
193-
focusables.add( focusable() );
194-
focusables.add( nonFocusable() );
195-
196175
focusTracker.focusedElement = focusables.get( 1 ).element;
197176

198177
expect( cycler.first ).to.equal( focusables.get( 1 ) );
@@ -208,12 +187,9 @@ describe( 'FocusCycler', () => {
208187
} );
209188

210189
it( 'does not throw when no focusable items', () => {
211-
focusables = new ViewCollection();
190+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
212191
cycler = new FocusCycler( { focusables, focusTracker } );
213192

214-
focusables.add( nonFocusable() );
215-
focusables.add( nonFocusable() );
216-
217193
expect( () => {
218194
cycler.focusFirst();
219195
} ).to.not.throw();
@@ -231,11 +207,7 @@ describe( 'FocusCycler', () => {
231207
it( 'ignores invisible items', () => {
232208
const item = focusable();
233209

234-
focusables = new ViewCollection();
235-
focusables.add( nonFocusable() );
236-
focusables.add( focusable( true ) );
237-
focusables.add( item );
238-
210+
focusables = new ViewCollection( [ nonFocusable(), focusable( true ), item ] );
239211
cycler = new FocusCycler( { focusables, focusTracker } );
240212

241213
cycler.focusFirst();
@@ -251,12 +223,9 @@ describe( 'FocusCycler', () => {
251223
} );
252224

253225
it( 'does not throw when no focusable items', () => {
254-
focusables = new ViewCollection();
226+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
255227
cycler = new FocusCycler( { focusables, focusTracker } );
256228

257-
focusables.add( nonFocusable() );
258-
focusables.add( nonFocusable() );
259-
260229
expect( () => {
261230
cycler.focusLast();
262231
} ).to.not.throw();
@@ -281,12 +250,9 @@ describe( 'FocusCycler', () => {
281250
} );
282251

283252
it( 'does not throw when no focusable items', () => {
284-
focusables = new ViewCollection();
253+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
285254
cycler = new FocusCycler( { focusables, focusTracker } );
286255

287-
focusables.add( nonFocusable() );
288-
focusables.add( nonFocusable() );
289-
290256
expect( () => {
291257
cycler.focusNext();
292258
} ).to.not.throw();
@@ -311,12 +277,9 @@ describe( 'FocusCycler', () => {
311277
} );
312278

313279
it( 'does not throw when no focusable items', () => {
314-
focusables = new ViewCollection();
280+
focusables = new ViewCollection( [ nonFocusable(), nonFocusable() ] );
315281
cycler = new FocusCycler( { focusables, focusTracker } );
316282

317-
focusables.add( nonFocusable() );
318-
focusables.add( nonFocusable() );
319-
320283
expect( () => {
321284
cycler.focusPrevious();
322285
} ).to.not.throw();

tests/view.js

+11
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ describe( 'View', () => {
8989
expect( view._viewCollections ).to.have.length( 2 );
9090
expect( view._viewCollections.get( 1 ) ).to.equal( collection );
9191
} );
92+
93+
it( 'accepts initial views', () => {
94+
const viewA = new View();
95+
const viewB = new View();
96+
97+
const collection = view.createCollection( [ viewA, viewB ] );
98+
99+
expect( collection ).to.have.length( 2 );
100+
expect( collection.get( 0 ) ).to.equal( viewA );
101+
expect( collection.get( 1 ) ).to.equal( viewB );
102+
} );
92103
} );
93104

94105
describe( 'registerChild()', () => {

tests/viewcollection.js

+29-4
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@ describe( 'ViewCollection', () => {
2020

2121
describe( 'constructor()', () => {
2222
it( 'sets basic properties and attributes', () => {
23-
expect( collection.locale ).to.be.undefined;
2423
expect( collection._parentElement ).to.be.null;
2524
expect( collection._idProperty ).to.equal( 'viewUid' );
2625
} );
2726

28-
it( 'accepts locale and defines the locale property', () => {
29-
const locale = { t() {} };
27+
it( 'allows setting initial collection items', () => {
28+
const view1 = new View();
29+
const view2 = new View();
30+
const collection = new ViewCollection( [ view1, view2 ] );
3031

31-
expect( new ViewCollection( locale ).locale ).to.equal( locale );
32+
expect( collection ).to.have.length( 2 );
33+
expect( collection.get( 0 ) ).to.equal( view1 );
34+
expect( collection.get( 1 ) ).to.equal( view2 );
3235
} );
3336

3437
describe( 'child view management in DOM', () => {
@@ -165,6 +168,28 @@ describe( 'ViewCollection', () => {
165168
collection.setParent( el );
166169
expect( collection._parentElement ).to.equal( el );
167170
} );
171+
172+
it( 'udpates initial collection items in DOM', () => {
173+
const view1 = new View();
174+
view1.element = document.createElement( 'i' );
175+
sinon.spy( view1, 'render' );
176+
177+
const view2 = new View();
178+
view2.element = document.createElement( 'b' );
179+
sinon.spy( view2, 'render' );
180+
181+
const collection = new ViewCollection( [ view1, view2 ] );
182+
const parentElement = document.createElement( 'div' );
183+
184+
expect( collection ).to.have.length( 2 );
185+
expect( collection.get( 0 ) ).to.equal( view1 );
186+
expect( collection.get( 1 ) ).to.equal( view2 );
187+
188+
collection.setParent( parentElement );
189+
expect( normalizeHtml( parentElement.outerHTML ) ).to.equal( '<div><i></i><b></b></div>' );
190+
sinon.assert.calledOnce( view1.render );
191+
sinon.assert.calledOnce( view2.render );
192+
} );
168193
} );
169194

170195
describe( 'delegate()', () => {

0 commit comments

Comments
 (0)