| Index: third_party/polymer/v0_8/components/polymer/src/lib/template/x-repeat.html
|
| diff --git a/third_party/polymer/v0_8/components/polymer/src/lib/template/x-repeat.html b/third_party/polymer/v0_8/components/polymer/src/lib/template/x-repeat.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c5842688435a422632b89374760eef4f4459c9da
|
| --- /dev/null
|
| +++ b/third_party/polymer/v0_8/components/polymer/src/lib/template/x-repeat.html
|
| @@ -0,0 +1,546 @@
|
| +<!--
|
| +@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
|
| +-->
|
| +
|
| +<!--
|
| +
|
| +**THIS ELEMENT IS EXPERIMENTAL. API AND NAME SUBJECT TO CHANGE.**
|
| +
|
| +The `x-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. `x-repeat` accepts an `items` property, and one
|
| +instance of the template is stamped for each item into the DOM at the location
|
| +of the `x-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="x-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 (`push`, `pop`, `splice`, `shift`,
|
| +`unshift`) are observed via `Array.observe` (where supported, or an
|
| +shim of this API on unsupported browsers), and template instances are kept in
|
| +sync with the data in the array.
|
| +
|
| +A view-specific filter/sort may be applied to each `x-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.
|
| +
|
| +For example, for an `x-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="x-repeat" items="{{employees}}"
|
| + filter="isEngineer" observe="type manager.type">
|
| +```
|
| +
|
| +-->
|
| +
|
| +<link rel="import" href="templatizer.html">
|
| +<link rel="import" href="../array-observe.html">
|
| +<link rel="import" href="../collection.html">
|
| +
|
| +<script>
|
| +
|
| + Polymer({
|
| +
|
| + is: 'x-repeat',
|
| + extends: 'template',
|
| +
|
| + 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
|
| + },
|
| +
|
| + /**
|
| + * 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
|
| + },
|
| +
|
| + behaviors: [
|
| + Polymer.Templatizer
|
| + ],
|
| +
|
| + observers: [
|
| + '_itemsChanged(items.*)'
|
| + ],
|
| +
|
| + created: function() {
|
| + this.boundCollectionObserver = this.render.bind(this);
|
| + },
|
| +
|
| + ready: function() {
|
| + // Templatizing (generating the instance constructor) needs to wait
|
| + // until attached, since it may not have its template content handed
|
| + // back to it until then, following its host template stamping
|
| + if (!this.ctor) {
|
| + this.templatize(this);
|
| + }
|
| + },
|
| +
|
| + _sortChanged: function() {
|
| + var dataHost = this._getRootDataHost();
|
| + this._sortFn = this.sort && (typeof this.sort == 'function' ?
|
| + this.sort : dataHost[this.sort].bind(this.host));
|
| + if (this.items) {
|
| + this.debounce('render', this.render);
|
| + }
|
| + },
|
| +
|
| + _filterChanged: function() {
|
| + var dataHost = this._getRootDataHost();
|
| + this._filterFn = this.filter && (typeof this.filter == 'function' ?
|
| + this.filter : dataHost[this.filter].bind(this.host));
|
| + if (this.items) {
|
| + this.debounce('render', this.render);
|
| + }
|
| + },
|
| +
|
| + _observeChanged: function() {
|
| + this._observePaths = this.observe &&
|
| + this.observe.replace('.*', '.').split(' ');
|
| + },
|
| +
|
| + _itemsChanged: function(change) {
|
| + if (change.path == 'items') {
|
| + this._unobserveCollection();
|
| + if (change.value) {
|
| + this._observeCollection(change.value);
|
| + this.debounce('render', this.render);
|
| + }
|
| + } else {
|
| + this._forwardItemPath(change.path, change.value);
|
| + this._checkObservedPaths(change.path);
|
| + }
|
| + },
|
| +
|
| + _checkObservedPaths: function(path) {
|
| + if (this._observePaths && path.indexOf('items.') === 0) {
|
| + path = path.substring(path.indexOf('.', 6) + 1);
|
| + var paths = this._observePaths;
|
| + for (var i=0; i<paths.length; i++) {
|
| + if (path.indexOf(paths[i]) === 0) {
|
| + this.debounce('render', this.render, this.delay);
|
| + return;
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + _observeCollection: function(items) {
|
| + this.collection = Array.isArray(items) ? Polymer.Collection.get(items) : items;
|
| + this.collection.observe(this.boundCollectionObserver);
|
| + },
|
| +
|
| + _unobserveCollection: function() {
|
| + if (this.collection) {
|
| + this.collection.unobserve(this.boundCollectionObserver);
|
| + }
|
| + },
|
| +
|
| + render: function(splices) {
|
| + this.flushDebouncer('render');
|
| + var c = this.collection;
|
| + if (splices) {
|
| + if (this._sortFn || splices[0].index == null) {
|
| + this._applySplicesViewSort(splices);
|
| + } else {
|
| + this._applySplicesArraySort(splices);
|
| + }
|
| + } else {
|
| + this._sortAndFilter();
|
| + }
|
| + var rowForKey = this._rowForKey = {};
|
| + var keys = this._orderedKeys;
|
| + // Assign items and keys
|
| + this.rows = this.rows || [];
|
| + for (var i=0; i<keys.length; i++) {
|
| + var key = keys[i];
|
| + var item = c.getItem(key);
|
| + var row = this.rows[i];
|
| + rowForKey[key] = i;
|
| + if (!row) {
|
| + this.rows.push(row = this._insertRow(i, null, item));
|
| + }
|
| + row.item = item;
|
| + row.key = key;
|
| + row.index = i;
|
| + }
|
| + // Remove extra
|
| + for (; i<this.rows.length; i++) {
|
| + this._detachRow(i);
|
| + }
|
| + this.rows.splice(keys.length, this.rows.length-keys.length);
|
| + },
|
| +
|
| + _sortAndFilter: function() {
|
| + var c = this.collection;
|
| + this._orderedKeys = c.getKeys();
|
| + // Filter
|
| + if (this._filterFn) {
|
| + this._orderedKeys = this._orderedKeys.filter(function(a) {
|
| + return this._filterFn(c.getItem(a));
|
| + }, this);
|
| + }
|
| + // Sort
|
| + if (this._sortFn) {
|
| + this._orderedKeys.sort(function(a, b) {
|
| + return this._sortFn(c.getItem(a), c.getItem(b));
|
| + }.bind(this));
|
| + }
|
| + },
|
| +
|
| + _keySort: function(a, b) {
|
| + return this.collection.getKey(a) - this.collection.getKey(b);
|
| + },
|
| +
|
| + _applySplicesViewSort: function(splices) {
|
| + var c = this.collection;
|
| + var keys = this._orderedKeys;
|
| + var rows = this.rows;
|
| + var removedRows = [];
|
| + var addedKeys = [];
|
| + var pool = [];
|
| + var sortFn = this._sortFn || this._keySort.bind(this);
|
| + splices.forEach(function(s) {
|
| + // Collect all removed row idx's
|
| + for (var i=0; i<s.removed.length; i++) {
|
| + var idx = this._rowForKey[s.removed[i]];
|
| + if (idx != null) {
|
| + removedRows.push(idx);
|
| + }
|
| + }
|
| + // Collect all added keys
|
| + for (i=0; i<s.added.length; i++) {
|
| + addedKeys.push(s.added[i]);
|
| + }
|
| + }, this);
|
| + if (removedRows.length) {
|
| + // Sort removed rows idx's
|
| + removedRows.sort();
|
| + // Remove keys and pool rows (backwards, so we don't invalidate rowForKey)
|
| + for (i=removedRows.length-1; i>=0 ; i--) {
|
| + var idx = removedRows[i];
|
| + pool.push(this._detachRow(idx));
|
| + rows.splice(idx, 1);
|
| + keys.splice(idx, 1);
|
| + }
|
| + }
|
| + if (addedKeys.length) {
|
| + // Filter added keys
|
| + if (this._filterFn) {
|
| + addedKeys = addedKeys.filter(function(a) {
|
| + return this._filterFn(c.getItem(a));
|
| + }, this);
|
| + }
|
| + // Sort added keys
|
| + addedKeys.sort(function(a, b) {
|
| + return this.sortFn(c.getItem(a), c.getItem(b));
|
| + }, this);
|
| + // Insert new rows using sort (from pool or newly created)
|
| + var start = 0;
|
| + for (i=0; i<addedKeys.length; i++) {
|
| + start = this._insertRowIntoViewSort(start, addedKeys[i], pool);
|
| + }
|
| + }
|
| + },
|
| +
|
| + _insertRowIntoViewSort: function(start, key, pool) {
|
| + var c = this.collection;
|
| + var item = c.getItem(key);
|
| + var end = this.rows.length - 1;
|
| + var idx = -1;
|
| + var sortFn = this._sortFn || this._keySort.bind(this);
|
| + // Binary search for insertion point
|
| + while (start <= end) {
|
| + var mid = (start + end) >> 1;
|
| + var midKey = this._orderedKeys[mid];
|
| + var cmp = 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 key & row at insertion point
|
| + this._orderedKeys.splice(idx, 0, key);
|
| + this.rows.splice(idx, 0, this._insertRow(idx, pool));
|
| + return idx;
|
| + },
|
| +
|
| + _applySplicesArraySort: function(splices) {
|
| + var keys = this._orderedKeys;
|
| + var pool = [];
|
| + splices.forEach(function(s) {
|
| + // Remove & pool rows first, to ensure we can fully reuse removed rows
|
| + for (var i=0; i<s.removed.length; i++) {
|
| + pool.push(this._detachRow(s.index + i));
|
| + }
|
| + this.rows.splice(s.index, s.removed.length);
|
| + }, this);
|
| + var c = this.collection;
|
| + var filterDelta = 0;
|
| + splices.forEach(function(s) {
|
| + // Filter added keys
|
| + var addedKeys = s.added;
|
| + if (this._filterFn) {
|
| + addedKeys = addedKeys.filter(function(a) {
|
| + return this._filterFn(c.getItem(a));
|
| + }, this);
|
| + filterDelta += (s.added.length - addedKeys.length);
|
| + }
|
| + var idx = s.index - filterDelta;
|
| + // Apply splices to keys
|
| + var args = [idx, s.removed.length].concat(addedKeys);
|
| + keys.splice.apply(keys, args);
|
| + // Insert new rows (from pool or newly created)
|
| + var addedRows = [];
|
| + for (i=0; i<s.added.length; i++) {
|
| + addedRows.push(this._insertRow(idx + i, pool));
|
| + }
|
| + args = [s.index, 0].concat(addedRows);
|
| + this.rows.splice.apply(this.rows, args);
|
| + }, this);
|
| + },
|
| +
|
| + _detachRow: function(idx) {
|
| + var row = this.rows[idx];
|
| + var parentNode = Polymer.dom(this).parentNode;
|
| + for (var i=0; i<row._children.length; i++) {
|
| + var el = row._children[i];
|
| + Polymer.dom(row.root).appendChild(el);
|
| + }
|
| + return row;
|
| + },
|
| +
|
| + _insertRow: function(idx, pool, item) {
|
| + var row = (pool && pool.pop()) || this._generateRow(idx, item);
|
| + var beforeRow = this.rows[idx];
|
| + var beforeNode = beforeRow ? beforeRow._children[0] : this;
|
| + var parentNode = Polymer.dom(this).parentNode;
|
| + Polymer.dom(parentNode).insertBefore(row.root, beforeNode);
|
| + return row;
|
| + },
|
| +
|
| + _generateRow: function(idx, item) {
|
| + var row = this.stamp({
|
| + index: idx,
|
| + key: this.collection.getKey(item),
|
| + item: item
|
| + });
|
| + // each row is a document fragment which is lost when we appendChild,
|
| + // so we have to track each child individually
|
| + var children = [];
|
| + for (var n = row.root.firstChild; n; n=n.nextSibling) {
|
| + children.push(n);
|
| + n._templateInstance = row;
|
| + }
|
| + // Since archetype overrides Base/HTMLElement, Safari complains
|
| + // when accessing `children`
|
| + row._children = children;
|
| + return row;
|
| + },
|
| +
|
| + // Implements extension point from Templatizer mixin
|
| + _getStampedChildren: function() {
|
| + var children = [];
|
| + if (this.rows) {
|
| + for (var i=0; i<this.rows.length; i++) {
|
| + var c = this.rows[i]._children;
|
| + for (var j=0; j<c.length; j++)
|
| + children.push(c[j]);
|
| + }
|
| + }
|
| + return children;
|
| + },
|
| +
|
| + // Implements extension point from Templatizer
|
| + // Called as a side effect of a template instance path change, responsible
|
| + // for notifying items.<key-for-row>.<path> change up to host
|
| + _forwardInstancePath: function(row, root, subPath, value) {
|
| + if (root == 'item') {
|
| + this.notifyPath('items.' + row.key + '.' + subPath, value);
|
| + }
|
| + },
|
| +
|
| + // Implements extension point from Templatizer mixin
|
| + // Called as side-effect of a host property change, responsible for
|
| + // notifying parent.<prop> path change on each row
|
| + _forwardParentProp: function(prop, value) {
|
| + if (this.rows) {
|
| + this.rows.forEach(function(row) {
|
| + row.parent[prop] = value;
|
| + row.notifyPath('parent.' + prop, value, true);
|
| + }, this);
|
| + }
|
| + },
|
| +
|
| + // Implements extension point from Templatizer
|
| + // Called as side-effect of a host path change, responsible for
|
| + // notifying parent.<path> path change on each row
|
| + _forwardParentPath: function(path, value) {
|
| + if (this.rows) {
|
| + this.rows.forEach(function(row) {
|
| + row.notifyPath('parent.' + path, value, true);
|
| + }, this);
|
| + }
|
| + },
|
| +
|
| + // Called as a side effect of a host items.<key>.<path> path change,
|
| + // responsible for notifying item.<path> changes to row for key
|
| + _forwardItemPath: function(path, value) {
|
| + if (this._rowForKey) {
|
| + // 'items.'.length == 6
|
| + var dot = path.indexOf('.', 6);
|
| + var key = path.substring(6, dot < 0 ? path.length : dot);
|
| + var idx = this._rowForKey[key];
|
| + var row = this.rows[idx];
|
| + if (row) {
|
| + if (dot >= 0) {
|
| + path = 'item.' + path.substring(dot+1);
|
| + row.notifyPath(path, value, true);
|
| + } else {
|
| + row.item = value;
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + _instanceForElement: function(el) {
|
| + while (el && !el._templateInstance) {
|
| + el = el.parentNode;
|
| + }
|
| + return el && el._templateInstance;
|
| + },
|
| +
|
| + /**
|
| + * Returns the item associated with a given element stamped by
|
| + * this `x-repeat`.
|
| + */
|
| + itemForElement: function(el) {
|
| + var instance = this._instanceForElement(el);
|
| + return instance && instance.item;
|
| + },
|
| +
|
| + /**
|
| + * Returns the `Polymer.Collection` key associated with a given
|
| + * element stamped by this `x-repeat`.
|
| + */
|
| + keyForElement: function(el) {
|
| + var instance = this._instanceForElement(el);
|
| + return instance && instance.key;
|
| + },
|
| +
|
| + /**
|
| + * Returns the index in `items` associated with a given element
|
| + * stamped by this `x-repeat`.
|
| + */
|
| + indexForElement: function(el) {
|
| + var instance = this._instanceForElement(el);
|
| + return this.rows.indexOf(instance);
|
| + }
|
| +
|
| + });
|
| +
|
| +
|
| +</script>
|
|
|