Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(129)

Side by Side Diff: appengine/config_service/ui/bower_components/polymer/lib/elements/dom-repeat.html

Issue 2923973003: Added base template for config ui. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698