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

Commit

Permalink
feat(virtualRepeat): Add md-auto-shrink and md-auto-shrink-min
Browse files Browse the repository at this point in the history
Closes #3536.
  • Loading branch information
kseamon authored and Splaktar committed Jul 4, 2015
1 parent 9a313b8 commit 9b23ebf
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 31 deletions.
117 changes: 86 additions & 31 deletions src/components/virtualRepeat/virtualRepeater.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ angular.module('material.components.virtualRepeat', [
*
* @param {boolean=} md-orient-horizontal Whether the container should scroll horizontally
* (defaults to scrolling vertically).
* @param {boolean=} md-auto-shrink When present, the container will shrink to fit
* the number of items when that number is less than its original size.
* @param {number=} md-auto-shrink-min Minimum number of items that md-auto-shrink
* will shrink to (default: 0).
*/
function VirtualRepeatContainerDirective() {
return {
Expand Down Expand Up @@ -89,6 +93,12 @@ function VirtualRepeatContainerController($$rAF, $scope, $element, $attrs) {
this.horizontal = this.$attrs.hasOwnProperty('mdOrientHorizontal');
/** @type {!VirtualRepeatController} The repeater inside of this container */
this.repeater = null;
/** @type {boolean} Whether auto-shrink is enabled */
this.autoShrink = this.$attrs.hasOwnProperty('mdAutoShrink');
/** @type {number} Minimum number of items to auto-shrink to */
this.autoShrinkMin = parseInt(this.$attrs.mdAutoShrinkMin, 10) || 0;
/** @type {?number} Original container size when shrank */
this.originalSize = null;

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


/**
* Resizes the container.
* @private
* @param {number} The new size to set.
*/
VirtualRepeatContainerController.prototype.setSize_ = function(size) {
this.size = size;
this.$element[0].style[this.isHorizontal() ? 'width' : 'height'] = size + 'px';
};


/** Instructs the container to re-measure its size. */
VirtualRepeatContainerController.prototype.updateSize = function() {
if (this.originalSize) return;

this.size = this.isHorizontal()
? this.$element[0].clientWidth
: this.$element[0].clientHeight;
Expand All @@ -145,44 +168,76 @@ VirtualRepeatContainerController.prototype.getScrollSize = function() {


/**
* Sets the scrollHeight or scrollWidth. Called by the repeater based on
* its item count and item size.
* Sets the scroller element to the specified size.
* @private
* @param {number} size The new size.
*/
VirtualRepeatContainerController.prototype.setScrollSize = function(size) {
if (this.scrollSize !== size) {
var dimension = this.isHorizontal() ? 'width' : 'height';
var crossDimension = this.isHorizontal() ? 'height' : 'width';

// If the size falls within the browser's maximum explicit size for a single element, we can
// set the size and be done. Otherwise, we have to create children that add up the the desired
// size.
if (size < MAX_ELEMENT_SIZE) {
this.sizer.style[dimension] = size + 'px';
} else {
// Clear any existing dimensions.
this.sizer.innerHTML = '';
this.sizer.style[dimension] = 'auto';
this.sizer.style[crossDimension] = 'auto';

// Divide the total size we have to render into N max-size pieces.
var numChildren = Math.floor(size / MAX_ELEMENT_SIZE);

// Element template to clone for each max-size piece.
var sizerChild = document.createElement('div');
sizerChild.style[dimension] = MAX_ELEMENT_SIZE + 'px';
sizerChild.style[crossDimension] = '1px';

for (var i = 0; i < numChildren; i++) {
this.sizer.appendChild(sizerChild.cloneNode(false));
VirtualRepeatContainerController.prototype.sizeScroller_ = function(size) {
var dimension = this.isHorizontal() ? 'width' : 'height';
var crossDimension = this.isHorizontal() ? 'height' : 'width';

// If the size falls within the browser's maximum explicit size for a single element, we can
// set the size and be done. Otherwise, we have to create children that add up the the desired
// size.
if (size < MAX_ELEMENT_SIZE) {
this.sizer.style[dimension] = size + 'px';
} else {
// Clear any existing dimensions.
this.sizer.innerHTML = '';
this.sizer.style[dimension] = 'auto';
this.sizer.style[crossDimension] = 'auto';

// Divide the total size we have to render into N max-size pieces.
var numChildren = Math.floor(size / MAX_ELEMENT_SIZE);

// Element template to clone for each max-size piece.
var sizerChild = document.createElement('div');
sizerChild.style[dimension] = MAX_ELEMENT_SIZE + 'px';
sizerChild.style[crossDimension] = '1px';

for (var i = 0; i < numChildren; i++) {
this.sizer.appendChild(sizerChild.cloneNode(false));
}

// Re-use the element template for the remainder.
sizerChild.style[dimension] = (size - (numChildren * MAX_ELEMENT_SIZE)) + 'px';
this.sizer.appendChild(sizerChild);
}
};


/**
* If auto-shrinking is enabled, shrinks or unshrinks as appropriate.
* @private
* @param {number} size The new size.
*/
VirtualRepeatContainerController.prototype.autoShrink_ = function(size) {
var shrinkSize = Math.max(size, this.autoShrinkMin * this.repeater.getItemSize());
if (this.autoShrink && shrinkSize !== this.size) {
if (shrinkSize < (this.originalSize || this.size)) {
if (!this.originalSize) {
this.originalSize = this.size;
}

// Re-use the element template for the remainder.
sizerChild.style[dimension] = (size - (numChildren * MAX_ELEMENT_SIZE)) + 'px';
this.sizer.appendChild(sizerChild);
this.setSize_(shrinkSize);
} else if (this.originalSize) {
this.setSize_(this.originalSize);
this.originalSize = null;
}
}
};


/**
* Sets the scrollHeight or scrollWidth. Called by the repeater based on
* its item count and item size.
* @param {number} size The new size.
*/
VirtualRepeatContainerController.prototype.setScrollSize = function(size) {
if (this.scrollSize === size) return;

this.sizeScroller_(size);
this.autoShrink_(size);
this.scrollSize = size;
};

Expand Down
100 changes: 100 additions & 0 deletions src/components/virtualRepeat/virtualRepeater.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,106 @@ describe('<md-virtual-repeat>', function() {
expect(scroller[0].scrollTop).toBe(10 * ITEM_SIZE);
});

it('should shrink the container when the number of items goes down (vertical)', function() {
container.attr('md-auto-shrink', '');
createRepeater();
scope.items = createItems(NUM_ITEMS);
scope.$apply();
$$rAF.flush();

expect(container[0].offsetHeight).toBe(100);

// With 5 items...
scope.items = createItems(5);
scope.$apply();
expect(container[0].offsetHeight).toBe(5 * ITEM_SIZE);

// With 0 items...
scope.items = [];
scope.$apply();
expect(container[0].offsetHeight).toBe(0);

// With lots of items again...
scope.items = createItems(NUM_ITEMS);
scope.$apply();
expect(container[0].offsetHeight).toBe(100);
});

it('should shrink the container when the number of items goes down (horizontal)', function() {
container.attr({
'md-auto-shrink': '',
'md-orient-horizontal': ''
});
createRepeater();
scope.items = createItems(NUM_ITEMS);
scope.$apply();
$$rAF.flush();

expect(container[0].offsetWidth).toBe(150);

// With 5 items...
scope.items = createItems(5);
scope.$apply();
expect(container[0].offsetWidth).toBe(5 * ITEM_SIZE);

// With 0 items...
scope.items = [];
scope.$apply();
expect(container[0].offsetWidth).toBe(0);

// With lots of items again...
scope.items = createItems(NUM_ITEMS);
scope.$apply();
expect(container[0].offsetWidth).toBe(150);
});

it('should not shrink below the specified md-auto-shrink-min (vertical)', function() {
container.attr({
'md-auto-shrink': '',
'md-auto-shrink-min': '2'
});
createRepeater();
scope.items = createItems(NUM_ITEMS);
scope.$apply();
$$rAF.flush();

expect(container[0].offsetHeight).toBe(100);

// With 5 items...
scope.items = createItems(5);
scope.$apply();
expect(container[0].offsetHeight).toBe(5 * ITEM_SIZE);

// With 0 items...
scope.items = [];
scope.$apply();
expect(container[0].offsetHeight).toBe(2 * ITEM_SIZE);
});

it('should not shrink below the specified md-auto-shrink-min (horizontal)', function() {
container.attr({
'md-auto-shrink': '',
'md-auto-shrink-min': '2',
'md-orient-horizontal': ''
});
createRepeater();
scope.items = createItems(NUM_ITEMS);
scope.$apply();
$$rAF.flush();

expect(container[0].offsetWidth).toBe(150);

// With 5 items...
scope.items = createItems(5);
scope.$apply();
expect(container[0].offsetWidth).toBe(5 * ITEM_SIZE);

// With 0 items...
scope.items = [];
scope.$apply();
expect(container[0].offsetWidth).toBe(2 * ITEM_SIZE);
});

/**
* Facade to access transform properly even when jQuery is used;
* since jQuery's css function is obtaining the computed style (not wanted)
Expand Down

0 comments on commit 9b23ebf

Please sign in to comment.