Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 79dcbf7

Browse files
kseamonThomasBurleson
authored andcommitted
feat(virtualRepeat): Add md-auto-shrink and md-auto-shrink-min
Closes #3536.
1 parent 6fc9212 commit 79dcbf7

File tree

2 files changed

+186
-31
lines changed

2 files changed

+186
-31
lines changed

src/components/virtualRepeat/virtualRepeater.js

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ angular.module('material.components.virtualRepeat', [
3131
*
3232
* @param {boolean=} md-orient-horizontal Whether the container should scroll horizontally
3333
* (defaults to scrolling vertically).
34+
* @param {boolean=} md-auto-shrink When present, the container will shrink to fit
35+
* the number of items when that number is less than its original size.
36+
* @param {number=} md-auto-shrink-min Minimum number of items that md-auto-shrink
37+
* will shrink to (default: 0).
3438
*/
3539
function VirtualRepeatContainerDirective() {
3640
return {
@@ -89,6 +93,12 @@ function VirtualRepeatContainerController($$rAF, $scope, $element, $attrs) {
8993
this.horizontal = this.$attrs.hasOwnProperty('mdOrientHorizontal');
9094
/** @type {!VirtualRepeatController} The repeater inside of this container */
9195
this.repeater = null;
96+
/** @type {boolean} Whether auto-shrink is enabled */
97+
this.autoShrink = this.$attrs.hasOwnProperty('mdAutoShrink');
98+
/** @type {number} Minimum number of items to auto-shrink to */
99+
this.autoShrinkMin = parseInt(this.$attrs.mdAutoShrinkMin, 10) || 0;
100+
/** @type {?number} Original container size when shrank */
101+
this.originalSize = null;
92102

93103
this.scroller = $element[0].getElementsByClassName('md-virtual-repeat-scroller')[0];
94104
this.sizer = this.scroller.getElementsByClassName('md-virtual-repeat-sizer')[0];
@@ -129,8 +139,21 @@ VirtualRepeatContainerController.prototype.getSize = function() {
129139
};
130140

131141

142+
/**
143+
* Resizes the container.
144+
* @private
145+
* @param {number} The new size to set.
146+
*/
147+
VirtualRepeatContainerController.prototype.setSize_ = function(size) {
148+
this.size = size;
149+
this.$element[0].style[this.isHorizontal() ? 'width' : 'height'] = size + 'px';
150+
};
151+
152+
132153
/** Instructs the container to re-measure its size. */
133154
VirtualRepeatContainerController.prototype.updateSize = function() {
155+
if (this.originalSize) return;
156+
134157
this.size = this.isHorizontal()
135158
? this.$element[0].clientWidth
136159
: this.$element[0].clientHeight;
@@ -145,44 +168,76 @@ VirtualRepeatContainerController.prototype.getScrollSize = function() {
145168

146169

147170
/**
148-
* Sets the scrollHeight or scrollWidth. Called by the repeater based on
149-
* its item count and item size.
171+
* Sets the scroller element to the specified size.
172+
* @private
150173
* @param {number} size The new size.
151174
*/
152-
VirtualRepeatContainerController.prototype.setScrollSize = function(size) {
153-
if (this.scrollSize !== size) {
154-
var dimension = this.isHorizontal() ? 'width' : 'height';
155-
var crossDimension = this.isHorizontal() ? 'height' : 'width';
156-
157-
// If the size falls within the browser's maximum explicit size for a single element, we can
158-
// set the size and be done. Otherwise, we have to create children that add up the the desired
159-
// size.
160-
if (size < MAX_ELEMENT_SIZE) {
161-
this.sizer.style[dimension] = size + 'px';
162-
} else {
163-
// Clear any existing dimensions.
164-
this.sizer.innerHTML = '';
165-
this.sizer.style[dimension] = 'auto';
166-
this.sizer.style[crossDimension] = 'auto';
167-
168-
// Divide the total size we have to render into N max-size pieces.
169-
var numChildren = Math.floor(size / MAX_ELEMENT_SIZE);
170-
171-
// Element template to clone for each max-size piece.
172-
var sizerChild = document.createElement('div');
173-
sizerChild.style[dimension] = MAX_ELEMENT_SIZE + 'px';
174-
sizerChild.style[crossDimension] = '1px';
175-
176-
for (var i = 0; i < numChildren; i++) {
177-
this.sizer.appendChild(sizerChild.cloneNode(false));
175+
VirtualRepeatContainerController.prototype.sizeScroller_ = function(size) {
176+
var dimension = this.isHorizontal() ? 'width' : 'height';
177+
var crossDimension = this.isHorizontal() ? 'height' : 'width';
178+
179+
// If the size falls within the browser's maximum explicit size for a single element, we can
180+
// set the size and be done. Otherwise, we have to create children that add up the the desired
181+
// size.
182+
if (size < MAX_ELEMENT_SIZE) {
183+
this.sizer.style[dimension] = size + 'px';
184+
} else {
185+
// Clear any existing dimensions.
186+
this.sizer.innerHTML = '';
187+
this.sizer.style[dimension] = 'auto';
188+
this.sizer.style[crossDimension] = 'auto';
189+
190+
// Divide the total size we have to render into N max-size pieces.
191+
var numChildren = Math.floor(size / MAX_ELEMENT_SIZE);
192+
193+
// Element template to clone for each max-size piece.
194+
var sizerChild = document.createElement('div');
195+
sizerChild.style[dimension] = MAX_ELEMENT_SIZE + 'px';
196+
sizerChild.style[crossDimension] = '1px';
197+
198+
for (var i = 0; i < numChildren; i++) {
199+
this.sizer.appendChild(sizerChild.cloneNode(false));
200+
}
201+
202+
// Re-use the element template for the remainder.
203+
sizerChild.style[dimension] = (size - (numChildren * MAX_ELEMENT_SIZE)) + 'px';
204+
this.sizer.appendChild(sizerChild);
205+
}
206+
};
207+
208+
209+
/**
210+
* If auto-shrinking is enabled, shrinks or unshrinks as appropriate.
211+
* @private
212+
* @param {number} size The new size.
213+
*/
214+
VirtualRepeatContainerController.prototype.autoShrink_ = function(size) {
215+
var shrinkSize = Math.max(size, this.autoShrinkMin * this.repeater.getItemSize());
216+
if (this.autoShrink && shrinkSize !== this.size) {
217+
if (shrinkSize < (this.originalSize || this.size)) {
218+
if (!this.originalSize) {
219+
this.originalSize = this.size;
178220
}
179221

180-
// Re-use the element template for the remainder.
181-
sizerChild.style[dimension] = (size - (numChildren * MAX_ELEMENT_SIZE)) + 'px';
182-
this.sizer.appendChild(sizerChild);
222+
this.setSize_(shrinkSize);
223+
} else if (this.originalSize) {
224+
this.setSize_(this.originalSize);
225+
this.originalSize = null;
183226
}
184227
}
228+
};
229+
230+
231+
/**
232+
* Sets the scrollHeight or scrollWidth. Called by the repeater based on
233+
* its item count and item size.
234+
* @param {number} size The new size.
235+
*/
236+
VirtualRepeatContainerController.prototype.setScrollSize = function(size) {
237+
if (this.scrollSize === size) return;
185238

239+
this.sizeScroller_(size);
240+
this.autoShrink_(size);
186241
this.scrollSize = size;
187242
};
188243

src/components/virtualRepeat/virtualRepeater.spec.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,106 @@ describe('<md-virtual-repeat>', function() {
261261
expect(scroller[0].scrollTop).toBe(10 * ITEM_SIZE);
262262
});
263263

264+
it('should shrink the container when the number of items goes down (vertical)', function() {
265+
container.attr('md-auto-shrink', '');
266+
createRepeater();
267+
scope.items = createItems(NUM_ITEMS);
268+
scope.$apply();
269+
$$rAF.flush();
270+
271+
expect(container[0].offsetHeight).toBe(100);
272+
273+
// With 5 items...
274+
scope.items = createItems(5);
275+
scope.$apply();
276+
expect(container[0].offsetHeight).toBe(5 * ITEM_SIZE);
277+
278+
// With 0 items...
279+
scope.items = [];
280+
scope.$apply();
281+
expect(container[0].offsetHeight).toBe(0);
282+
283+
// With lots of items again...
284+
scope.items = createItems(NUM_ITEMS);
285+
scope.$apply();
286+
expect(container[0].offsetHeight).toBe(100);
287+
});
288+
289+
it('should shrink the container when the number of items goes down (horizontal)', function() {
290+
container.attr({
291+
'md-auto-shrink': '',
292+
'md-orient-horizontal': ''
293+
});
294+
createRepeater();
295+
scope.items = createItems(NUM_ITEMS);
296+
scope.$apply();
297+
$$rAF.flush();
298+
299+
expect(container[0].offsetWidth).toBe(150);
300+
301+
// With 5 items...
302+
scope.items = createItems(5);
303+
scope.$apply();
304+
expect(container[0].offsetWidth).toBe(5 * ITEM_SIZE);
305+
306+
// With 0 items...
307+
scope.items = [];
308+
scope.$apply();
309+
expect(container[0].offsetWidth).toBe(0);
310+
311+
// With lots of items again...
312+
scope.items = createItems(NUM_ITEMS);
313+
scope.$apply();
314+
expect(container[0].offsetWidth).toBe(150);
315+
});
316+
317+
it('should not shrink below the specified md-auto-shrink-min (vertical)', function() {
318+
container.attr({
319+
'md-auto-shrink': '',
320+
'md-auto-shrink-min': '2'
321+
});
322+
createRepeater();
323+
scope.items = createItems(NUM_ITEMS);
324+
scope.$apply();
325+
$$rAF.flush();
326+
327+
expect(container[0].offsetHeight).toBe(100);
328+
329+
// With 5 items...
330+
scope.items = createItems(5);
331+
scope.$apply();
332+
expect(container[0].offsetHeight).toBe(5 * ITEM_SIZE);
333+
334+
// With 0 items...
335+
scope.items = [];
336+
scope.$apply();
337+
expect(container[0].offsetHeight).toBe(2 * ITEM_SIZE);
338+
});
339+
340+
it('should not shrink below the specified md-auto-shrink-min (horizontal)', function() {
341+
container.attr({
342+
'md-auto-shrink': '',
343+
'md-auto-shrink-min': '2',
344+
'md-orient-horizontal': ''
345+
});
346+
createRepeater();
347+
scope.items = createItems(NUM_ITEMS);
348+
scope.$apply();
349+
$$rAF.flush();
350+
351+
expect(container[0].offsetWidth).toBe(150);
352+
353+
// With 5 items...
354+
scope.items = createItems(5);
355+
scope.$apply();
356+
expect(container[0].offsetWidth).toBe(5 * ITEM_SIZE);
357+
358+
// With 0 items...
359+
scope.items = [];
360+
scope.$apply();
361+
expect(container[0].offsetWidth).toBe(2 * ITEM_SIZE);
362+
});
363+
264364
/**
265365
* Facade to access transform properly even when jQuery is used;
266366
* since jQuery's css function is obtaining the computed style (not wanted)

0 commit comments

Comments
 (0)