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> |