From 6cebeace91bcbede2fe3559389c453f5fd5188ee Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Thu, 2 Mar 2017 02:28:46 -0800 Subject: [PATCH 1/3] Adds `restamp` mode to dom-repeat. --- lib/elements/dom-repeat.html | 196 +++++++++++++++++++++++------------ test/runner.html | 1 + test/unit/dom-repeat.html | 12 ++- 3 files changed, 138 insertions(+), 71 deletions(-) diff --git a/lib/elements/dom-repeat.html b/lib/elements/dom-repeat.html index 5efa30a43d..694a29beb9 100644 --- a/lib/elements/dom-repeat.html +++ b/lib/elements/dom-repeat.html @@ -262,6 +262,32 @@ _targetFrameTime: { type: Number, computed: '__computeFrameTime(targetFramerate)' + }, + + /** + * When `restamp` is true, template instances are never reused with + * different items. Instances mapping to current items are moved + * to their new location, new instances are created for any new + * items, and instances for removed items are discarded. This mode is + * generally more expensive than `restamp: false` as it results in + * more instances to be created and discarded during updates and + * disconnection/reconnection churn. By default, object identity is + * used for mapping instances to items. Set `restampKey` to provide + * key based identity. + */ + restamp: { + type: Boolean + }, + + /** + * When `restamp: true` is used, `restampKey` can be used to provide + * a path into the item object that serves as a unique key for the + * item, for purposes of mapping instances to items. Instances for + * items with the same key will be reused, while all others will be + * either restamped or discarded. + */ + restampKey: { + type: String } } @@ -276,7 +302,6 @@ super(); this.__instances = []; this.__limit = Infinity; - this.__pool = []; this.__renderDebouncer = null; this.__itemsIdxToInstIdx = {}; this.__chunkCount = null; @@ -291,8 +316,9 @@ disconnectedCallback() { super.disconnectedCallback(); this.__isDetached = true; - for (let i=0; i + this.__filterFn(items[i], idx, items)); + } + // Apply user sort + if (this.__sortFn) { + inst2items.sort((a, b) => this.__sortFn(items[a], items[b])); + } + // items->inst map kept for item path forwarding + const items2inst = this.__itemsIdxToInstIdx = {}; + const instances = this.__instances; + const limit = Math.max(Math.min(inst2items.length, this.__limit), 0); + // Generate instances and assign items + if (this.restamp) { + this.__renderRestamp(items, instances, limit, inst2items, items2inst); + } else { + this.__renderRefresh(items, instances, limit, inst2items, items2inst); + } + // Remove any extra instances from previous state + while (this.__instances.length > limit) { + this.__detachAndRemoveInstanceAt(limit); + } // Set rendered item count this._setRenderedItemCount(this.__instances.length); // Notify users @@ -505,65 +552,90 @@ this.__tryRenderChunk(); } - __applyFullRefresh() { - const items = this.items || []; - let isntIdxToItemsIdx = new Array(items.length); - for (let i=0; i - this.__filterFn(items[i], idx, array)); - } - // Apply user sort - if (this.__sortFn) { - isntIdxToItemsIdx.sort((a, b) => this.__sortFn(items[a], items[b])); + __renderRestamp(items, instances, limit, inst2items, items2inst) { + let keyPath = this.restampKey; + const instCache = new Map(); + nextItem: for (let i=0; iinst map kept for item path forwarding - const itemsIdxToInstIdx = this.__itemsIdxToInstIdx = {}; - let instIdx = 0; - // Generate instances and assign items - const limit = Math.min(isntIdxToItemsIdx.length, this.__limit); - for (; instIdx cache.forEach(inst => this.__detachInstance(inst))); + } + + __renderRefresh(items, instances, limit, inst2items, items2inst) { + for (let i=0; i=instIdx; i--) { - this.__detachAndRemoveInstance(i); + items2inst[itemIdx] = i; } } - __detachInstance(idx) { - let inst = this.__instances[idx]; + __detachInstance(inst) { for (let i=0; i +