Skip to content

Commit

Permalink
dom-repeat: Instance to item binding demo
Browse files Browse the repository at this point in the history
If important instance state is not handled by property binding,
using dom-repeat currently leads to unexpected behaviour when the
items list is re-arranged. A good example of this is input focus
which will not behave as the user expects. By optionally binding
instances to items, the developer can ensure that unmanaged
instance state will follow the items as the user would expect.

This commit provides a domRepeat.refProp property that can be
used to specify a property of the model.item object that will
be used as a key/reference. E.g:

```
<dom-repeat ref-prop='key'>
  <template>
    <span>My key is:</span><span>{{item.key}}</span>
  </template>
</dom-repeat>
```

When the domRepeat.items property changes (or the sort and filter
functions change), instances that are bound to a key will be moved
in the dom to match the new position of the key. Note that
a bound instance will be removed as soon as a render is performed
and the key is not present. This might change when the pooling
policy is refined.

All existing tests are passing on my local machine.
No new tests have been written yet. I will wait for feedback before
doing this.
  • Loading branch information
mattpalermo committed Apr 21, 2017
1 parent 053ac15 commit 1d07acb
Showing 1 changed file with 28 additions and 16 deletions.
44 changes: 28 additions & 16 deletions lib/elements/dom-repeat.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@
type: Array
},

/**
* The property of each item that will be used as an instance binding
* reference. If this property is set in an item, a single instance will
* be used to represent the item even in between updates.
*/
refProp: {
type: String
},

/**
* The name of the variable to add to the binding scope for the array
* element associated with a given template instance.
Expand Down Expand Up @@ -275,6 +284,7 @@
this.__instances = [];
this.__limit = Infinity;
this.__pool = [];
this.__boundpool = {};
this.__renderDebouncer = null;
this.__itemsIdxToInstIdx = {};
this.__chunkCount = null;
Expand Down Expand Up @@ -492,6 +502,7 @@
// reuse pooled instances across turns, however we still need to decide
// semantics regarding how long to hold, how many to hold, etc.
this.__pool.length = 0;
this.__boundpool = {};
// Set rendered item count
this._setRenderedItemCount(this.__instances.length);
// Notify users
Expand Down Expand Up @@ -520,26 +531,20 @@
}
// items->inst map kept for item path forwarding
const itemsIdxToInstIdx = this.__itemsIdxToInstIdx = {};
let instIdx = 0;
// Detach and remove all instances.
// This makes moving around the reference bound instances easier to
// reason about.
for (let i = this.__instances.length-1; i >= 0; i--) {
this.__detachAndRemoveInstance(i);
}
// Generate instances and assign items
const limit = Math.min(isntIdxToItemsIdx.length, this.__limit);
let instIdx = 0;
for (; instIdx<limit; instIdx++) {
let inst = this.__instances[instIdx];
let itemIdx = isntIdxToItemsIdx[instIdx];
let item = items[itemIdx];
itemsIdxToInstIdx[itemIdx] = instIdx;
if (inst && instIdx < this.__limit) {
inst._setPendingProperty(this.as, item);
inst._setPendingProperty(this.indexAs, instIdx);
inst._setPendingProperty(this.itemsIndexAs, itemIdx);
inst._flushProperties();
} else {
this.__insertInstance(item, instIdx, itemIdx);
}
}
// Remove any extra instances from previous state
for (let i=this.__instances.length-1; i>=instIdx; i--) {
this.__detachAndRemoveInstance(i);
this.__insertInstance(item, instIdx, itemIdx);
}
}

Expand All @@ -560,7 +565,13 @@
__detachAndRemoveInstance(idx) {
let inst = this.__detachInstance(idx);
if (inst) {
this.__pool.push(inst);
let item = inst[this.as];
let ref = item ? item[this.refProp] : undefined;
if (ref) {
this.__boundpool[ref] = inst;
} else {
this.__pool.push(inst);
}
}
this.__instances.splice(idx, 1);
}
Expand All @@ -574,7 +585,8 @@
}

__insertInstance(item, instIdx, itemIdx) {
let inst = this.__pool.pop();
let ref = item ? item[this.refProp] : undefined;
let inst = ref ? this.__boundpool[ref] : this.__pool.pop();
if (inst) {
// TODO(kschaaf): If the pool is shared across turns, hostProps
// need to be re-set to reused instances in addition to item
Expand Down

0 comments on commit 1d07acb

Please sign in to comment.