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 @@ }); + + + + + + + + + + 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(); + + }); + + }); +