-
Notifications
You must be signed in to change notification settings - Fork 2k
/
dom-repeat.html
879 lines (795 loc) · 29.5 KB
/
dom-repeat.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<!--
The `dom-repeat` element is a custom `HTMLTemplateElement` type extension that
automatically stamps and binds one instance of template content to each object
in a user-provided array. `dom-repeat` accepts an `items` property, and one
instance of the template is stamped for each item into the DOM at the location
of the `dom-repeat` element. The `item` property will be set on each instance's
binding scope, thus templates should bind to sub-properties of `item`.
Example:
```html
<dom-module id="employee-list">
<template>
<div> Employee list: </div>
<template is="dom-repeat" items="{{employees}}">
<div>First name: <span>{{item.first}}</span></div>
<div>Last name: <span>{{item.last}}</span></div>
</template>
</template>
<script>
Polymer({
is: 'employee-list',
ready: function() {
this.employees = [
{first: 'Bob', last: 'Smith'},
{first: 'Sally', last: 'Johnson'},
...
];
}
});
</script>
</dom-module>
```
Notifications for changes to items sub-properties will be forwarded to template
instances, which will update via the normal structured data notification system.
Mutations to the `items` array itself should be made using the Array
mutation API's on `Polymer.Base` (`push`, `pop`, `splice`, `shift`,
`unshift`), and template instances will be kept in sync with the data in the
array.
Events caught by event handlers within the `dom-repeat` template will be
decorated with a `model` property, which represents the binding scope for
each template instance. The model is an instance of Polymer.Base, and should
be used to manipulate data on the instance, for example
`event.model.set('item.checked', true);`.
Alternatively, the model for a template instance for an element stamped by
a `dom-repeat` can be obtained using the `modelForElement` API on the
`dom-repeat` that stamped it, for example
`this.$.domRepeat.modelForElement(event.target).set('item.checked', true);`.
This may be useful for manipulating instance data of event targets obtained
by event handlers on parents of the `dom-repeat` (event delegation).
To filter or sort the _displayed_ items in your list, specify a `filter` or
`sort` property on the `dom-repeat` (or both):
* `filter`. Specifies a filter callback function, that takes a single argument
(the item) and returns true to display the item, false to omit it.
(Note that this is **similar** to the standard
`Array` [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) API, but the callback only takes a single argument.)
* `sort`. Specifies a comparison function following the standard `Array`
[`sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) API.
In both cases, the value can be either a function object, or a string identifying a
function defined on the host element.
By default, the `filter` and `sort` functions only run when the array itself
is mutated (for example, by adding or removing items).
To re-run the `filter` or `sort` functions when certain sub-fields
of `items` change, set the `observe` property to a space-separated list of
`item` sub-fields that should cause the list to be re-filtered or re-sorted. If
the filter or sort function depends on properties not contained in `items`,
the user should observe changes to those properties and call `render` to update
the view based on the dependency change.
For example, for an `dom-repeat` with a filter of the following:
```js
isEngineer: function(item) {
return item.type == 'engineer' || item.manager.type == 'engineer';
}
```
Then the `observe` property should be configured as follows:
```html
<template is="dom-repeat" items="{{employees}}"
filter="isEngineer" observe="type manager.type">
```
-->
<link rel="import" href="templatizer.html">
<link rel="import" href="../collection.html">
<script>
Polymer({
is: 'dom-repeat',
extends: 'template',
_template: null,
/**
* Fired whenever DOM is added or removed by this template (by
* default, rendering occurs lazily). To force immediate rendering, call
* `render`.
*
* @event dom-change
*/
properties: {
/**
* An array containing items determining how many instances of the template
* to stamp and that that each template instance should bind to.
*/
items: {
type: Array
},
/**
* The name of the variable to add to the binding scope for the array
* element associated with a given template instance.
*/
as: {
type: String,
value: 'item'
},
/**
* The name of the variable to add to the binding scope with the index
* for the inst. If `sort` is provided, the index will reflect the
* sorted order (rather than the original array order).
*/
indexAs: {
type: String,
value: 'index'
},
/**
* A function that should determine the sort order of the items. This
* property should either be provided as a string, indicating a method
* name on the element's host, or else be an actual function. The
* function should match the sort function passed to `Array.sort`.
* Using a sort function has no effect on the underlying `items` array.
*/
sort: {
type: Function,
observer: '_sortChanged'
},
/**
* `filter`. Specifies a filter callback function, that takes a single
* argument (the item) and returns true to display the item, false to omit
* it. Using a filter callback has no effect on the underlying `items`
* array.
* (Note that this is **similar** to the standard `Array`
* [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) API, but the callback only takes a single argument.)
*/
filter: {
type: Function,
observer: '_filterChanged'
},
/**
* When using a `filter` or `sort` function, the `observe` property
* should be set to a space-separated list of the names of item
* sub-fields that should trigger a re-sort or re-filter when changed.
* These should generally be fields of `item` that the sort or filter
* function depends on.
*/
observe: {
type: String,
observer: '_observeChanged'
},
/**
* When using a `filter` or `sort` function, the `delay` property
* determines a debounce time after a change to observed item
* properties that must pass before the filter or sort is re-run.
* This is useful in rate-limiting shuffing of the view when
* item changes may be frequent.
*/
delay: Number,
/**
* Count of currently rendered items after `filter` (if any) has been applied.
* If "chunking mode" is enabled, `renderedItemCount` is updated each time a
* set of template instances is rendered.
*
*/
renderedItemCount: {
type: Number,
notify: !Polymer.Settings.suppressTemplateNotifications,
readOnly: true
},
/**
* Defines an initial count of template instances to render after setting
* the `items` array, before the next paint, and puts the `dom-repeat`
* into "chunking mode". The remaining items will be created and rendered
* incrementally at each animation frame therof until all instances have
* been rendered.
*/
initialCount: {
type: Number,
observer: '_initializeChunking'
},
/**
* When `initialCount` is used, this property defines a frame rate to
* target by throttling the number of instances rendered each frame to
* not exceed the budget for the target frame rate. Setting this to a
* higher number will allow lower latency and higher throughput for
* things like event handlers, but will result in a longer time for the
* remaining items to complete rendering.
*/
targetFramerate: {
type: Number,
value: 20
},
/**
* When the global `Polymer.Settings.suppressDomChange` setting is used,
* setting `notifyDomChange: true` will enable firing `dom-change` events
* on this element.
*/
notifyDomChange: {
type: Boolean
},
_targetFrameTime: {
type: Number,
computed: '_computeFrameTime(targetFramerate)'
}
},
behaviors: [
Polymer.Templatizer
],
observers: [
'_itemsChanged(items.*)'
],
created: function() {
this._instances = [];
this._pool = [];
this._limit = Infinity;
var self = this;
this._boundRenderChunk = function() {
self._renderChunk();
};
},
detached: function() {
this.__isDetached = true;
for (var i=0; i<this._instances.length; i++) {
this._detachInstance(i);
}
},
attached: function() {
// only perform attachment if the element was previously detached.
if (this.__isDetached) {
this.__isDetached = false;
var refNode;
var parentNode = Polymer.dom(this).parentNode;
// Affordance for 2.x hybrid mode
if (parentNode.localName == this.is) {
refNode = parentNode;
parentNode = Polymer.dom(parentNode).parentNode;
} else {
refNode = this;
}
var parent = Polymer.dom(parentNode);
for (var i=0; i<this._instances.length; i++) {
this._attachInstance(i, parent, refNode);
}
}
},
ready: function() {
// Template instance props that should be excluded from forwarding
this._instanceProps = {
__key__: true
};
this._instanceProps[this.as] = true;
this._instanceProps[this.indexAs] = true;
// Templatizing (generating the instance constructor) needs to wait
// until ready, since won't have its template content handed back to
// it until then
if (!this.ctor) {
this.templatize(this);
}
},
_sortChanged: function(sort) {
var dataHost = this._getRootDataHost();
this._sortFn = sort && (typeof sort == 'function' ? sort :
function() { return dataHost[sort].apply(dataHost, arguments); });
this._needFullRefresh = true;
if (this.items) {
this._debounceTemplate(this._render);
}
},
_filterChanged: function(filter) {
var dataHost = this._getRootDataHost();
this._filterFn = filter && (typeof filter == 'function' ? filter :
function() { return dataHost[filter].apply(dataHost, arguments); });
this._needFullRefresh = true;
if (this.items) {
this._debounceTemplate(this._render);
}
},
_computeFrameTime: function(rate) {
return Math.ceil(1000/rate);
},
_initializeChunking: function() {
if (this.initialCount) {
this._limit = this.initialCount;
this._chunkCount = this.initialCount;
this._lastChunkTime = performance.now();
}
},
_tryRenderChunk: function() {
// Debounced so that multiple calls through `_render` between animation
// frames only queue one new rAF (e.g. array mutation & chunked render)
if (this.items && this._limit < this.items.length) {
this.debounce('renderChunk', this._requestRenderChunk);
}
},
_requestRenderChunk: function() {
requestAnimationFrame(this._boundRenderChunk);
},
_renderChunk: function() {
// Simple auto chunkSize throttling algorithm based on feedback loop:
// measure actual time between frames and scale chunk count by ratio
// of target/actual frame time
var currChunkTime = performance.now();
var ratio = this._targetFrameTime / (currChunkTime - this._lastChunkTime);
this._chunkCount = Math.round(this._chunkCount * ratio) || 1;
this._limit += this._chunkCount;
this._lastChunkTime = currChunkTime;
this._debounceTemplate(this._render);
},
_observeChanged: function() {
this._observePaths = this.observe &&
this.observe.replace('.*', '.').split(' ');
},
_itemsChanged: function(change) {
if (change.path == 'items') {
if (Array.isArray(this.items)) {
this.collection = Polymer.Collection.get(this.items);
} else if (!this.items) {
this.collection = null;
} else {
this._error(this._logf('dom-repeat', 'expected array for `items`,' +
' found', this.items));
}
this._keySplices = [];
this._indexSplices = [];
this._needFullRefresh = true;
this._initializeChunking();
this._debounceTemplate(this._render);
} else if (change.path == 'items.splices') {
this._keySplices = this._keySplices.concat(change.value.keySplices);
this._indexSplices = this._indexSplices.concat(change.value.indexSplices);
this._debounceTemplate(this._render);
} else { // items.*
// slice off 'items.' ('items.'.length == 6)
var subpath = change.path.slice(6);
this._forwardItemPath(subpath, change.value);
this._checkObservedPaths(subpath);
}
},
_checkObservedPaths: function(path) {
if (this._observePaths) {
path = path.substring(path.indexOf('.') + 1);
var paths = this._observePaths;
for (var i=0; i<paths.length; i++) {
if (path.indexOf(paths[i]) === 0) {
// TODO(kschaaf): interim solution: ideally this is just an incremental
// insertion sort of the changed item
this._needFullRefresh = true;
if (this.delay) {
this.debounce('render', this._render, this.delay);
} else {
this._debounceTemplate(this._render);
}
return;
}
}
}
},
/**
* Forces the element to render its content. Normally rendering is
* asynchronous to a provoking change. This is done for efficiency so
* that multiple changes trigger only a single render. The render method
* should be called if, for example, template rendering is required to
* validate application state.
*/
render: function() {
// Queue this repeater, then flush all in order
this._needFullRefresh = true;
this._debounceTemplate(this._render);
this._flushTemplates();
},
_render: function() {
if (!this.ctor) {
// Fail nicely here against errors thrown during templatizing
return;
}
// Choose rendering path: full vs. incremental using splices
if (this._needFullRefresh) {
// Full refresh when items, sort, or filter change, or when render() called
this._applyFullRefresh();
this._needFullRefresh = false;
} else if (this._keySplices.length) {
// Incremental refresh when splices were queued
if (this._sortFn) {
this._applySplicesUserSort(this._keySplices);
} else {
if (this._filterFn) {
// TODK(kschaaf): Filtering using array sort takes slow path
this._applyFullRefresh();
} else {
this._applySplicesArrayOrder(this._indexSplices);
}
}
} else {
// Otherwise only limit changed; no change to instances, just need to
// upgrade more placeholders to instances
}
this._keySplices = [];
this._indexSplices = [];
// Update final _keyToInstIdx and instance indices, and
// upgrade/downgrade placeholders
var keyToIdx = this._keyToInstIdx = {};
for (var i=this._instances.length-1; i>=0; i--) {
var inst = this._instances[i];
if (inst.isPlaceholder && i<this._limit) {
inst = this._insertInstance(i, inst.__key__);
} else if (!inst.isPlaceholder && i>=this._limit) {
inst = this._downgradeInstance(i, inst.__key__);
}
keyToIdx[inst.__key__] = i;
if (!inst.isPlaceholder) {
inst.__setProperty(this.indexAs, i, true);
}
}
// Reset the 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;
// Set rendered item count
this._setRenderedItemCount(this._instances.length);
// Notify users
if (!Polymer.Settings.suppressTemplateNotifications || this.notifyDomChange) {
this.fire('dom-change');
}
// Check to see if we need to render more items
this._tryRenderChunk();
},
// Render method 1: full refesh
// ----
// Full list of keys is pulled from the collection, then sorted, filtered,
// and iterated to create (or reuse) existing instances
_applyFullRefresh: function() {
var c = this.collection;
// Start with unordered keys for user sort,
// or get them in array order for array order
var keys;
if (this._sortFn) {
keys = c ? c.getKeys() : [];
} else {
keys = [];
var items = this.items;
if (items) {
for (var i=0; i<items.length; i++) {
keys.push(c.getKey(items[i]));
}
}
}
// capture reference for use in filter/sort fn's
var self = this;
// Apply user filter to keys
if (this._filterFn) {
keys = keys.filter(function(a) {
return self._filterFn(c.getItem(a));
});
}
// Apply user sort to keys
if (this._sortFn) {
keys.sort(function(a, b) {
return self._sortFn(c.getItem(a), c.getItem(b));
});
}
// Generate instances and assign items and keys
for (i=0; i<keys.length; i++) {
var key = keys[i];
var inst = this._instances[i];
if (inst) {
inst.__key__ = key;
if (!inst.isPlaceholder && i < this._limit) {
inst.__setProperty(this.as, c.getItem(key), true);
}
} else if (i < this._limit) {
this._insertInstance(i, key);
} else {
this._insertPlaceholder(i, key);
}
}
// Remove any extra instances from previous state
for (var j=this._instances.length-1; j>=i; j--) {
this._detachAndRemoveInstance(j);
}
},
_numericSort: function(a, b) {
return a - b;
},
// Render method 2: incremental update using splices with user sort applied
// ----
// Removed/added keys are deduped, all removed rows are detached and pooled
// first, and added rows are insertion-sorted into place using user sort
_applySplicesUserSort: function(splices) {
var c = this.collection;
var keyMap = {};
var key;
// Dedupe added and removed keys to a final added/removed map
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (var j=0; j<s.removed.length; j++) {
key = s.removed[j];
keyMap[key] = keyMap[key] ? null : -1;
}
for (j=0; j<s.added.length; j++) {
key = s.added[j];
keyMap[key] = keyMap[key] ? null : 1;
}
}
// Convert added/removed key map to added/removed arrays
var removedIdxs = [];
var addedKeys = [];
for (key in keyMap) {
if (keyMap[key] === -1) {
removedIdxs.push(this._keyToInstIdx[key]);
}
if (keyMap[key] === 1) {
addedKeys.push(key);
}
}
// Remove & pool removed instances
if (removedIdxs.length) {
// Sort removed instances idx's then remove backwards,
// so we don't invalidate instance index
// use numeric sort, default .sort is alphabetic
removedIdxs.sort(this._numericSort);
for (i=removedIdxs.length-1; i>=0 ; i--) {
var idx = removedIdxs[i];
// Removed idx may be undefined if item was previously filtered out
if (idx !== undefined) {
this._detachAndRemoveInstance(idx);
}
}
}
// capture reference for use in filter/sort fn's
var self = this;
// Add instances for added keys
if (addedKeys.length) {
// Filter added keys
if (this._filterFn) {
addedKeys = addedKeys.filter(function(a) {
return self._filterFn(c.getItem(a));
});
}
// Sort added keys
addedKeys.sort(function(a, b) {
return self._sortFn(c.getItem(a), c.getItem(b));
});
// Insertion-sort new instances into place (from pool or newly created)
var start = 0;
for (i=0; i<addedKeys.length; i++) {
start = this._insertRowUserSort(start, addedKeys[i]);
}
}
},
_insertRowUserSort: function(start, key) {
var c = this.collection;
var item = c.getItem(key);
var end = this._instances.length - 1;
var idx = -1;
// Binary search for insertion point
while (start <= end) {
var mid = (start + end) >> 1;
var midKey = this._instances[mid].__key__;
var cmp = this._sortFn(c.getItem(midKey), item);
if (cmp < 0) {
start = mid + 1;
} else if (cmp > 0) {
end = mid - 1;
} else {
idx = mid;
break;
}
}
if (idx < 0) {
idx = end + 1;
}
// Insert instance at insertion point
this._insertPlaceholder(idx, key);
return idx;
},
// Render method 3: incremental update using splices with array order
// ----
// Splices are processed in order; removed rows are pooled, and added
// rows are as placeholders, and placeholders are updated to
// actual rows at the end to take full advantage of removed rows
_applySplicesArrayOrder: function(splices) {
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
// Detach & pool removed instances
for (var j=0; j<s.removed.length; j++) {
this._detachAndRemoveInstance(s.index);
}
for (j=0; j<s.addedKeys.length; j++) {
this._insertPlaceholder(s.index+j, s.addedKeys[j]);
}
}
},
_detachInstance: function(idx) {
var inst = this._instances[idx];
if (!inst.isPlaceholder) {
for (var i=0; i<inst._children.length; i++) {
var el = inst._children[i];
Polymer.dom(inst.root).appendChild(el);
}
return inst;
}
},
_attachInstance: function(idx, parent, refNode) {
var inst = this._instances[idx];
if (!inst.isPlaceholder) {
parent.insertBefore(inst.root, refNode);
}
},
_detachAndRemoveInstance: function(idx) {
var inst = this._detachInstance(idx);
if (inst) {
this._pool.push(inst);
}
this._instances.splice(idx, 1);
},
_insertPlaceholder: function(idx, key) {
this._instances.splice(idx, 0, {
isPlaceholder: true,
__key__: key
});
},
_stampInstance: function(idx, key) {
var model = {
__key__: key
};
model[this.as] = this.collection.getItem(key);
model[this.indexAs] = idx;
return this.stamp(model);
},
_insertInstance: function(idx, key) {
var inst = this._pool.pop();
if (inst) {
// 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 {
inst = this._stampInstance(idx, key);
}
var beforeRow = this._instances[idx + 1];
var beforeNode = beforeRow && !beforeRow.isPlaceholder ? beforeRow._children[0] : this;
var parentNode = Polymer.dom(this).parentNode;
// Affordance for 2.x hybrid mode
if (parentNode.localName == this.is) {
if (beforeNode == this) {
beforeNode = parentNode;
}
parentNode = Polymer.dom(parentNode).parentNode;
}
Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
this._instances[idx] = inst;
return inst;
},
_downgradeInstance: function(idx, key) {
var inst = this._detachInstance(idx);
if (inst) {
this._pool.push(inst);
}
inst = {
isPlaceholder: true,
__key__: key
};
this._instances[idx] = inst;
return inst;
},
// Implements extension point from Templatizer mixin
_showHideChildren: function(hidden) {
for (var i=0; i<this._instances.length; i++) {
if (!this._instances[i].isPlaceholder)
this._instances[i]._showHideChildren(hidden);
}
},
// Called as a side effect of a template item change, responsible
// for notifying items.<key-for-inst> change up to host
_forwardInstanceProp: function(inst, prop, value) {
if (prop == this.as) {
var idx;
if (this._sortFn || this._filterFn) {
// Known slow lookup: when sorted/filtered, there is no way to
// efficiently memoize the array index and keep it in sync with array
// mutations, so we need to look the item up in the array
// This can happen e.g. when array of strings is repeated into inputs
idx = this.items.indexOf(this.collection.getItem(inst.__key__));
} else {
// When there is no sort/filter, the view index is the array index
idx = inst[this.indexAs];
}
this.set('items.' + idx, value);
}
},
// Implements extension point from Templatizer
// Called as a side effect of a template instance path change, responsible
// for notifying items.<key-for-inst>.<path> change up to host
_forwardInstancePath: function(inst, path, value) {
if (path.indexOf(this.as + '.') === 0) {
this._notifyPath('items.' + inst.__key__ + '.' +
path.slice(this.as.length + 1), value);
}
},
// Implements extension point from Templatizer mixin
// Called as side-effect of a host property change, responsible for
// notifying parent path change on each inst
_forwardParentProp: function(prop, value) {
var i$ = this._instances;
for (var i=0, inst; (i<i$.length) && (inst=i$[i]); i++) {
if (!inst.isPlaceholder) {
inst.__setProperty(prop, value, true);
}
}
},
// Implements extension point from Templatizer
// Called as side-effect of a host path change, responsible for
// notifying parent path change on each inst
_forwardParentPath: function(path, value) {
var i$ = this._instances;
for (var i=0, inst; (i<i$.length) && (inst=i$[i]); i++) {
if (!inst.isPlaceholder) {
inst._notifyPath(path, value, true);
}
}
},
// Called as a side effect of a host items.<key>.<path> path change,
// responsible for notifying item.<path> changes to inst for key
_forwardItemPath: function(path, value) {
if (this._keyToInstIdx) {
var dot = path.indexOf('.');
var key = path.substring(0, dot < 0 ? path.length : dot);
var idx = this._keyToInstIdx[key];
var inst = this._instances[idx];
if (inst && !inst.isPlaceholder) {
if (dot >= 0) {
path = this.as + '.' + path.substring(dot+1);
inst._notifyPath(path, value, true);
} else {
inst.__setProperty(this.as, value, true);
}
}
}
},
/**
* Returns the item associated with a given element stamped by
* this `dom-repeat`.
*
* Note, to modify sub-properties of the item,
* `modelForElement(el).set('item.<sub-prop>', value)`
* should be used.
*
* @method itemForElement
* @param {HTMLElement} el Element for which to return the item.
* @return {any} Item associated with the element.
*/
itemForElement: function(el) {
var instance = this.modelForElement(el);
return instance && instance[this.as];
},
/**
* Returns the `Polymer.Collection` key associated with a given
* element stamped by this `dom-repeat`.
*
* @method keyForElement
* @param {HTMLElement} el Element for which to return the key.
* @return {any} Key associated with the element.
*/
keyForElement: function(el) {
var instance = this.modelForElement(el);
return instance && instance.__key__;
},
/**
* Returns the inst index for a given element stamped by this `dom-repeat`.
* If `sort` is provided, the index will reflect the sorted order (rather
* than the original array order).
*
* @method indexForElement
* @param {HTMLElement} el Element for which to return the index.
* @return {any} Row index associated with the element (note this may
* not correspond to the array index if a user `sort` is applied).
*/
indexForElement: function(el) {
var instance = this.modelForElement(el);
return instance && instance[this.indexAs];
}
});
</script>