polymer-rails 0.2.8 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -3
- data/app/assets/javascripts/polymer/polymer-micro.html +36 -0
- data/app/assets/javascripts/polymer/polymer-mini.html +45 -0
- data/app/assets/javascripts/polymer/polymer.html +59 -11
- data/app/assets/javascripts/polymer/src/lib/annotations/annotations.html +262 -0
- data/app/assets/javascripts/polymer/src/lib/annotations/demo/app-chrome.html +60 -0
- data/app/assets/javascripts/polymer/src/lib/array-observe.html +118 -0
- data/app/assets/javascripts/polymer/src/lib/array-splice.html +262 -0
- data/app/assets/javascripts/polymer/src/lib/async.html +68 -0
- data/app/assets/javascripts/polymer/src/lib/base.html +117 -0
- data/app/assets/javascripts/polymer/src/lib/bind/accessors.html +223 -0
- data/app/assets/javascripts/polymer/src/lib/bind/demo/app-chrome.html +28 -0
- data/app/assets/javascripts/polymer/src/lib/bind/demo/app.html +29 -0
- data/app/assets/javascripts/polymer/src/lib/bind/demo/src/annotations-bind-demo.html +76 -0
- data/app/assets/javascripts/polymer/src/lib/bind/demo/src/bind-demo.html +83 -0
- data/app/assets/javascripts/polymer/src/lib/bind/effects.html +80 -0
- data/app/assets/javascripts/polymer/src/lib/case-map.html +46 -0
- data/app/assets/javascripts/polymer/src/lib/collection.html +179 -0
- data/app/assets/javascripts/polymer/src/lib/css-parse.html +131 -0
- data/app/assets/javascripts/polymer/src/lib/debounce.html +69 -0
- data/app/assets/javascripts/polymer/src/lib/dom-api.html +467 -0
- data/app/assets/javascripts/polymer/src/lib/dom-module.html +68 -0
- data/app/assets/javascripts/polymer/src/lib/event-api.html +92 -0
- data/app/assets/javascripts/polymer/src/lib/expr/focus.html +22 -0
- data/app/assets/javascripts/polymer/src/lib/expr/gestures.html +1 -0
- data/app/assets/javascripts/polymer/src/lib/expr/log.html +21 -0
- data/app/assets/javascripts/polymer/src/lib/expr/sinspect.html +235 -0
- data/app/assets/javascripts/polymer/src/lib/expr/style-auditor.html +123 -0
- data/app/assets/javascripts/polymer/src/lib/expr/style-protector.html +52 -0
- data/app/assets/javascripts/polymer/src/lib/gestures.html +284 -0
- data/app/assets/javascripts/polymer/src/lib/lang.html +21 -0
- data/app/assets/javascripts/polymer/src/lib/module.html +56 -0
- data/app/assets/javascripts/polymer/src/lib/polymer-bootstrap.html +78 -0
- data/app/assets/javascripts/polymer/src/lib/resolve-url.html +82 -0
- data/app/assets/javascripts/polymer/src/lib/settings.html +52 -0
- data/app/assets/javascripts/polymer/src/lib/style-defaults.html +32 -0
- data/app/assets/javascripts/polymer/src/lib/style-transformer.html +185 -0
- data/app/assets/javascripts/polymer/src/lib/style-util.html +77 -0
- data/app/assets/javascripts/polymer/src/lib/template/templatizer.html +132 -0
- data/app/assets/javascripts/polymer/src/lib/template/x-array-selector.html +178 -0
- data/app/assets/javascripts/polymer/src/lib/template/x-autobind.html +80 -0
- data/app/assets/javascripts/polymer/src/lib/template/x-if.html +115 -0
- data/app/assets/javascripts/polymer/src/lib/template/x-repeat.html +510 -0
- data/app/assets/javascripts/polymer/src/lib/template/x-template.html +39 -0
- data/app/assets/javascripts/polymer/src/lib/x-style.html +115 -0
- data/app/assets/javascripts/polymer/src/micro/attributes.html +180 -0
- data/app/assets/javascripts/polymer/src/micro/constructor.html +74 -0
- data/app/assets/javascripts/polymer/src/micro/extends.html +79 -0
- data/app/assets/javascripts/polymer/src/micro/mixins.html +40 -0
- data/app/assets/javascripts/polymer/src/micro/properties.html +96 -0
- data/app/assets/javascripts/polymer/src/micro/tag.html +28 -0
- data/app/assets/javascripts/polymer/src/mini/ready.html +180 -0
- data/app/assets/javascripts/polymer/src/mini/shadow.html +41 -0
- data/app/assets/javascripts/polymer/src/mini/shady.html +365 -0
- data/app/assets/javascripts/polymer/src/mini/template.html +56 -0
- data/app/assets/javascripts/polymer/src/polymer-lib.html +15 -0
- data/app/assets/javascripts/polymer/src/standard/annotations.html +198 -0
- data/app/assets/javascripts/polymer/src/standard/configure.html +160 -0
- data/app/assets/javascripts/polymer/src/standard/effects.html +215 -0
- data/app/assets/javascripts/polymer/src/standard/events.html +127 -0
- data/app/assets/javascripts/polymer/src/standard/notify-path.html +260 -0
- data/app/assets/javascripts/polymer/src/standard/resolveUrl.html +27 -0
- data/app/assets/javascripts/polymer/src/standard/styling.html +157 -0
- data/app/assets/javascripts/polymer/src/standard/utils.html +158 -0
- data/app/assets/javascripts/polymer/src/standard/x-styling.html +300 -0
- data/app/assets/javascripts/webcomponentsjs/webcomponents-lite.js +2270 -0
- data/lib/generators/polymer/component/templates/component.html.erb +3 -3
- data/lib/generators/polymer/install/install_generator.rb +1 -1
- data/lib/polymer-rails/version.rb +1 -1
- metadata +80 -5
- data/app/assets/javascripts/polymer/layout.html +0 -286
- data/app/assets/javascripts/polymer/polymer.js +0 -14
- data/app/assets/javascripts/polymer/webcomponents.js +0 -14
@@ -0,0 +1,178 @@
|
|
1
|
+
<!--
|
2
|
+
@license
|
3
|
+
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
4
|
+
This code may only be used under the BSD style license found at http://polymer.github.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/CONTRIBUTORS.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/PATENTS.txt
|
9
|
+
-->
|
10
|
+
|
11
|
+
<!--
|
12
|
+
Keeping structured data in sync requires that Polymer understand the path
|
13
|
+
associations of data being bound. The `x-array-selector` element ensures path
|
14
|
+
linkage when selecting specific items from an array (either single or multiple).
|
15
|
+
The `items` property accepts an array of user data, and via the `select(item)`
|
16
|
+
and `deselect(item)` API, updates the `selected` property which may be bound to
|
17
|
+
other parts of the application, and any changes to sub-fields of `selected`
|
18
|
+
item(s) will be kept in sync with items in the `items` array. When `multi`
|
19
|
+
is false, `selected` is a property representing the last selected item. When
|
20
|
+
`multi` is true, `selected` is an array of multiply selected items.
|
21
|
+
|
22
|
+
```html
|
23
|
+
<dom-module id="employee-list">
|
24
|
+
|
25
|
+
<template>
|
26
|
+
|
27
|
+
<div> Employee list: </div>
|
28
|
+
<template is="x-repeat" id="employeeList" items="{{employees}}">
|
29
|
+
<div>First name: <span>{{item.first}}</span></div>
|
30
|
+
<div>Last name: <span>{{item.last}}</span></div>
|
31
|
+
<button on-click="toggleSelection">Select</button>
|
32
|
+
</template>
|
33
|
+
|
34
|
+
<x-array-selector id="selector" items="{{employees}}" selected="{{selected}}" multi toggle></x-array-selector>
|
35
|
+
|
36
|
+
<div> Selected employees: </div>
|
37
|
+
<template is="x-repeat" items="{{selected}}">
|
38
|
+
<div>First name: <span>{{item.first}}</span></div>
|
39
|
+
<div>Last name: <span>{{item.last}}</span></div>
|
40
|
+
</template>
|
41
|
+
|
42
|
+
</template>
|
43
|
+
|
44
|
+
<script>
|
45
|
+
Polymer({
|
46
|
+
is: 'employee-list',
|
47
|
+
ready: function() {
|
48
|
+
this.employees = [
|
49
|
+
{first: 'Bob', last: 'Smith'},
|
50
|
+
{first: 'Sally', last: 'Johnson'},
|
51
|
+
...
|
52
|
+
];
|
53
|
+
},
|
54
|
+
toggleSelection: function(e) {
|
55
|
+
var item = this.$.employeeList.itemForElement(e.target);
|
56
|
+
this.$.selector.select(item);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
</script>
|
60
|
+
|
61
|
+
</dom-module>
|
62
|
+
```
|
63
|
+
-->
|
64
|
+
|
65
|
+
<script>
|
66
|
+
|
67
|
+
Polymer({
|
68
|
+
is: 'x-array-selector',
|
69
|
+
|
70
|
+
properties: {
|
71
|
+
|
72
|
+
/**
|
73
|
+
* An array containing items from which selection will be made.
|
74
|
+
*/
|
75
|
+
items: {
|
76
|
+
type: Array,
|
77
|
+
observer: '_itemsChanged'
|
78
|
+
},
|
79
|
+
|
80
|
+
/**
|
81
|
+
* When `multi` is true, this is an array that contains any selected.
|
82
|
+
* When `multi` is false, this is the currently selected item, or `null`
|
83
|
+
* if no item is selected.
|
84
|
+
*/
|
85
|
+
selected: {
|
86
|
+
type: Object,
|
87
|
+
notify: true
|
88
|
+
},
|
89
|
+
|
90
|
+
/**
|
91
|
+
* When `true`, calling `select` on an item that is already selected
|
92
|
+
* will deselect the item.
|
93
|
+
*/
|
94
|
+
toggle: Boolean,
|
95
|
+
|
96
|
+
/**
|
97
|
+
* When `true`, multiple items may be selected at once (in this case,
|
98
|
+
* `selected` is an array of currently selected items). When `false`,
|
99
|
+
* only one item may be selected at a time.
|
100
|
+
*/
|
101
|
+
multi: Boolean
|
102
|
+
},
|
103
|
+
|
104
|
+
_itemsChanged: function() {
|
105
|
+
// Unbind previous selection
|
106
|
+
if (Array.isArray(this.selected)) {
|
107
|
+
for (var i=0; i<this.selected.length; i++) {
|
108
|
+
this.unbindPaths('selected.' + i);
|
109
|
+
}
|
110
|
+
} else {
|
111
|
+
this.unbindPaths('selected');
|
112
|
+
}
|
113
|
+
// Initialize selection
|
114
|
+
if (this.multi) {
|
115
|
+
this.selected = [];
|
116
|
+
} else {
|
117
|
+
this.selected = null;
|
118
|
+
}
|
119
|
+
},
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Deselects the given item if it is already selected.
|
123
|
+
*/
|
124
|
+
deselect: function(item) {
|
125
|
+
if (this.multi) {
|
126
|
+
var scol = Polymer.Collection.get(this.selected);
|
127
|
+
// var skey = scol.getKey(item);
|
128
|
+
// if (skey >= 0) {
|
129
|
+
var sidx = this.selected.indexOf(item);
|
130
|
+
if (sidx >= 0) {
|
131
|
+
var skey = scol.getKey(item);
|
132
|
+
this.selected.splice(sidx, 1);
|
133
|
+
// scol.remove(item);
|
134
|
+
this.unbindPaths('selected.' + skey);
|
135
|
+
return true;
|
136
|
+
}
|
137
|
+
} else {
|
138
|
+
this.selected = null;
|
139
|
+
this.unbindPaths('selected');
|
140
|
+
}
|
141
|
+
},
|
142
|
+
|
143
|
+
/**
|
144
|
+
* Selects the given item. When `toggle` is true, this will automatically
|
145
|
+
* deselect the item if already selected.
|
146
|
+
*/
|
147
|
+
select: function(item) {
|
148
|
+
var icol = Polymer.Collection.get(this.items);
|
149
|
+
var key = icol.getKey(item);
|
150
|
+
if (this.multi) {
|
151
|
+
// var sidx = this.selected.indexOf(item);
|
152
|
+
// if (sidx < 0) {
|
153
|
+
var scol = Polymer.Collection.get(this.selected);
|
154
|
+
var skey = scol.getKey(item);
|
155
|
+
if (skey >= 0) {
|
156
|
+
this.deselect(item);
|
157
|
+
} else if (this.toggle) {
|
158
|
+
this.selected.push(item);
|
159
|
+
// this.bindPaths('selected.' + sidx, 'items.' + skey);
|
160
|
+
// skey = Polymer.Collection.get(this.selected).add(item);
|
161
|
+
this.async(function() {
|
162
|
+
skey = scol.getKey(item);
|
163
|
+
this.bindPaths('selected.' + skey, 'items.' + key);
|
164
|
+
});
|
165
|
+
}
|
166
|
+
} else {
|
167
|
+
if (this.toggle && item == this.selected) {
|
168
|
+
this.deselect();
|
169
|
+
} else {
|
170
|
+
this.bindPaths('selected', 'items.' + key);
|
171
|
+
this.selected = item;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
});
|
177
|
+
|
178
|
+
</script>
|
@@ -0,0 +1,80 @@
|
|
1
|
+
<!--
|
2
|
+
@license
|
3
|
+
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
4
|
+
This code may only be used under the BSD style license found at http://polymer.github.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/CONTRIBUTORS.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/PATENTS.txt
|
9
|
+
-->
|
10
|
+
|
11
|
+
<!--
|
12
|
+
|
13
|
+
**THIS ELEMENT IS EXPERIMENTAL. API AND NAME SUBJECT TO CHANGE.**
|
14
|
+
|
15
|
+
Polymer's binding features are only available within templates that are managed
|
16
|
+
by Polymer. As such, these features are available in templates used to define
|
17
|
+
Polymer elements, for example, but not for elements placed directly in the main
|
18
|
+
document.
|
19
|
+
|
20
|
+
In order to use Polymer bindings without defining a new custom element, elements
|
21
|
+
utilizing bindings may be wrapped with the `x-autobind` template extension.
|
22
|
+
This template will immediately stamp itself into the main document and bind
|
23
|
+
elements to the template itself as the binding scope.
|
24
|
+
|
25
|
+
```html
|
26
|
+
<!doctype html>
|
27
|
+
<html>
|
28
|
+
<head>
|
29
|
+
<meta charset="utf-8">
|
30
|
+
<script src="components/webcomponentsjs/webcomponents-lite.js"></script>
|
31
|
+
<link rel="import" href="components/polymer/polymer.html">
|
32
|
+
<link rel="import" href="components/core-ajax/core-ajax.html">
|
33
|
+
|
34
|
+
</head>
|
35
|
+
<body>
|
36
|
+
|
37
|
+
<template is="x-autobind">
|
38
|
+
|
39
|
+
<core-ajax url="http://..." lastresponse="{{data}}"></core-ajax>
|
40
|
+
|
41
|
+
<template is="x-repeat" items="{{data}}">
|
42
|
+
<div><span>{{item.first}}</span> <span>{{item.last}}</span></div>
|
43
|
+
</template>
|
44
|
+
|
45
|
+
</template>
|
46
|
+
|
47
|
+
</body>
|
48
|
+
</html>
|
49
|
+
```
|
50
|
+
|
51
|
+
-->
|
52
|
+
|
53
|
+
<script>
|
54
|
+
|
55
|
+
Polymer({
|
56
|
+
|
57
|
+
is: 'x-autobind',
|
58
|
+
|
59
|
+
extends: 'template',
|
60
|
+
|
61
|
+
registerFeatures: function() {
|
62
|
+
this._prepExtends();
|
63
|
+
this._prepConstructor();
|
64
|
+
},
|
65
|
+
|
66
|
+
_finishDistribute: function() {
|
67
|
+
var parentDom = Polymer.dom(Polymer.dom(this).parentNode);
|
68
|
+
parentDom.insertBefore(this.root, this);
|
69
|
+
},
|
70
|
+
|
71
|
+
initFeatures: function() {
|
72
|
+
this._template = this;
|
73
|
+
this._prepAnnotations();
|
74
|
+
this._prepEffects();
|
75
|
+
Polymer.Base.initFeatures.call(this);
|
76
|
+
}
|
77
|
+
|
78
|
+
});
|
79
|
+
|
80
|
+
</script>
|
@@ -0,0 +1,115 @@
|
|
1
|
+
<!--
|
2
|
+
@license
|
3
|
+
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
4
|
+
This code may only be used under the BSD style license found at http://polymer.github.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/CONTRIBUTORS.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/PATENTS.txt
|
9
|
+
-->
|
10
|
+
|
11
|
+
<link rel="import" href="templatizer.html">
|
12
|
+
|
13
|
+
<script>
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Stamps the template iff the `if` property is truthy.
|
17
|
+
*
|
18
|
+
* When `if` becomes falsey, the stamped content is hidden but not
|
19
|
+
* removed from dom. When `if` subsequently becomes truthy again, the content
|
20
|
+
* is simply re-shown. This approach is used due to its favorable performance
|
21
|
+
* characteristics: the expense of creating template content is paid only
|
22
|
+
* once and lazily.
|
23
|
+
*
|
24
|
+
* Set the `restamp` property to true to force the stamped content to be
|
25
|
+
* created / destroyed when the `if` condition changes.
|
26
|
+
*/
|
27
|
+
Polymer({
|
28
|
+
|
29
|
+
is: 'x-if',
|
30
|
+
extends: 'template',
|
31
|
+
|
32
|
+
properties: {
|
33
|
+
|
34
|
+
'if': {
|
35
|
+
type: Boolean
|
36
|
+
},
|
37
|
+
|
38
|
+
item: {
|
39
|
+
type: Object
|
40
|
+
},
|
41
|
+
|
42
|
+
restamp: {
|
43
|
+
type: Boolean
|
44
|
+
}
|
45
|
+
|
46
|
+
},
|
47
|
+
|
48
|
+
mixins: [
|
49
|
+
Polymer.Templatizer
|
50
|
+
],
|
51
|
+
|
52
|
+
observers: {
|
53
|
+
'if item restamp': 'render'
|
54
|
+
},
|
55
|
+
|
56
|
+
render: function() {
|
57
|
+
if (!this.ctor) {
|
58
|
+
this._wrapTextNodes(this._content);
|
59
|
+
this.templatize(this);
|
60
|
+
}
|
61
|
+
if (this.if) {
|
62
|
+
this._ensureInstance();
|
63
|
+
} else if (this.restamp) {
|
64
|
+
this._teardownInstance();
|
65
|
+
}
|
66
|
+
if (!this.restamp && this._instance) {
|
67
|
+
this._showHideInstance(this.if);
|
68
|
+
}
|
69
|
+
},
|
70
|
+
|
71
|
+
_ensureInstance: function() {
|
72
|
+
if (!this._instance) {
|
73
|
+
// TODO(sorvell): pickup stamping logic from x-repeat
|
74
|
+
this._instance = this.stamp(this.item);
|
75
|
+
var root = this._instance.root;
|
76
|
+
this._instance._children = Array.prototype.slice.call(root.childNodes);
|
77
|
+
// TODO(sorvell): this incantation needs to be simpler.
|
78
|
+
var parent = Polymer.dom(Polymer.dom(this).parentNode);
|
79
|
+
parent.insertBefore(root, this);
|
80
|
+
}
|
81
|
+
},
|
82
|
+
|
83
|
+
_teardownInstance: function() {
|
84
|
+
if (this._instance) {
|
85
|
+
var parent = Polymer.dom(Polymer.dom(this).parentNode);
|
86
|
+
this._instance._children.forEach(function(n) {
|
87
|
+
parent.removeChild(n);
|
88
|
+
});
|
89
|
+
this._instance = null;
|
90
|
+
}
|
91
|
+
},
|
92
|
+
|
93
|
+
_wrapTextNodes: function(root) {
|
94
|
+
// wrap text nodes in span so they can be hidden.
|
95
|
+
for (var n = root.firstChild; n; n=n.nextSibling) {
|
96
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
97
|
+
var s = document.createElement('span');
|
98
|
+
root.insertBefore(s, n);
|
99
|
+
s.appendChild(n);
|
100
|
+
n = s;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
},
|
104
|
+
|
105
|
+
_showHideInstance: function(showing) {
|
106
|
+
this._instance._children.forEach(function(n) {
|
107
|
+
if (n.setAttribute) {
|
108
|
+
this.serializeValueToAttribute(!showing, 'hidden', n);
|
109
|
+
}
|
110
|
+
}, this);
|
111
|
+
}
|
112
|
+
|
113
|
+
});
|
114
|
+
|
115
|
+
</script>
|
@@ -0,0 +1,510 @@
|
|
1
|
+
<!--
|
2
|
+
@license
|
3
|
+
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
4
|
+
This code may only be used under the BSD style license found at http://polymer.github.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/CONTRIBUTORS.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/PATENTS.txt
|
9
|
+
-->
|
10
|
+
|
11
|
+
<!--
|
12
|
+
|
13
|
+
**THIS ELEMENT IS EXPERIMENTAL. API AND NAME SUBJECT TO CHANGE.**
|
14
|
+
|
15
|
+
The `x-repeat` element is a custom `HTMLTemplateElement` type extension that
|
16
|
+
automatically stamps and binds one instance of template content to each object
|
17
|
+
in a user-provided array. `x-repeat` accepts an `items` property, and one
|
18
|
+
instance of the template is stamped for each item into the DOM at the location
|
19
|
+
of the `x-repeat` element. The `item` property will be set on each instance's
|
20
|
+
binding scope, thus templates should bind to sub-properties of `item`.
|
21
|
+
|
22
|
+
Example:
|
23
|
+
|
24
|
+
```html
|
25
|
+
<dom-module id="employee-list">
|
26
|
+
|
27
|
+
<template>
|
28
|
+
|
29
|
+
<div> Employee list: </div>
|
30
|
+
<template is="x-repeat" items="{{employees}}">
|
31
|
+
<div>First name: <span>{{item.first}}</span></div>
|
32
|
+
<div>Last name: <span>{{item.last}}</span></div>
|
33
|
+
</template>
|
34
|
+
|
35
|
+
</template>
|
36
|
+
|
37
|
+
<script>
|
38
|
+
Polymer({
|
39
|
+
is: 'employee-list',
|
40
|
+
ready: function() {
|
41
|
+
this.employees = [
|
42
|
+
{first: 'Bob', last: 'Smith'},
|
43
|
+
{first: 'Sally', last: 'Johnson'},
|
44
|
+
...
|
45
|
+
];
|
46
|
+
}
|
47
|
+
});
|
48
|
+
</script>
|
49
|
+
|
50
|
+
</dom-module>
|
51
|
+
```
|
52
|
+
|
53
|
+
Notifications for changes to items sub-properties will be forwarded to template
|
54
|
+
instances, which will update via the normal structured data notification system.
|
55
|
+
|
56
|
+
Mutations to the `items` array itself (`push`, `pop`, `splice`, `shift`,
|
57
|
+
`unshift`) are observed via `Array.observe` (where supported, or an
|
58
|
+
shim of this API on unsupported browsers), and template instances are kept in
|
59
|
+
sync with the data in the array.
|
60
|
+
|
61
|
+
A view-specific filter/sort may be applied to each `x-repeat` by supplying a
|
62
|
+
`filter` and/or `sort` property. This may be a string that names a function on
|
63
|
+
the host, or a function may be assigned to the property directly. The functions
|
64
|
+
should implemented following the standard `Array` filter/sort API.
|
65
|
+
|
66
|
+
In order to re-run the filter or sort functions based on changes to sub-fields
|
67
|
+
of `items`, the `observe` property may be set as a space-separated list of
|
68
|
+
`item` sub-fields that should cause a re-filter/sort when modified.
|
69
|
+
|
70
|
+
For example, for an `x-repeat` with a filter of the following:
|
71
|
+
|
72
|
+
```js
|
73
|
+
isEngineer: function(item) {
|
74
|
+
return item.type == 'engineer' || item.manager.type == 'engineer';
|
75
|
+
}
|
76
|
+
```
|
77
|
+
|
78
|
+
Then the `observe` property should be configured as follows:
|
79
|
+
|
80
|
+
```html
|
81
|
+
<template is="x-repeat" items="{{employees}}"
|
82
|
+
filter="isEngineer" observe="type manager.type">
|
83
|
+
```
|
84
|
+
|
85
|
+
-->
|
86
|
+
|
87
|
+
<link rel="import" href="templatizer.html">
|
88
|
+
<link rel="import" href="../array-observe.html">
|
89
|
+
<link rel="import" href="../collection.html">
|
90
|
+
|
91
|
+
<script>
|
92
|
+
|
93
|
+
Polymer({
|
94
|
+
|
95
|
+
is: 'x-repeat',
|
96
|
+
extends: 'template',
|
97
|
+
|
98
|
+
properties: {
|
99
|
+
|
100
|
+
/**
|
101
|
+
* An array containing items determining how many instances of the template
|
102
|
+
* to stamp and that that each template instance should bind to.
|
103
|
+
*/
|
104
|
+
items: {
|
105
|
+
type: Array
|
106
|
+
},
|
107
|
+
|
108
|
+
/**
|
109
|
+
* A function that should determine the sort order of the items. This
|
110
|
+
* property should either be provided as a string, indicating a method
|
111
|
+
* name on the element's host, or else be an actual function. The
|
112
|
+
* function should match the sort function passed to `Array.sort`.
|
113
|
+
* Using a sort function has no effect on the underlying `items` array.
|
114
|
+
*/
|
115
|
+
sort: {
|
116
|
+
type: Function,
|
117
|
+
observer: '_sortChanged'
|
118
|
+
},
|
119
|
+
|
120
|
+
/**
|
121
|
+
* A function that can be used to filter items out of the view. This
|
122
|
+
* property should either be provided as a string, indicating a method
|
123
|
+
* name on the element's host, or else be an actual function. The
|
124
|
+
* function should match the sort function passed to `Array.filter`.
|
125
|
+
* Using a filter function has no effect on the underlying `items` array.
|
126
|
+
*/
|
127
|
+
filter: {
|
128
|
+
type: Function,
|
129
|
+
observer: '_filterChanged'
|
130
|
+
},
|
131
|
+
|
132
|
+
/**
|
133
|
+
* When using a `filter` or `sort` function, the `observe` property
|
134
|
+
* should be set to a space-separated list of the names of item
|
135
|
+
* sub-fields that should trigger a re-sort or re-filter when changed.
|
136
|
+
* These should generally be fields of `item` that the sort or filter
|
137
|
+
* function depends on.
|
138
|
+
*/
|
139
|
+
observe: {
|
140
|
+
type: String,
|
141
|
+
observer: '_observeChanged'
|
142
|
+
},
|
143
|
+
|
144
|
+
/**
|
145
|
+
* When using a `filter` or `sort` function, the `delay` property
|
146
|
+
* determines a debounce time after a change to observed item
|
147
|
+
* properties that must pass before the filter or sort is re-run.
|
148
|
+
* This is useful in rate-limiting shuffing of the view when
|
149
|
+
* item changes may be frequent.
|
150
|
+
*/
|
151
|
+
delay: Number
|
152
|
+
},
|
153
|
+
|
154
|
+
mixins: [
|
155
|
+
Polymer.Templatizer
|
156
|
+
],
|
157
|
+
|
158
|
+
observers: {
|
159
|
+
'items.*': '_itemsChanged'
|
160
|
+
},
|
161
|
+
|
162
|
+
created: function() {
|
163
|
+
this.boundCollectionObserver = this.render.bind(this);
|
164
|
+
},
|
165
|
+
|
166
|
+
ready: function() {
|
167
|
+
// Templatizing (generating the instance constructor) needs to wait
|
168
|
+
// until attached, since it may not have its template content handed
|
169
|
+
// back to it until then, following its host template stamping
|
170
|
+
if (!this.ctor) {
|
171
|
+
this.templatize(this);
|
172
|
+
}
|
173
|
+
if (this._renderPendingAttach) {
|
174
|
+
this._renderPendingAttach = false;
|
175
|
+
this.render();
|
176
|
+
}
|
177
|
+
},
|
178
|
+
|
179
|
+
_sortChanged: function() {
|
180
|
+
this._sortFn = this.sort && (typeof this.sort == 'function' ?
|
181
|
+
this.sort : this.host[this.sort].bind(this.host));
|
182
|
+
this.debounce('render', this.render);
|
183
|
+
},
|
184
|
+
|
185
|
+
_filterChanged: function() {
|
186
|
+
this._filterFn = this.filter && (typeof this.filter == 'function' ?
|
187
|
+
this.filter : this.host[this.filter].bind(this.host));
|
188
|
+
this.debounce('render', this.render);
|
189
|
+
},
|
190
|
+
|
191
|
+
_observeChanged: function() {
|
192
|
+
this._observePaths = this.observe &&
|
193
|
+
this.observe.replace('.*', '.').split(' ');
|
194
|
+
},
|
195
|
+
|
196
|
+
_itemsChanged: function(items, old, path) {
|
197
|
+
if (path) {
|
198
|
+
this._notifyElement(path, items);
|
199
|
+
this._checkObservedPaths(path);
|
200
|
+
} else {
|
201
|
+
if (old) {
|
202
|
+
this._unobserveCollection(old);
|
203
|
+
}
|
204
|
+
if (items) {
|
205
|
+
this._observeCollection(items);
|
206
|
+
this.debounce('render', this.render);
|
207
|
+
}
|
208
|
+
}
|
209
|
+
},
|
210
|
+
|
211
|
+
_checkObservedPaths: function(path) {
|
212
|
+
if (this._observePaths && path.indexOf('items.') === 0) {
|
213
|
+
path = path.substring(path.indexOf('.', 6) + 1);
|
214
|
+
var paths = this._observePaths;
|
215
|
+
for (var i=0; i<paths.length; i++) {
|
216
|
+
if (path.indexOf(paths[i]) === 0) {
|
217
|
+
this.debounce('render', this.render, this.delay);
|
218
|
+
return;
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
},
|
223
|
+
|
224
|
+
_observeCollection: function(items) {
|
225
|
+
this.collection = Array.isArray(items) ? Polymer.Collection.get(items) : items;
|
226
|
+
this.collection.observe(this.boundCollectionObserver);
|
227
|
+
},
|
228
|
+
|
229
|
+
_unobserveCollection: function(items) {
|
230
|
+
var collection = Polymer.Collection.get(items);
|
231
|
+
collection.unobserve(this.boundCollectionObserver);
|
232
|
+
},
|
233
|
+
|
234
|
+
render: function(splices) {
|
235
|
+
// TODO(kschaaf): should actually queue splices also
|
236
|
+
if (!this.isAttached) {
|
237
|
+
// Render must follow attachment
|
238
|
+
this._renderPendingAttach = true;
|
239
|
+
return;
|
240
|
+
}
|
241
|
+
this._render(splices);
|
242
|
+
},
|
243
|
+
|
244
|
+
_render: function(splices) {
|
245
|
+
var c = this.collection;
|
246
|
+
if (splices) {
|
247
|
+
if (this._sortFn || splices[0].index == null) {
|
248
|
+
this._applySplicesViewSort(splices);
|
249
|
+
} else {
|
250
|
+
this._applySplicesArraySort(splices);
|
251
|
+
}
|
252
|
+
} else {
|
253
|
+
this._sortAndFilter();
|
254
|
+
}
|
255
|
+
var rowForKey = this._rowForKey = {};
|
256
|
+
var keys = this._orderedKeys;
|
257
|
+
// Assign items and keys
|
258
|
+
this.rows = this.rows || [];
|
259
|
+
for (var i=0; i<keys.length; i++) {
|
260
|
+
var key = keys[i];
|
261
|
+
var item = c.getItem(key);
|
262
|
+
var row = this.rows[i];
|
263
|
+
if (!row) {
|
264
|
+
this.rows.push(row = this._insertRow(i, null, item));
|
265
|
+
}
|
266
|
+
row.item = item;
|
267
|
+
row.key = key;
|
268
|
+
rowForKey[key] = i;
|
269
|
+
}
|
270
|
+
// Remove extra
|
271
|
+
for (; i<this.rows.length; i++) {
|
272
|
+
this._detachRow(i);
|
273
|
+
}
|
274
|
+
this.rows.splice(keys.length, this.rows.length-keys.length);
|
275
|
+
},
|
276
|
+
|
277
|
+
_sortAndFilter: function() {
|
278
|
+
var c = this.collection;
|
279
|
+
this._orderedKeys = c.getKeys();
|
280
|
+
// Filter
|
281
|
+
if (this._filterFn) {
|
282
|
+
this._orderedKeys = this._orderedKeys.filter(function(a) {
|
283
|
+
return this._filterFn(c.getItem(a));
|
284
|
+
}, this);
|
285
|
+
}
|
286
|
+
// Sort
|
287
|
+
if (this._sortFn) {
|
288
|
+
this._orderedKeys.sort(function(a, b) {
|
289
|
+
return this._sortFn(c.getItem(a), c.getItem(b));
|
290
|
+
}.bind(this));
|
291
|
+
}
|
292
|
+
},
|
293
|
+
|
294
|
+
_keySort: function(a, b) {
|
295
|
+
return this.collection.getKey(a) - this.collection.getKey(b);
|
296
|
+
},
|
297
|
+
|
298
|
+
_applySplicesViewSort: function(splices) {
|
299
|
+
var c = this.collection;
|
300
|
+
var keys = this._orderedKeys;
|
301
|
+
var rows = this.rows;
|
302
|
+
var removedRows = [];
|
303
|
+
var addedKeys = [];
|
304
|
+
var pool = [];
|
305
|
+
var sortFn = this._sortFn || this._keySort.bind(this);
|
306
|
+
splices.forEach(function(s) {
|
307
|
+
// Collect all removed row idx's
|
308
|
+
for (var i=0; i<s.removed.length; i++) {
|
309
|
+
var idx = this._rowForKey[s.removed[i]];
|
310
|
+
if (idx != null) {
|
311
|
+
removedRows.push(idx);
|
312
|
+
}
|
313
|
+
}
|
314
|
+
// Collect all added keys
|
315
|
+
for (i=0; i<s.added.length; i++) {
|
316
|
+
addedKeys.push(s.added[i]);
|
317
|
+
}
|
318
|
+
}, this);
|
319
|
+
if (removedRows.length) {
|
320
|
+
// Sort removed rows idx's
|
321
|
+
removedRows.sort();
|
322
|
+
// Remove keys and pool rows (backwards, so we don't invalidate rowForKey)
|
323
|
+
for (i=removedRows.length-1; i>=0 ; i--) {
|
324
|
+
var idx = removedRows[i];
|
325
|
+
pool.push(this._detachRow(idx));
|
326
|
+
rows.splice(idx, 1);
|
327
|
+
keys.splice(idx, 1);
|
328
|
+
}
|
329
|
+
}
|
330
|
+
if (addedKeys.length) {
|
331
|
+
// Filter added keys
|
332
|
+
if (this._filterFn) {
|
333
|
+
addedKeys = addedKeys.filter(function(a) {
|
334
|
+
return this._filterFn(c.getItem(a));
|
335
|
+
}, this);
|
336
|
+
}
|
337
|
+
// Sort added keys
|
338
|
+
addedKeys.sort(function(a, b) {
|
339
|
+
return this.sortFn(c.getItem(a), c.getItem(b));
|
340
|
+
}, this);
|
341
|
+
// Insert new rows using sort (from pool or newly created)
|
342
|
+
var start = 0;
|
343
|
+
for (i=0; i<addedKeys.length; i++) {
|
344
|
+
start = this._insertRowIntoViewSort(start, addedKeys[i], pool);
|
345
|
+
}
|
346
|
+
}
|
347
|
+
},
|
348
|
+
|
349
|
+
_insertRowIntoViewSort: function(start, key, pool) {
|
350
|
+
var c = this.collection;
|
351
|
+
var item = c.getItem(key);
|
352
|
+
var end = this.rows.length - 1;
|
353
|
+
var idx = -1;
|
354
|
+
var sortFn = this._sortFn || this._keySort.bind(this);
|
355
|
+
// Binary search for insertion point
|
356
|
+
while (start <= end) {
|
357
|
+
var mid = (start + end) >> 1;
|
358
|
+
var midKey = this._orderedKeys[mid];
|
359
|
+
var cmp = sortFn(c.getItem(midKey), item);
|
360
|
+
if (cmp < 0) {
|
361
|
+
start = mid + 1;
|
362
|
+
} else if (cmp > 0) {
|
363
|
+
end = mid - 1;
|
364
|
+
} else {
|
365
|
+
idx = mid;
|
366
|
+
break;
|
367
|
+
}
|
368
|
+
}
|
369
|
+
if (idx < 0) {
|
370
|
+
idx = end + 1;
|
371
|
+
}
|
372
|
+
// Insert key & row at insertion point
|
373
|
+
this._orderedKeys.splice(idx, 0, key);
|
374
|
+
this.rows.splice(idx, 0, this._insertRow(idx, pool));
|
375
|
+
return idx;
|
376
|
+
},
|
377
|
+
|
378
|
+
_applySplicesArraySort: function(splices) {
|
379
|
+
var keys = this._orderedKeys;
|
380
|
+
var pool = [];
|
381
|
+
splices.forEach(function(s) {
|
382
|
+
// Remove & pool rows first, to ensure we can fully reuse removed rows
|
383
|
+
for (var i=0; i<s.removed.length; i++) {
|
384
|
+
pool.push(this._detachRow(s.index + i));
|
385
|
+
}
|
386
|
+
this.rows.splice(s.index, s.removed.length);
|
387
|
+
}, this);
|
388
|
+
var c = this.collection;
|
389
|
+
var filterDelta = 0;
|
390
|
+
splices.forEach(function(s) {
|
391
|
+
// Filter added keys
|
392
|
+
var addedKeys = s.added;
|
393
|
+
if (this._filterFn) {
|
394
|
+
addedKeys = addedKeys.filter(function(a) {
|
395
|
+
return this._filterFn(c.getItem(a));
|
396
|
+
}, this);
|
397
|
+
filterDelta += (s.added.length - addedKeys.length);
|
398
|
+
}
|
399
|
+
var idx = s.index - filterDelta;
|
400
|
+
// Apply splices to keys
|
401
|
+
var args = [idx, s.removed.length].concat(addedKeys);
|
402
|
+
keys.splice.apply(keys, args);
|
403
|
+
// Insert new rows (from pool or newly created)
|
404
|
+
var addedRows = [];
|
405
|
+
for (i=0; i<s.added.length; i++) {
|
406
|
+
addedRows.push(this._insertRow(idx + i, pool));
|
407
|
+
}
|
408
|
+
args = [s.index, 0].concat(addedRows);
|
409
|
+
this.rows.splice.apply(this.rows, args);
|
410
|
+
}, this);
|
411
|
+
},
|
412
|
+
|
413
|
+
_detachRow: function(idx) {
|
414
|
+
var row = this.rows[idx];
|
415
|
+
var parentNode = Polymer.dom(this).parentNode;
|
416
|
+
for (var i=0; i<row._children.length; i++) {
|
417
|
+
var el = row._children[i];
|
418
|
+
Polymer.dom(row.root).appendChild(el);
|
419
|
+
}
|
420
|
+
return row;
|
421
|
+
},
|
422
|
+
|
423
|
+
_insertRow: function(idx, pool, item) {
|
424
|
+
var row = (pool && pool.pop()) || this._generateRow(idx, item);
|
425
|
+
var beforeRow = this.rows[idx];
|
426
|
+
var beforeNode = beforeRow ? beforeRow._children[0] : this;
|
427
|
+
var parentNode = Polymer.dom(this).parentNode;
|
428
|
+
row.root.__styleScoped = true;
|
429
|
+
Polymer.dom(parentNode).insertBefore(row.root, beforeNode);
|
430
|
+
return row;
|
431
|
+
},
|
432
|
+
|
433
|
+
_generateRow: function(idx, item) {
|
434
|
+
var row = this.stamp({
|
435
|
+
item: item,
|
436
|
+
pathDelegate: this
|
437
|
+
});
|
438
|
+
// each row is a document fragment which is lost when we appendChild,
|
439
|
+
// so we have to track each child individually
|
440
|
+
var children = [];
|
441
|
+
for (var n = row.root.firstChild; n; n=n.nextSibling) {
|
442
|
+
children.push(n);
|
443
|
+
n._templateInstance = row;
|
444
|
+
}
|
445
|
+
// Since archetype overrides Base/HTMLElement, Safari complains
|
446
|
+
// when accessing `children`
|
447
|
+
row._children = children;
|
448
|
+
return row;
|
449
|
+
},
|
450
|
+
|
451
|
+
_notifyDelegatePath: function(row, path, value) {
|
452
|
+
this.notifyPath(path.replace('item', 'items.' + row.key), value);
|
453
|
+
},
|
454
|
+
|
455
|
+
_notifyElement: function(path, value) {
|
456
|
+
if (this._rowForKey) {
|
457
|
+
// 'items.'.length == 6
|
458
|
+
var dot = path.indexOf('.', 6);
|
459
|
+
var key = path.substring(6, dot < 0 ? path.length : dot);
|
460
|
+
var idx = this._rowForKey[key];
|
461
|
+
var row = this.rows[idx];
|
462
|
+
if (row) {
|
463
|
+
if (dot >= 0) {
|
464
|
+
path = 'item.' + path.substring(dot+1);
|
465
|
+
row.notifyPath(path, value, true);
|
466
|
+
} else {
|
467
|
+
row.item = value;
|
468
|
+
}
|
469
|
+
}
|
470
|
+
}
|
471
|
+
},
|
472
|
+
|
473
|
+
_instanceForElement: function(el) {
|
474
|
+
while (el && !el._templateInstance) {
|
475
|
+
el = el.parentNode;
|
476
|
+
}
|
477
|
+
return el._templateInstance;
|
478
|
+
},
|
479
|
+
|
480
|
+
/**
|
481
|
+
* Returns the item associated with a given element stamped by
|
482
|
+
* this `x-repeat`.
|
483
|
+
*/
|
484
|
+
itemForElement: function(el) {
|
485
|
+
var instance = this._instanceForElement(el);
|
486
|
+
return instance && instance.item;
|
487
|
+
},
|
488
|
+
|
489
|
+
/**
|
490
|
+
* Returns the `Polymer.Collection` key associated with a given
|
491
|
+
* element stamped by this `x-repeat`.
|
492
|
+
*/
|
493
|
+
keyForElement: function(el) {
|
494
|
+
var instance = this._instanceForElement(el);
|
495
|
+
return instance && instance.key;
|
496
|
+
},
|
497
|
+
|
498
|
+
/**
|
499
|
+
* Returns the index in `items` associated with a given element
|
500
|
+
* stamped by this `x-repeat`.
|
501
|
+
*/
|
502
|
+
indexForElement: function(el) {
|
503
|
+
var instance = this._instanceForElement(el);
|
504
|
+
return this.rows.indexOf(instance);
|
505
|
+
}
|
506
|
+
|
507
|
+
});
|
508
|
+
|
509
|
+
|
510
|
+
</script>
|