UNPKG

webdash-readme-preview

Version:
377 lines (350 loc) 14.2 kB
<!-- @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) { 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>