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