| OLD | NEW |
| (Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2017 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="../../polymer-element.html"> |
| 12 <link rel="import" href="../utils/templatize.html"> |
| 13 <link rel="import" href="../utils/debounce.html"> |
| 14 <link rel="import" href="../utils/flush.html"> |
| 15 <link rel="import" href="../mixins/mutable-data.html"> |
| 16 |
| 17 <script> |
| 18 (function() { |
| 19 'use strict'; |
| 20 |
| 21 /** |
| 22 * @constructor |
| 23 * @implements {Polymer_OptionalMutableData} |
| 24 * @extends {Polymer.Element} |
| 25 */ |
| 26 const domRepeatBase = Polymer.OptionalMutableData(Polymer.Element); |
| 27 |
| 28 /** |
| 29 * The `<dom-repeat>` element will automatically stamp and binds one instance |
| 30 * of template content to each object in a user-provided array. |
| 31 * `dom-repeat` accepts an `items` property, and one instance of the template |
| 32 * is stamped for each item into the DOM at the location of the `dom-repeat` |
| 33 * element. The `item` property will be set on each instance's binding |
| 34 * scope, thus templates should bind to sub-properties of `item`. |
| 35 * |
| 36 * Example: |
| 37 * |
| 38 * ```html |
| 39 * <dom-module id="employee-list"> |
| 40 * |
| 41 * <template> |
| 42 * |
| 43 * <div> Employee list: </div> |
| 44 * <template is="dom-repeat" items="{{employees}}"> |
| 45 * <div>First name: <span>{{item.first}}</span></div> |
| 46 * <div>Last name: <span>{{item.last}}</span></div> |
| 47 * </template> |
| 48 * |
| 49 * </template> |
| 50 * |
| 51 * <script> |
| 52 * Polymer({ |
| 53 * is: 'employee-list', |
| 54 * ready: function() { |
| 55 * this.employees = [ |
| 56 * {first: 'Bob', last: 'Smith'}, |
| 57 * {first: 'Sally', last: 'Johnson'}, |
| 58 * ... |
| 59 * ]; |
| 60 * } |
| 61 * }); |
| 62 * < /script> |
| 63 * |
| 64 * </dom-module> |
| 65 * ``` |
| 66 * |
| 67 * Notifications for changes to items sub-properties will be forwarded to temp
late |
| 68 * instances, which will update via the normal structured data notification sy
stem. |
| 69 * |
| 70 * Mutations to the `items` array itself should me made using the Array |
| 71 * mutation API's on `Polymer.Base` (`push`, `pop`, `splice`, `shift`, |
| 72 * `unshift`), and template instances will be kept in sync with the data in th
e |
| 73 * array. |
| 74 * |
| 75 * Events caught by event handlers within the `dom-repeat` template will be |
| 76 * decorated with a `model` property, which represents the binding scope for |
| 77 * each template instance. The model is an instance of Polymer.Base, and shou
ld |
| 78 * be used to manipulate data on the instance, for example |
| 79 * `event.model.set('item.checked', true);`. |
| 80 * |
| 81 * Alternatively, the model for a template instance for an element stamped by |
| 82 * a `dom-repeat` can be obtained using the `modelForElement` API on the |
| 83 * `dom-repeat` that stamped it, for example |
| 84 * `this.$.domRepeat.modelForElement(event.target).set('item.checked', true);`
. |
| 85 * This may be useful for manipulating instance data of event targets obtained |
| 86 * by event handlers on parents of the `dom-repeat` (event delegation). |
| 87 * |
| 88 * A view-specific filter/sort may be applied to each `dom-repeat` by supplyin
g a |
| 89 * `filter` and/or `sort` property. This may be a string that names a functio
n on |
| 90 * the host, or a function may be assigned to the property directly. The func
tions |
| 91 * should implemented following the standard `Array` filter/sort API. |
| 92 * |
| 93 * In order to re-run the filter or sort functions based on changes to sub-fie
lds |
| 94 * of `items`, the `observe` property may be set as a space-separated list of |
| 95 * `item` sub-fields that should cause a re-filter/sort when modified. If |
| 96 * the filter or sort function depends on properties not contained in `items`, |
| 97 * the user should observe changes to those properties and call `render` to up
date |
| 98 * the view based on the dependency change. |
| 99 * |
| 100 * For example, for an `dom-repeat` with a filter of the following: |
| 101 * |
| 102 * ```js |
| 103 * isEngineer: function(item) { |
| 104 * return item.type == 'engineer' || item.manager.type == 'engineer'; |
| 105 * } |
| 106 * ``` |
| 107 * |
| 108 * Then the `observe` property should be configured as follows: |
| 109 * |
| 110 * ```html |
| 111 * <template is="dom-repeat" items="{{employees}}" |
| 112 * filter="isEngineer" observe="type manager.type"> |
| 113 * ``` |
| 114 * |
| 115 * @polymerElement |
| 116 * @memberof Polymer |
| 117 * @extends Polymer.Element |
| 118 * @mixes Polymer.MutableData |
| 119 * @summary Custom element for stamping instance of a template bound to |
| 120 * items in an array. |
| 121 */ |
| 122 class DomRepeat extends domRepeatBase { |
| 123 |
| 124 // Not needed to find template; can be removed once the analyzer |
| 125 // can find the tag name from customElements.define call |
| 126 static get is() { return 'dom-repeat'; } |
| 127 |
| 128 static get template() { return null; } |
| 129 |
| 130 static get properties() { |
| 131 |
| 132 /** |
| 133 * Fired whenever DOM is added or removed by this template (by |
| 134 * default, rendering occurs lazily). To force immediate rendering, call |
| 135 * `render`. |
| 136 * |
| 137 * @event dom-change |
| 138 */ |
| 139 return { |
| 140 |
| 141 /** |
| 142 * An array containing items determining how many instances of the templ
ate |
| 143 * to stamp and that that each template instance should bind to. |
| 144 */ |
| 145 items: { |
| 146 type: Array |
| 147 }, |
| 148 |
| 149 /** |
| 150 * The name of the variable to add to the binding scope for the array |
| 151 * element associated with a given template instance. |
| 152 */ |
| 153 as: { |
| 154 type: String, |
| 155 value: 'item' |
| 156 }, |
| 157 |
| 158 /** |
| 159 * The name of the variable to add to the binding scope with the index |
| 160 * of the instance in the sorted and filtered list of rendered items. |
| 161 * Note, for the index in the `this.items` array, use the value of the |
| 162 * `itemsIndexAs` property. |
| 163 */ |
| 164 indexAs: { |
| 165 type: String, |
| 166 value: 'index' |
| 167 }, |
| 168 |
| 169 /** |
| 170 * The name of the variable to add to the binding scope with the index |
| 171 * of the instance in the `this.items` array. Note, for the index of |
| 172 * this instance in the sorted and filtered list of rendered items, |
| 173 * use the value of the `indexAs` property. |
| 174 */ |
| 175 itemsIndexAs: { |
| 176 type: String, |
| 177 value: 'itemsIndex' |
| 178 }, |
| 179 |
| 180 /** |
| 181 * A function that should determine the sort order of the items. This |
| 182 * property should either be provided as a string, indicating a method |
| 183 * name on the element's host, or else be an actual function. The |
| 184 * function should match the sort function passed to `Array.sort`. |
| 185 * Using a sort function has no effect on the underlying `items` array. |
| 186 */ |
| 187 sort: { |
| 188 type: Function, |
| 189 observer: '__sortChanged' |
| 190 }, |
| 191 |
| 192 /** |
| 193 * A function that can be used to filter items out of the view. This |
| 194 * property should either be provided as a string, indicating a method |
| 195 * name on the element's host, or else be an actual function. The |
| 196 * function should match the sort function passed to `Array.filter`. |
| 197 * Using a filter function has no effect on the underlying `items` array
. |
| 198 */ |
| 199 filter: { |
| 200 type: Function, |
| 201 observer: '__filterChanged' |
| 202 }, |
| 203 |
| 204 /** |
| 205 * When using a `filter` or `sort` function, the `observe` property |
| 206 * should be set to a space-separated list of the names of item |
| 207 * sub-fields that should trigger a re-sort or re-filter when changed. |
| 208 * These should generally be fields of `item` that the sort or filter |
| 209 * function depends on. |
| 210 */ |
| 211 observe: { |
| 212 type: String, |
| 213 observer: '__observeChanged' |
| 214 }, |
| 215 |
| 216 /** |
| 217 * When using a `filter` or `sort` function, the `delay` property |
| 218 * determines a debounce time after a change to observed item |
| 219 * properties that must pass before the filter or sort is re-run. |
| 220 * This is useful in rate-limiting shuffing of the view when |
| 221 * item changes may be frequent. |
| 222 */ |
| 223 delay: Number, |
| 224 |
| 225 /** |
| 226 * Count of currently rendered items after `filter` (if any) has been ap
plied. |
| 227 * If "chunking mode" is enabled, `renderedItemCount` is updated each ti
me a |
| 228 * set of template instances is rendered. |
| 229 * |
| 230 */ |
| 231 renderedItemCount: { |
| 232 type: Number, |
| 233 notify: true, |
| 234 readOnly: true |
| 235 }, |
| 236 |
| 237 /** |
| 238 * Defines an initial count of template instances to render after settin
g |
| 239 * the `items` array, before the next paint, and puts the `dom-repeat` |
| 240 * into "chunking mode". The remaining items will be created and render
ed |
| 241 * incrementally at each animation frame therof until all instances have |
| 242 * been rendered. |
| 243 */ |
| 244 initialCount: { |
| 245 type: Number, |
| 246 observer: '__initializeChunking' |
| 247 }, |
| 248 |
| 249 /** |
| 250 * When `initialCount` is used, this property defines a frame rate to |
| 251 * target by throttling the number of instances rendered each frame to |
| 252 * not exceed the budget for the target frame rate. Setting this to a |
| 253 * higher number will allow lower latency and higher throughput for |
| 254 * things like event handlers, but will result in a longer time for the |
| 255 * remaining items to complete rendering. |
| 256 */ |
| 257 targetFramerate: { |
| 258 type: Number, |
| 259 value: 20 |
| 260 }, |
| 261 |
| 262 _targetFrameTime: { |
| 263 type: Number, |
| 264 computed: '__computeFrameTime(targetFramerate)' |
| 265 } |
| 266 |
| 267 } |
| 268 |
| 269 } |
| 270 |
| 271 static get observers() { |
| 272 return [ '__itemsChanged(items.*)' ] |
| 273 } |
| 274 |
| 275 constructor() { |
| 276 super(); |
| 277 this.__instances = []; |
| 278 this.__limit = Infinity; |
| 279 this.__pool = []; |
| 280 this.__renderDebouncer = null; |
| 281 this.__itemsIdxToInstIdx = {}; |
| 282 this.__chunkCount = null; |
| 283 this.__lastChunkTime = null; |
| 284 this.__needFullRefresh = false; |
| 285 this.__sortFn = null; |
| 286 this.__filterFn = null; |
| 287 this.__observePaths = null; |
| 288 this.__ctor = null; |
| 289 } |
| 290 |
| 291 disconnectedCallback() { |
| 292 super.disconnectedCallback(); |
| 293 this.__isDetached = true; |
| 294 for (let i=0; i<this.__instances.length; i++) { |
| 295 this.__detachInstance(i); |
| 296 } |
| 297 } |
| 298 |
| 299 connectedCallback() { |
| 300 super.connectedCallback(); |
| 301 // only perform attachment if the element was previously detached. |
| 302 if (this.__isDetached) { |
| 303 this.__isDetached = false; |
| 304 let parent = this.parentNode; |
| 305 for (let i=0; i<this.__instances.length; i++) { |
| 306 this.__attachInstance(i, parent); |
| 307 } |
| 308 } |
| 309 } |
| 310 |
| 311 __ensureTemplatized() { |
| 312 // Templatizing (generating the instance constructor) needs to wait |
| 313 // until ready, since won't have its template content handed back to |
| 314 // it until then |
| 315 if (!this.__ctor) { |
| 316 let template = this.template = this.querySelector('template'); |
| 317 if (!template) { |
| 318 // // Wait until childList changes and template should be there by the
n |
| 319 let observer = new MutationObserver(() => { |
| 320 if (this.querySelector('template')) { |
| 321 observer.disconnect(); |
| 322 this.__render(); |
| 323 } else { |
| 324 throw new Error('dom-repeat requires a <template> child'); |
| 325 } |
| 326 }) |
| 327 observer.observe(this, {childList: true}); |
| 328 return false; |
| 329 } |
| 330 // Template instance props that should be excluded from forwarding |
| 331 let instanceProps = {}; |
| 332 instanceProps[this.as] = true; |
| 333 instanceProps[this.indexAs] = true; |
| 334 instanceProps[this.itemsIndexAs] = true; |
| 335 this.__ctor = Polymer.Templatize.templatize(template, this, { |
| 336 mutableData: this.mutableData, |
| 337 parentModel: true, |
| 338 instanceProps: instanceProps, |
| 339 forwardHostProp: function(prop, value) { |
| 340 let i$ = this.__instances; |
| 341 for (let i=0, inst; (i<i$.length) && (inst=i$[i]); i++) { |
| 342 inst.forwardHostProp(prop, value); |
| 343 } |
| 344 }, |
| 345 notifyInstanceProp: function(inst, prop, value) { |
| 346 if (Polymer.Path.matches(this.as, prop)) { |
| 347 let idx = inst[this.itemsIndexAs]; |
| 348 if (prop == this.as) { |
| 349 this.items[idx] = value; |
| 350 } |
| 351 let path = Polymer.Path.translate(this.as, 'items.' + idx, prop); |
| 352 this.notifyPath(path, value); |
| 353 } |
| 354 } |
| 355 }); |
| 356 } |
| 357 return true; |
| 358 } |
| 359 |
| 360 __getMethodHost() { |
| 361 // Technically this should be the owner of the outermost template. |
| 362 // In shadow dom, this is always getRootNode().host, but we can |
| 363 // approximate this via cooperation with our dataHost always setting |
| 364 // `_methodHost` as long as there were bindings (or id's) on this |
| 365 // instance causing it to get a dataHost. |
| 366 return this.__dataHost._methodHost || this.__dataHost; |
| 367 } |
| 368 |
| 369 __sortChanged(sort) { |
| 370 let methodHost = this.__getMethodHost(); |
| 371 this.__sortFn = sort && (typeof sort == 'function' ? sort : |
| 372 function() { return methodHost[sort].apply(methodHost, arguments); }); |
| 373 this.__needFullRefresh = true; |
| 374 if (this.items) { |
| 375 this.__debounceRender(this.__render); |
| 376 } |
| 377 } |
| 378 |
| 379 __filterChanged(filter) { |
| 380 let methodHost = this.__getMethodHost(); |
| 381 this.__filterFn = filter && (typeof filter == 'function' ? filter : |
| 382 function() { return methodHost[filter].apply(methodHost, arguments); }); |
| 383 this.__needFullRefresh = true; |
| 384 if (this.items) { |
| 385 this.__debounceRender(this.__render); |
| 386 } |
| 387 } |
| 388 |
| 389 __computeFrameTime(rate) { |
| 390 return Math.ceil(1000/rate); |
| 391 } |
| 392 |
| 393 __initializeChunking() { |
| 394 if (this.initialCount) { |
| 395 this.__limit = this.initialCount; |
| 396 this.__chunkCount = this.initialCount; |
| 397 this.__lastChunkTime = performance.now(); |
| 398 } |
| 399 } |
| 400 |
| 401 __tryRenderChunk() { |
| 402 // Debounced so that multiple calls through `_render` between animation |
| 403 // frames only queue one new rAF (e.g. array mutation & chunked render) |
| 404 if (this.items && this.__limit < this.items.length) { |
| 405 this.__debounceRender(this.__requestRenderChunk); |
| 406 } |
| 407 } |
| 408 |
| 409 __requestRenderChunk() { |
| 410 requestAnimationFrame(()=>this.__renderChunk()); |
| 411 } |
| 412 |
| 413 __renderChunk() { |
| 414 // Simple auto chunkSize throttling algorithm based on feedback loop: |
| 415 // measure actual time between frames and scale chunk count by ratio |
| 416 // of target/actual frame time |
| 417 let currChunkTime = performance.now(); |
| 418 let ratio = this._targetFrameTime / (currChunkTime - this.__lastChunkTime)
; |
| 419 this.__chunkCount = Math.round(this.__chunkCount * ratio) || 1; |
| 420 this.__limit += this.__chunkCount; |
| 421 this.__lastChunkTime = currChunkTime; |
| 422 this.__debounceRender(this.__render); |
| 423 } |
| 424 |
| 425 __observeChanged() { |
| 426 this.__observePaths = this.observe && |
| 427 this.observe.replace('.*', '.').split(' '); |
| 428 } |
| 429 |
| 430 __itemsChanged(change) { |
| 431 if (this.items && !Array.isArray(this.items)) { |
| 432 console.warn('dom-repeat expected array for `items`, found', this.items)
; |
| 433 } |
| 434 // If path was to an item (e.g. 'items.3' or 'items.3.foo'), forward the |
| 435 // path to that instance synchronously (retuns false for non-item paths) |
| 436 if (!this.__handleItemPath(change.path, change.value)) { |
| 437 // Otherwise, the array was reset ('items') or spliced ('items.splices')
, |
| 438 // so queue a full refresh |
| 439 this.__needFullRefresh = true; |
| 440 this.__initializeChunking(); |
| 441 this.__debounceRender(this.__render); |
| 442 } |
| 443 } |
| 444 |
| 445 __handleObservedPaths(path) { |
| 446 if (this.__observePaths) { |
| 447 path = path.substring(path.indexOf('.') + 1); |
| 448 let paths = this.__observePaths; |
| 449 for (let i=0; i<paths.length; i++) { |
| 450 if (path.indexOf(paths[i]) === 0) { |
| 451 this.__needFullRefresh = true; |
| 452 this.__debounceRender(this.__render, this.delay); |
| 453 return true; |
| 454 } |
| 455 } |
| 456 } |
| 457 } |
| 458 |
| 459 /** |
| 460 * @param {function()} fn Function to debounce. |
| 461 * @param {number=} delay Delay in ms to debounce by. |
| 462 */ |
| 463 __debounceRender(fn, delay) { |
| 464 this.__renderDebouncer = Polymer.Debouncer.debounce( |
| 465 this.__renderDebouncer |
| 466 , delay > 0 ? Polymer.Async.timeOut.after(delay) : Polymer.Async.micro
Task |
| 467 , fn.bind(this)); |
| 468 Polymer.enqueueDebouncer(this.__renderDebouncer); |
| 469 } |
| 470 |
| 471 /** |
| 472 * Forces the element to render its content. Normally rendering is |
| 473 * asynchronous to a provoking change. This is done for efficiency so |
| 474 * that multiple changes trigger only a single render. The render method |
| 475 * should be called if, for example, template rendering is required to |
| 476 * validate application state. |
| 477 */ |
| 478 render() { |
| 479 // Queue this repeater, then flush all in order |
| 480 this.__needFullRefresh = true; |
| 481 this.__debounceRender(this.__render); |
| 482 Polymer.flush(); |
| 483 } |
| 484 |
| 485 __render() { |
| 486 if (!this.__ensureTemplatized()) { |
| 487 // No template found yet |
| 488 return; |
| 489 } |
| 490 this.__applyFullRefresh(); |
| 491 // Reset the pool |
| 492 // TODO(kschaaf): Reuse pool across turns and nested templates |
| 493 // Now that objects/arrays are re-evaluated when set, we can safely |
| 494 // reuse pooled instances across turns, however we still need to decide |
| 495 // semantics regarding how long to hold, how many to hold, etc. |
| 496 this.__pool.length = 0; |
| 497 // Set rendered item count |
| 498 this._setRenderedItemCount(this.__instances.length); |
| 499 // Notify users |
| 500 this.dispatchEvent(new CustomEvent('dom-change', { |
| 501 bubbles: true, |
| 502 composed: true |
| 503 })); |
| 504 // Check to see if we need to render more items |
| 505 this.__tryRenderChunk(); |
| 506 } |
| 507 |
| 508 __applyFullRefresh() { |
| 509 const items = this.items || []; |
| 510 let isntIdxToItemsIdx = new Array(items.length); |
| 511 for (let i=0; i<items.length; i++) { |
| 512 isntIdxToItemsIdx[i] = i; |
| 513 } |
| 514 // Apply user filter |
| 515 if (this.__filterFn) { |
| 516 isntIdxToItemsIdx = isntIdxToItemsIdx.filter((i, idx, array) => |
| 517 this.__filterFn(items[i], idx, array)); |
| 518 } |
| 519 // Apply user sort |
| 520 if (this.__sortFn) { |
| 521 isntIdxToItemsIdx.sort((a, b) => this.__sortFn(items[a], items[b])); |
| 522 } |
| 523 // items->inst map kept for item path forwarding |
| 524 const itemsIdxToInstIdx = this.__itemsIdxToInstIdx = {}; |
| 525 let instIdx = 0; |
| 526 // Generate instances and assign items |
| 527 const limit = Math.min(isntIdxToItemsIdx.length, this.__limit); |
| 528 for (; instIdx<limit; instIdx++) { |
| 529 let inst = this.__instances[instIdx]; |
| 530 let itemIdx = isntIdxToItemsIdx[instIdx]; |
| 531 let item = items[itemIdx]; |
| 532 itemsIdxToInstIdx[itemIdx] = instIdx; |
| 533 if (inst && instIdx < this.__limit) { |
| 534 inst._setPendingProperty(this.as, item); |
| 535 inst._setPendingProperty(this.indexAs, instIdx); |
| 536 inst._setPendingProperty(this.itemsIndexAs, itemIdx); |
| 537 inst._flushProperties(); |
| 538 } else { |
| 539 this.__insertInstance(item, instIdx, itemIdx); |
| 540 } |
| 541 } |
| 542 // Remove any extra instances from previous state |
| 543 for (let i=this.__instances.length-1; i>=instIdx; i--) { |
| 544 this.__detachAndRemoveInstance(i); |
| 545 } |
| 546 } |
| 547 |
| 548 __detachInstance(idx) { |
| 549 let inst = this.__instances[idx]; |
| 550 for (let i=0; i<inst.children.length; i++) { |
| 551 let el = inst.children[i]; |
| 552 inst.root.appendChild(el); |
| 553 } |
| 554 return inst; |
| 555 } |
| 556 |
| 557 __attachInstance(idx, parent) { |
| 558 let inst = this.__instances[idx]; |
| 559 parent.insertBefore(inst.root, this); |
| 560 } |
| 561 |
| 562 __detachAndRemoveInstance(idx) { |
| 563 let inst = this.__detachInstance(idx); |
| 564 if (inst) { |
| 565 this.__pool.push(inst); |
| 566 } |
| 567 this.__instances.splice(idx, 1); |
| 568 } |
| 569 |
| 570 __stampInstance(item, instIdx, itemIdx) { |
| 571 let model = {}; |
| 572 model[this.as] = item; |
| 573 model[this.indexAs] = instIdx; |
| 574 model[this.itemsIndexAs] = itemIdx; |
| 575 return new this.__ctor(model); |
| 576 } |
| 577 |
| 578 __insertInstance(item, instIdx, itemIdx) { |
| 579 let inst = this.__pool.pop(); |
| 580 if (inst) { |
| 581 // TODO(kschaaf): If the pool is shared across turns, hostProps |
| 582 // need to be re-set to reused instances in addition to item |
| 583 inst._setPendingProperty(this.as, item); |
| 584 inst._setPendingProperty(this.indexAs, instIdx); |
| 585 inst._setPendingProperty(this.itemsIndexAs, itemIdx); |
| 586 inst._flushProperties(); |
| 587 } else { |
| 588 inst = this.__stampInstance(item, instIdx, itemIdx); |
| 589 } |
| 590 let beforeRow = this.__instances[instIdx + 1]; |
| 591 let beforeNode = beforeRow ? beforeRow.children[0] : this; |
| 592 this.parentNode.insertBefore(inst.root, beforeNode); |
| 593 this.__instances[instIdx] = inst; |
| 594 return inst; |
| 595 } |
| 596 |
| 597 // Implements extension point from Templatize mixin |
| 598 _showHideChildren(hidden) { |
| 599 for (let i=0; i<this.__instances.length; i++) { |
| 600 this.__instances[i]._showHideChildren(hidden); |
| 601 } |
| 602 } |
| 603 |
| 604 // Called as a side effect of a host items.<key>.<path> path change, |
| 605 // responsible for notifying item.<path> changes to inst for key |
| 606 __handleItemPath(path, value) { |
| 607 let itemsPath = path.slice(6); // 'items.'.length == 6 |
| 608 let dot = itemsPath.indexOf('.'); |
| 609 let itemsIdx = dot < 0 ? itemsPath : itemsPath.substring(0, dot); |
| 610 // If path was index into array... |
| 611 if (itemsIdx == parseInt(itemsIdx, 10)) { |
| 612 let itemSubPath = dot < 0 ? '' : itemsPath.substring(dot+1); |
| 613 // See if the item subpath should trigger a full refresh... |
| 614 if (!this.__handleObservedPaths(itemSubPath)) { |
| 615 // If not, forward to the instance for that index |
| 616 let instIdx = this.__itemsIdxToInstIdx[itemsIdx]; |
| 617 let inst = this.__instances[instIdx]; |
| 618 if (inst) { |
| 619 let itemPath = this.as + (itemSubPath ? '.' + itemSubPath : ''); |
| 620 // This is effectively `notifyPath`, but avoids some of the overhead |
| 621 // of the public API |
| 622 inst._setPendingPropertyOrPath(itemPath, value, false, true); |
| 623 inst._flushProperties(); |
| 624 } |
| 625 } |
| 626 return true; |
| 627 } |
| 628 } |
| 629 |
| 630 /** |
| 631 * Returns the item associated with a given element stamped by |
| 632 * this `dom-repeat`. |
| 633 * |
| 634 * Note, to modify sub-properties of the item, |
| 635 * `modelForElement(el).set('item.<sub-prop>', value)` |
| 636 * should be used. |
| 637 * |
| 638 * @param {HTMLElement} el Element for which to return the item. |
| 639 * @return {*} Item associated with the element. |
| 640 */ |
| 641 itemForElement(el) { |
| 642 let instance = this.modelForElement(el); |
| 643 return instance && instance[this.as]; |
| 644 } |
| 645 |
| 646 /** |
| 647 * Returns the inst index for a given element stamped by this `dom-repeat`. |
| 648 * If `sort` is provided, the index will reflect the sorted order (rather |
| 649 * than the original array order). |
| 650 * |
| 651 * @param {HTMLElement} el Element for which to return the index. |
| 652 * @return {*} Row index associated with the element (note this may |
| 653 * not correspond to the array index if a user `sort` is applied). |
| 654 */ |
| 655 indexForElement(el) { |
| 656 let instance = this.modelForElement(el); |
| 657 return instance && instance[this.indexAs]; |
| 658 } |
| 659 |
| 660 /** |
| 661 * Returns the template "model" associated with a given element, which |
| 662 * serves as the binding scope for the template instance the element is |
| 663 * contained in. A template model is an instance of `Polymer.Base`, and |
| 664 * should be used to manipulate data associated with this template instance. |
| 665 * |
| 666 * Example: |
| 667 * |
| 668 * let model = modelForElement(el); |
| 669 * if (model.index < 10) { |
| 670 * model.set('item.checked', true); |
| 671 * } |
| 672 * |
| 673 * @param {HTMLElement} el Element for which to return a template model. |
| 674 * @return {TemplateInstanceBase} Model representing the binding scope for |
| 675 * the element. |
| 676 */ |
| 677 modelForElement(el) { |
| 678 return Polymer.Templatize.modelForElement(this.template, el); |
| 679 } |
| 680 |
| 681 } |
| 682 |
| 683 customElements.define(DomRepeat.is, DomRepeat); |
| 684 |
| 685 Polymer.DomRepeat = DomRepeat; |
| 686 |
| 687 })(); |
| 688 |
| 689 </script> |
| OLD | NEW |