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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: appengine/config_service/ui/bower_components/polymer/lib/elements/dom-repeat.html
diff --git a/appengine/config_service/ui/bower_components/polymer/lib/elements/dom-repeat.html b/appengine/config_service/ui/bower_components/polymer/lib/elements/dom-repeat.html
new file mode 100644
index 0000000000000000000000000000000000000000..5efa30a43dc38664f6a8057b48136f02e1be4acb
--- /dev/null
+++ b/appengine/config_service/ui/bower_components/polymer/lib/elements/dom-repeat.html
@@ -0,0 +1,689 @@
+<!--
+@license
+Copyright (c) 2017 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
+-->
+
+<link rel="import" href="../../polymer-element.html">
+<link rel="import" href="../utils/templatize.html">
+<link rel="import" href="../utils/debounce.html">
+<link rel="import" href="../utils/flush.html">
+<link rel="import" href="../mixins/mutable-data.html">
+
+<script>
+(function() {
+ 'use strict';
+
+ /**
+ * @constructor
+ * @implements {Polymer_OptionalMutableData}
+ * @extends {Polymer.Element}
+ */
+ const domRepeatBase = Polymer.OptionalMutableData(Polymer.Element);
+
+ /**
+ * The `<dom-repeat>` element will automatically stamp 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 me 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).
+ *
+ * A view-specific filter/sort may be applied to each `dom-repeat` by supplying a
+ * `filter` and/or `sort` property. This may be a string that names a function on
+ * the host, or a function may be assigned to the property directly. The functions
+ * should implemented following the standard `Array` filter/sort API.
+ *
+ * In order to re-run the filter or sort functions based on changes to sub-fields
+ * of `items`, the `observe` property may be set as a space-separated list of
+ * `item` sub-fields that should cause a re-filter/sort when modified. 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">
+ * ```
+ *
+ * @polymerElement
+ * @memberof Polymer
+ * @extends Polymer.Element
+ * @mixes Polymer.MutableData
+ * @summary Custom element for stamping instance of a template bound to
+ * items in an array.
+ */
+ class DomRepeat extends domRepeatBase {
+
+ // Not needed to find template; can be removed once the analyzer
+ // can find the tag name from customElements.define call
+ static get is() { return 'dom-repeat'; }
+
+ static get template() { return null; }
+
+ static get properties() {
+
+ /**
+ * Fired whenever DOM is added or removed by this template (by
+ * default, rendering occurs lazily). To force immediate rendering, call
+ * `render`.
+ *
+ * @event dom-change
+ */
+ return {
+
+ /**
+ * 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
+ * of the instance in the sorted and filtered list of rendered items.
+ * Note, for the index in the `this.items` array, use the value of the
+ * `itemsIndexAs` property.
+ */
+ indexAs: {
+ type: String,
+ value: 'index'
+ },
+
+ /**
+ * The name of the variable to add to the binding scope with the index
+ * of the instance in the `this.items` array. Note, for the index of
+ * this instance in the sorted and filtered list of rendered items,
+ * use the value of the `indexAs` property.
+ */
+ itemsIndexAs: {
+ type: String,
+ value: 'itemsIndex'
+ },
+
+ /**
+ * 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'
+ },
+
+ /**
+ * A function that can be used to filter items out of the view. 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.filter`.
+ * Using a filter function has no effect on the underlying `items` array.
+ */
+ 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: true,
+ 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
+ },
+
+ _targetFrameTime: {
+ type: Number,
+ computed: '__computeFrameTime(targetFramerate)'
+ }
+
+ }
+
+ }
+
+ static get observers() {
+ return [ '__itemsChanged(items.*)' ]
+ }
+
+ constructor() {
+ super();
+ this.__instances = [];
+ this.__limit = Infinity;
+ this.__pool = [];
+ this.__renderDebouncer = null;
+ this.__itemsIdxToInstIdx = {};
+ this.__chunkCount = null;
+ this.__lastChunkTime = null;
+ this.__needFullRefresh = false;
+ this.__sortFn = null;
+ this.__filterFn = null;
+ this.__observePaths = null;
+ this.__ctor = null;
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this.__isDetached = true;
+ for (let i=0; i<this.__instances.length; i++) {
+ this.__detachInstance(i);
+ }
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ // only perform attachment if the element was previously detached.
+ if (this.__isDetached) {
+ this.__isDetached = false;
+ let parent = this.parentNode;
+ for (let i=0; i<this.__instances.length; i++) {
+ this.__attachInstance(i, parent);
+ }
+ }
+ }
+
+ __ensureTemplatized() {
+ // 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) {
+ let template = this.template = this.querySelector('template');
+ if (!template) {
+ // // Wait until childList changes and template should be there by then
+ let observer = new MutationObserver(() => {
+ if (this.querySelector('template')) {
+ observer.disconnect();
+ this.__render();
+ } else {
+ throw new Error('dom-repeat requires a <template> child');
+ }
+ })
+ observer.observe(this, {childList: true});
+ return false;
+ }
+ // Template instance props that should be excluded from forwarding
+ let instanceProps = {};
+ instanceProps[this.as] = true;
+ instanceProps[this.indexAs] = true;
+ instanceProps[this.itemsIndexAs] = true;
+ this.__ctor = Polymer.Templatize.templatize(template, this, {
+ mutableData: this.mutableData,
+ parentModel: true,
+ instanceProps: instanceProps,
+ forwardHostProp: function(prop, value) {
+ let i$ = this.__instances;
+ for (let i=0, inst; (i<i$.length) && (inst=i$[i]); i++) {
+ inst.forwardHostProp(prop, value);
+ }
+ },
+ notifyInstanceProp: function(inst, prop, value) {
+ if (Polymer.Path.matches(this.as, prop)) {
+ let idx = inst[this.itemsIndexAs];
+ if (prop == this.as) {
+ this.items[idx] = value;
+ }
+ let path = Polymer.Path.translate(this.as, 'items.' + idx, prop);
+ this.notifyPath(path, value);
+ }
+ }
+ });
+ }
+ return true;
+ }
+
+ __getMethodHost() {
+ // Technically this should be the owner of the outermost template.
+ // In shadow dom, this is always getRootNode().host, but we can
+ // approximate this via cooperation with our dataHost always setting
+ // `_methodHost` as long as there were bindings (or id's) on this
+ // instance causing it to get a dataHost.
+ return this.__dataHost._methodHost || this.__dataHost;
+ }
+
+ __sortChanged(sort) {
+ let methodHost = this.__getMethodHost();
+ this.__sortFn = sort && (typeof sort == 'function' ? sort :
+ function() { return methodHost[sort].apply(methodHost, arguments); });
+ this.__needFullRefresh = true;
+ if (this.items) {
+ this.__debounceRender(this.__render);
+ }
+ }
+
+ __filterChanged(filter) {
+ let methodHost = this.__getMethodHost();
+ this.__filterFn = filter && (typeof filter == 'function' ? filter :
+ function() { return methodHost[filter].apply(methodHost, arguments); });
+ this.__needFullRefresh = true;
+ if (this.items) {
+ this.__debounceRender(this.__render);
+ }
+ }
+
+ __computeFrameTime(rate) {
+ return Math.ceil(1000/rate);
+ }
+
+ __initializeChunking() {
+ if (this.initialCount) {
+ this.__limit = this.initialCount;
+ this.__chunkCount = this.initialCount;
+ this.__lastChunkTime = performance.now();
+ }
+ }
+
+ __tryRenderChunk() {
+ // 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.__debounceRender(this.__requestRenderChunk);
+ }
+ }
+
+ __requestRenderChunk() {
+ requestAnimationFrame(()=>this.__renderChunk());
+ }
+
+ __renderChunk() {
+ // 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
+ let currChunkTime = performance.now();
+ let ratio = this._targetFrameTime / (currChunkTime - this.__lastChunkTime);
+ this.__chunkCount = Math.round(this.__chunkCount * ratio) || 1;
+ this.__limit += this.__chunkCount;
+ this.__lastChunkTime = currChunkTime;
+ this.__debounceRender(this.__render);
+ }
+
+ __observeChanged() {
+ this.__observePaths = this.observe &&
+ this.observe.replace('.*', '.').split(' ');
+ }
+
+ __itemsChanged(change) {
+ if (this.items && !Array.isArray(this.items)) {
+ console.warn('dom-repeat expected array for `items`, found', this.items);
+ }
+ // If path was to an item (e.g. 'items.3' or 'items.3.foo'), forward the
+ // path to that instance synchronously (retuns false for non-item paths)
+ if (!this.__handleItemPath(change.path, change.value)) {
+ // Otherwise, the array was reset ('items') or spliced ('items.splices'),
+ // so queue a full refresh
+ this.__needFullRefresh = true;
+ this.__initializeChunking();
+ this.__debounceRender(this.__render);
+ }
+ }
+
+ __handleObservedPaths(path) {
+ if (this.__observePaths) {
+ path = path.substring(path.indexOf('.') + 1);
+ let paths = this.__observePaths;
+ for (let i=0; i<paths.length; i++) {
+ if (path.indexOf(paths[i]) === 0) {
+ this.__needFullRefresh = true;
+ this.__debounceRender(this.__render, this.delay);
+ return true;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {function()} fn Function to debounce.
+ * @param {number=} delay Delay in ms to debounce by.
+ */
+ __debounceRender(fn, delay) {
+ this.__renderDebouncer = Polymer.Debouncer.debounce(
+ this.__renderDebouncer
+ , delay > 0 ? Polymer.Async.timeOut.after(delay) : Polymer.Async.microTask
+ , fn.bind(this));
+ Polymer.enqueueDebouncer(this.__renderDebouncer);
+ }
+
+ /**
+ * 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() {
+ // Queue this repeater, then flush all in order
+ this.__needFullRefresh = true;
+ this.__debounceRender(this.__render);
+ Polymer.flush();
+ }
+
+ __render() {
+ if (!this.__ensureTemplatized()) {
+ // No template found yet
+ return;
+ }
+ this.__applyFullRefresh();
+ // Reset the pool
+ // TODO(kschaaf): Reuse pool across turns and nested templates
+ // Now that objects/arrays are re-evaluated when set, we can safely
+ // 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;
+ // Set rendered item count
+ this._setRenderedItemCount(this.__instances.length);
+ // Notify users
+ this.dispatchEvent(new CustomEvent('dom-change', {
+ bubbles: true,
+ composed: true
+ }));
+ // Check to see if we need to render more items
+ this.__tryRenderChunk();
+ }
+
+ __applyFullRefresh() {
+ const items = this.items || [];
+ let isntIdxToItemsIdx = new Array(items.length);
+ for (let i=0; i<items.length; i++) {
+ isntIdxToItemsIdx[i] = i;
+ }
+ // Apply user filter
+ if (this.__filterFn) {
+ isntIdxToItemsIdx = isntIdxToItemsIdx.filter((i, idx, array) =>
+ this.__filterFn(items[i], idx, array));
+ }
+ // Apply user sort
+ if (this.__sortFn) {
+ isntIdxToItemsIdx.sort((a, b) => this.__sortFn(items[a], items[b]));
+ }
+ // items->inst 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<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);
+ }
+ }
+
+ __detachInstance(idx) {
+ let inst = this.__instances[idx];
+ for (let i=0; i<inst.children.length; i++) {
+ let el = inst.children[i];
+ inst.root.appendChild(el);
+ }
+ return inst;
+ }
+
+ __attachInstance(idx, parent) {
+ let inst = this.__instances[idx];
+ parent.insertBefore(inst.root, this);
+ }
+
+ __detachAndRemoveInstance(idx) {
+ let inst = this.__detachInstance(idx);
+ if (inst) {
+ this.__pool.push(inst);
+ }
+ this.__instances.splice(idx, 1);
+ }
+
+ __stampInstance(item, instIdx, itemIdx) {
+ let model = {};
+ model[this.as] = item;
+ model[this.indexAs] = instIdx;
+ model[this.itemsIndexAs] = itemIdx;
+ return new this.__ctor(model);
+ }
+
+ __insertInstance(item, instIdx, itemIdx) {
+ let inst = 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
+ inst._setPendingProperty(this.as, item);
+ inst._setPendingProperty(this.indexAs, instIdx);
+ inst._setPendingProperty(this.itemsIndexAs, itemIdx);
+ inst._flushProperties();
+ } else {
+ inst = this.__stampInstance(item, instIdx, itemIdx);
+ }
+ let beforeRow = this.__instances[instIdx + 1];
+ let beforeNode = beforeRow ? beforeRow.children[0] : this;
+ this.parentNode.insertBefore(inst.root, beforeNode);
+ this.__instances[instIdx] = inst;
+ return inst;
+ }
+
+ // Implements extension point from Templatize mixin
+ _showHideChildren(hidden) {
+ for (let i=0; i<this.__instances.length; i++) {
+ this.__instances[i]._showHideChildren(hidden);
+ }
+ }
+
+ // Called as a side effect of a host items.<key>.<path> path change,
+ // responsible for notifying item.<path> changes to inst for key
+ __handleItemPath(path, value) {
+ let itemsPath = path.slice(6); // 'items.'.length == 6
+ let dot = itemsPath.indexOf('.');
+ let itemsIdx = dot < 0 ? itemsPath : itemsPath.substring(0, dot);
+ // If path was index into array...
+ if (itemsIdx == parseInt(itemsIdx, 10)) {
+ let itemSubPath = dot < 0 ? '' : itemsPath.substring(dot+1);
+ // See if the item subpath should trigger a full refresh...
+ if (!this.__handleObservedPaths(itemSubPath)) {
+ // If not, forward to the instance for that index
+ let instIdx = this.__itemsIdxToInstIdx[itemsIdx];
+ let inst = this.__instances[instIdx];
+ if (inst) {
+ let itemPath = this.as + (itemSubPath ? '.' + itemSubPath : '');
+ // This is effectively `notifyPath`, but avoids some of the overhead
+ // of the public API
+ inst._setPendingPropertyOrPath(itemPath, value, false, true);
+ inst._flushProperties();
+ }
+ }
+ return 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.
+ *
+ * @param {HTMLElement} el Element for which to return the item.
+ * @return {*} Item associated with the element.
+ */
+ itemForElement(el) {
+ let instance = this.modelForElement(el);
+ return instance && instance[this.as];
+ }
+
+ /**
+ * 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).
+ *
+ * @param {HTMLElement} el Element for which to return the index.
+ * @return {*} Row index associated with the element (note this may
+ * not correspond to the array index if a user `sort` is applied).
+ */
+ indexForElement(el) {
+ let instance = this.modelForElement(el);
+ return instance && instance[this.indexAs];
+ }
+
+ /**
+ * Returns the template "model" associated with a given element, which
+ * serves as the binding scope for the template instance the element is
+ * contained in. A template model is an instance of `Polymer.Base`, and
+ * should be used to manipulate data associated with this template instance.
+ *
+ * Example:
+ *
+ * let model = modelForElement(el);
+ * if (model.index < 10) {
+ * model.set('item.checked', true);
+ * }
+ *
+ * @param {HTMLElement} el Element for which to return a template model.
+ * @return {TemplateInstanceBase} Model representing the binding scope for
+ * the element.
+ */
+ modelForElement(el) {
+ return Polymer.Templatize.modelForElement(this.template, el);
+ }
+
+ }
+
+ customElements.define(DomRepeat.is, DomRepeat);
+
+ Polymer.DomRepeat = DomRepeat;
+
+})();
+
+</script>

Powered by Google App Engine
This is Rietveld 408576698