-
Notifications
You must be signed in to change notification settings - Fork 2k
/
class.html
377 lines (351 loc) · 13.9 KB
/
class.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
<!--
@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="legacy-element-mixin.html">
<script>
(function() {
'use strict';
let metaProps = {
attached: true,
detached: true,
ready: true,
created: true,
beforeRegister: true,
registered: true,
attributeChanged: true,
// meta objects
behaviors: true
};
/**
* Applies a "legacy" behavior or array of behaviors to the provided class.
*
* Note: this method will automatically also apply the `Polymer.LegacyElementMixin`
* to ensure that any legacy behaviors can rely on legacy Polymer API on
* the underlying element.
*
* @template T
* @param {!Object|!Array<!Object>} behaviors Behavior object or array of behaviors.
* @param {function(new:T)} klass Element class.
* @return {function(new:T)} Returns a new Element class extended by the
* passed in `behaviors` and also by `Polymer.LegacyElementMixin`.
* @memberof Polymer
* @suppress {invalidCasts, checkTypes}
*/
function mixinBehaviors(behaviors, klass) {
if (!behaviors) {
klass = /** @type {HTMLElement} */(klass); // eslint-disable-line no-self-assign
return klass;
}
// NOTE: ensure the behavior is extending a class with
// legacy element api. This is necessary since behaviors expect to be able
// to access 1.x legacy api.
klass = Polymer.LegacyElementMixin(klass);
if (!Array.isArray(behaviors)) {
behaviors = [behaviors];
}
let superBehaviors = klass.prototype.behaviors;
// get flattened, deduped list of behaviors *not* already on super class
behaviors = flattenBehaviors(behaviors, null, superBehaviors);
// mixin new behaviors
klass = _mixinBehaviors(behaviors, klass);
if (superBehaviors) {
behaviors = superBehaviors.concat(behaviors);
}
// Set behaviors on prototype for BC...
klass.prototype.behaviors = behaviors;
return klass;
}
// NOTE:
// 1.x
// Behaviors were mixed in *in reverse order* and de-duped on the fly.
// The rule was that behavior properties were copied onto the element
// prototype if and only if the property did not already exist.
// Given: Polymer{ behaviors: [A, B, C, A, B]}, property copy order was:
// (1), B, (2), A, (3) C. This means prototype properties win over
// B properties win over A win over C. This mirrors what would happen
// with inheritance if element extended B extended A extended C.
//
// Again given, Polymer{ behaviors: [A, B, C, A, B]}, the resulting
// `behaviors` array was [C, A, B].
// Behavior lifecycle methods were called in behavior array order
// followed by the element, e.g. (1) C.created, (2) A.created,
// (3) B.created, (4) element.created. There was no support for
// super, and "super-behavior" methods were callable only by name).
//
// 2.x
// Behaviors are made into proper mixins which live in the
// element's prototype chain. Behaviors are placed in the element prototype
// eldest to youngest and de-duped youngest to oldest:
// So, first [A, B, C, A, B] becomes [C, A, B] then,
// the element prototype becomes (oldest) (1) Polymer.Element, (2) class(C),
// (3) class(A), (4) class(B), (5) class(Polymer({...})).
// Result:
// This means element properties win over B properties win over A win
// over C. (same as 1.x)
// If lifecycle is called (super then me), order is
// (1) C.created, (2) A.created, (3) B.created, (4) element.created
// (again same as 1.x)
function _mixinBehaviors(behaviors, klass) {
for (let i=0; i<behaviors.length; i++) {
let b = behaviors[i];
if (b) {
klass = Array.isArray(b) ? _mixinBehaviors(b, klass) :
GenerateClassFromInfo(b, klass);
}
}
return klass;
}
/**
* @param {Array} behaviors List of behaviors to flatten.
* @param {Array=} list Target list to flatten behaviors into.
* @param {Array=} exclude List of behaviors to exclude from the list.
* @return {!Array} Returns the list of flattened behaviors.
*/
function flattenBehaviors(behaviors, list, exclude) {
list = list || [];
for (let i=behaviors.length-1; i >= 0; i--) {
let b = behaviors[i];
if (b) {
if (Array.isArray(b)) {
flattenBehaviors(b, list);
} else {
// dedup
if (list.indexOf(b) < 0 && (!exclude || exclude.indexOf(b) < 0)) {
list.unshift(b);
}
}
} else {
console.warn('behavior is null, check for missing or 404 import');
}
}
return list;
}
/**
* @param {!PolymerInit} info Polymer info object
* @param {function(new:HTMLElement)} Base base class to extend with info object
* @return {function(new:HTMLElement)} Generated class
* @suppress {checkTypes}
* @private
*/
function GenerateClassFromInfo(info, Base) {
/** @private */
class PolymerGenerated extends Base {
static get properties() {
return info.properties;
}
static get observers() {
return info.observers;
}
/**
* @return {HTMLTemplateElement} template for this class
*/
static get template() {
// get template first from any imperative set in `info._template`
return info._template ||
// next look in dom-module associated with this element's is.
Polymer.DomModule && Polymer.DomModule.import(this.is, 'template') ||
// next look for superclass template (note: use superclass symbol
// to ensure correct `this.is`)
Base.template ||
// finally fall back to `_template` in element's prototype.
this.prototype._template ||
null;
}
/**
* @return {void}
*/
created() {
super.created();
if (info.created) {
info.created.call(this);
}
}
/**
* @return {void}
*/
_registered() {
super._registered();
/* NOTE: `beforeRegister` is called here for bc, but the behavior
is different than in 1.x. In 1.0, the method was called *after*
mixing prototypes together but *before* processing of meta-objects.
However, dynamic effects can still be set here and can be done either
in `beforeRegister` or `registered`. It is no longer possible to set
`is` in `beforeRegister` as you could in 1.x.
*/
if (info.beforeRegister) {
info.beforeRegister.call(Object.getPrototypeOf(this));
}
if (info.registered) {
info.registered.call(Object.getPrototypeOf(this));
}
}
/**
* @return {void}
*/
_applyListeners() {
super._applyListeners();
if (info.listeners) {
for (let l in info.listeners) {
this._addMethodEventListenerToNode(this, l, info.listeners[l]);
}
}
}
// note: exception to "super then me" rule;
// do work before calling super so that super attributes
// only apply if not already set.
/**
* @return {void}
*/
_ensureAttributes() {
if (info.hostAttributes) {
for (let a in info.hostAttributes) {
this._ensureAttribute(a, info.hostAttributes[a]);
}
}
super._ensureAttributes();
}
/**
* @return {void}
*/
ready() {
super.ready();
if (info.ready) {
info.ready.call(this);
}
}
/**
* @return {void}
*/
attached() {
super.attached();
if (info.attached) {
info.attached.call(this);
}
}
/**
* @return {void}
*/
detached() {
super.detached();
if (info.detached) {
info.detached.call(this);
}
}
/**
* Implements native Custom Elements `attributeChangedCallback` to
* set an attribute value to a property via `_attributeToProperty`.
*
* @param {string} name Name of attribute that changed
* @param {?string} old Old attribute value
* @param {?string} value New attribute value
* @return {void}
*/
attributeChanged(name, old, value) {
super.attributeChanged(name, old, value);
if (info.attributeChanged) {
info.attributeChanged.call(this, name, old, value);
}
}
}
PolymerGenerated.generatedFrom = info;
for (let p in info) {
// NOTE: cannot copy `metaProps` methods onto prototype at least because
// `super.ready` must be called and is not included in the user fn.
if (!(p in metaProps)) {
let pd = Object.getOwnPropertyDescriptor(info, p);
if (pd) {
Object.defineProperty(PolymerGenerated.prototype, p, pd);
}
}
}
return PolymerGenerated;
}
/**
* Generates a class that extends `Polymer.LegacyElement` based on the
* provided info object. Metadata objects on the `info` object
* (`properties`, `observers`, `listeners`, `behaviors`, `is`) are used
* for Polymer's meta-programming systems, and any functions are copied
* to the generated class.
*
* Valid "metadata" values are as follows:
*
* `is`: String providing the tag name to register the element under. In
* addition, if a `dom-module` with the same id exists, the first template
* in that `dom-module` will be stamped into the shadow root of this element,
* with support for declarative event listeners (`on-...`), Polymer data
* bindings (`[[...]]` and `{{...}}`), and id-based node finding into
* `this.$`.
*
* `properties`: Object describing property-related metadata used by Polymer
* features (key: property names, value: object containing property metadata).
* Valid keys in per-property metadata include:
* - `type` (String|Number|Object|Array|...): Used by
* `attributeChangedCallback` to determine how string-based attributes
* are deserialized to JavaScript property values.
* - `notify` (boolean): Causes a change in the property to fire a
* non-bubbling event called `<property>-changed`. Elements that have
* enabled two-way binding to the property use this event to observe changes.
* - `readOnly` (boolean): Creates a getter for the property, but no setter.
* To set a read-only property, use the private setter method
* `_setProperty(property, value)`.
* - `observer` (string): Observer method name that will be called when
* the property changes. The arguments of the method are
* `(value, previousValue)`.
* - `computed` (string): String describing method and dependent properties
* for computing the value of this property (e.g. `'computeFoo(bar, zot)'`).
* Computed properties are read-only by default and can only be changed
* via the return value of the computing method.
*
* `observers`: Array of strings describing multi-property observer methods
* and their dependent properties (e.g. `'observeABC(a, b, c)'`).
*
* `listeners`: Object describing event listeners to be added to each
* instance of this element (key: event name, value: method name).
*
* `behaviors`: Array of additional `info` objects containing metadata
* and callbacks in the same format as the `info` object here which are
* merged into this element.
*
* `hostAttributes`: Object listing attributes to be applied to the host
* once created (key: attribute name, value: attribute value). Values
* are serialized based on the type of the value. Host attributes should
* generally be limited to attributes such as `tabIndex` and `aria-...`.
* Attributes in `hostAttributes` are only applied if a user-supplied
* attribute is not already present (attributes in markup override
* `hostAttributes`).
*
* In addition, the following Polymer-specific callbacks may be provided:
* - `registered`: called after first instance of this element,
* - `created`: called during `constructor`
* - `attached`: called during `connectedCallback`
* - `detached`: called during `disconnectedCallback`
* - `ready`: called before first `attached`, after all properties of
* this element have been propagated to its template and all observers
* have run
*
* @param {!PolymerInit} info Object containing Polymer metadata and functions
* to become class methods.
* @return {function(new:HTMLElement)} Generated class
* @memberof Polymer
*/
Polymer.Class = function(info) {
if (!info) {
console.warn('Polymer.Class requires `info` argument');
}
let klass = GenerateClassFromInfo(info, info.behaviors ?
// note: mixinBehaviors ensures `LegacyElementMixin`.
mixinBehaviors(info.behaviors, HTMLElement) :
Polymer.LegacyElementMixin(HTMLElement));
// decorate klass with registration info
klass.is = info.is;
return klass;
};
Polymer.mixinBehaviors = mixinBehaviors;
})();
</script>