diff --git a/src/lib/template/dom-repeat.html b/src/lib/template/dom-repeat.html
index b75f8ed496..f39144cee4 100644
--- a/src/lib/template/dom-repeat.html
+++ b/src/lib/template/dom-repeat.html
@@ -241,18 +241,6 @@
value: 20
},
- /**
- * Maximum number of removed instances to pool for reuse when rows are
- * added in a future turn. By default, pooling is enabled.
- *
- * Set to 0 to disable pooling, which will allow all removed instances to
- * be garbage collected.
- */
- poolSize: {
- type: Number,
- value: 1000
- },
-
_targetFrameTime: {
computed: '_computeFrameTime(targetFramerate)'
}
@@ -338,10 +326,10 @@
return Math.ceil(1000/rate);
},
- _initializeChunkCount: function(initialCount, chunkCount) {
- if (initialCount) {
- this.limit = initialCount;
- this._chunkCount = parseInt(chunkCount, 10) || initialCount;
+ _initializeChunkCount: function() {
+ if (this.initialCount) {
+ this.limit = this.initialCount;
+ this._chunkCount = parseInt(this.chunkCount, 10) || this.initialCount;
}
},
@@ -387,6 +375,9 @@
this._error(this._logf('dom-repeat', 'expected array for `items`,' +
' found', this.items));
}
+ if (this._instances.length && this.initialCount) {
+ this._initializeChunkCount();
+ }
this._keySplices = [];
this._indexSplices = [];
this._needFullRefresh = true;
@@ -472,8 +463,12 @@
}
}
// Reset the pool
- // TODO(kschaaf): Allow pool to be reused across turns & between nested
- // peer repeats (requires updating parentProps when reusing from pool)
+ // TODO(kschaaf): Reuse pool across turns and nested templates
+ // Requires updating parentProps and dealing with the fact that path
+ // notifications won't reach instances sitting in the pool, which
+ // could result in out-of-sync instances since simply re-setting
+ // `item` may not be sufficient if the pooled instance happens to be
+ // the same item.
this._pool.length = 0;
// Notify users
this.fire('dom-change');
@@ -518,10 +513,8 @@
var key = keys[i];
var inst = this._instances[i];
if (inst) {
- if (inst.isPlaceholder) {
- inst.__key__ = key;
- } else {
- inst.__setProperty('__key__', key, true);
+ inst.__key__ = key;
+ if (!inst.isPlaceholder && i < this.limit) {
inst.__setProperty(this.as, c.getItem(key), true);
}
} else {
@@ -661,7 +654,9 @@
var el = inst._children[i];
Polymer.dom(inst.root).appendChild(el);
}
- this._pool.push(inst);
+ if (!keepInstance) {
+ this._pool.push(inst);
+ }
}
if (!keepInstance) {
this._instances.splice(idx, 1);
@@ -669,6 +664,13 @@
return inst;
},
+ _detachAllRows: function() {
+ for (var i=0; i= this.limit) {
@@ -678,6 +680,8 @@
};
} else {
if (inst = this._pool.pop()) {
+ // TODO(kschaaf): If the pool is shared across turns, parentProps
+ // need to be re-set to reused instances in addition to item/key
inst.__setProperty(this.as, this.collection.getItem(key), true);
inst.__setProperty('__key__', key, true);
} else {
@@ -749,18 +753,24 @@
// Called as side-effect of a host property change, responsible for
// notifying parent path change on each inst
_forwardParentProp: function(prop, value) {
- this._instances.forEach(function(inst) {
- inst.__setProperty(prop, value, true);
- }, this);
+ for (var i=0, i$=this._instances, il=i$.length; i. path change,
@@ -771,7 +781,7 @@
var key = path.substring(0, dot < 0 ? path.length : dot);
var idx = this._keyToInstIdx[key];
var inst = this._instances[idx];
- if (inst) {
+ if (inst && !inst.isPlaceholder) {
if (dot >= 0) {
path = this.as + '.' + path.substring(dot+1);
inst._notifyPath(path, value, true);
diff --git a/test/unit/dom-repeat-elements.html b/test/unit/dom-repeat-elements.html
index 274294dae0..0ef268b41e 100644
--- a/test/unit/dom-repeat-elements.html
+++ b/test/unit/dom-repeat-elements.html
@@ -411,3 +411,64 @@
});
+
+
+
+
+ {{item.prop}}
+
+
+
+
+
+
+
+
+ {{item.prop}}
+
+
+
+
diff --git a/test/unit/dom-repeat.html b/test/unit/dom-repeat.html
index 65de283c22..45eff0fe18 100644
--- a/test/unit/dom-repeat.html
+++ b/test/unit/dom-repeat.html
@@ -71,6 +71,12 @@ inDocumentRepeater
x-primitive-large
+ x-repeat-limit
+
+
+ x-repeat-chunked
+
+
@@ -93,6 +99,7 @@ x-primitive-large
stamped[38] .. 3-3-3
*/
+
suite('errors', function() {
test('items must be array', function() {
@@ -3348,6 +3355,501 @@ x-primitive-large
});
+
+ suite('limit', function() {
+
+ var checkItemOrder = function(stamped) {
+ for (var i=0; i lastLength);
+ }
+ if (stamped.length < 100) {
+ lastLength = stamped.length;
+ checkUntilComplete();
+ } else {
+ // Final rendering at exact item count
+ assert.equal(stamped.length, 100);
+ done();
+ }
+ };
+ var checkUntilComplete = function() {
+ // On polyfilled MO, need to wait one setTimeout before rAF
+ if (MutationObserver._isPolyfilled) {
+ setTimeout(function() {
+ requestAnimationFrame(checkCount);
+ });
+ } else {
+ requestAnimationFrame(checkCount);
+ }
+ };
+
+ chunked.items = chunked.preppedItems.slice();
+ checkUntilComplete();
+
+ });
+
+ test('mutations during chunked rendering', function(done) {
+
+ var checkItemOrder = function(stamped) {
+ var last = -1;
+ for (var i=0; i last);
+ last = curr;
+ }
+ };
+
+ var mutateArray = function(repeater, renderedCount) {
+ // The goal here is to remove & add some, and do it over
+ // the threshold of where we have currently rendered items, and
+ // ensure that the prop values of the newly inserted items are in
+ // ascending order so we can do a simple check in checkItemOrder
+ var overlap = 2;
+ var remove = 4;
+ var add = 6;
+ var start = renderedCount.length - overlap;
+ if (start + add < repeater.items.length) {
+ var end = start + remove;
+ var args = ['items', start, remove];
+ var startVal = repeater.items[start].prop;
+ var endVal = repeater.items[end].prop;
+ var delta = (endVal - startVal) / add;
+ for (var i=0; i lastLength);
+ }
+ if (stamped.length < chunked.items.length) {
+ if (mutateCount-- > 0) {
+ mutateArray(chunked, stamped);
+ }
+ lastLength = stamped.length;
+ checkUntilComplete();
+ } else {
+ // Final rendering at exact item count
+ assert.equal(stamped.length, chunked.items.length);
+ done();
+ }
+ };
+ var checkUntilComplete = function() {
+ // On polyfilled MO, need to wait one setTimeout before rAF
+ if (MutationObserver._isPolyfilled) {
+ setTimeout(function() {
+ requestAnimationFrame(checkCount);
+ });
+ } else {
+ requestAnimationFrame(checkCount);
+ }
+ };
+
+ chunked.items = chunked.preppedItems.slice();
+ checkUntilComplete();
+
+ });
+
+
+ test('mutations during chunked rendering, sort & filtered', function(done) {
+
+ var checkItemOrder = function(stamped) {
+ var last = Infinity;
+ for (var i=0; i lastLength);
+ }
+ }
+ if (stamped.length < filteredLength) {
+ if (mutateCount-- > 0) {
+ mutateArray(chunked, stamped);
+ }
+ lastLength = stamped.length;
+ checkUntilComplete();
+ } else {
+ assert.equal(stamped.length, filteredLength);
+ done();
+ }
+ };
+ var checkUntilComplete = function() {
+ // On polyfilled MO, need to wait one setTimeout before rAF
+ if (MutationObserver._isPolyfilled) {
+ setTimeout(function() {
+ requestAnimationFrame(checkCount);
+ });
+ } else {
+ requestAnimationFrame(checkCount);
+ }
+ };
+
+ chunked.$.repeater.sort = function(a, b) {
+ return b.prop - a.prop;
+ };
+ chunked.$.repeater.filter = function(a) {
+ return (a.prop % 2) === 0;
+ };
+ chunked.items = chunked.preppedItems.slice();
+ checkUntilComplete();
+
+ });
+
+ });
+