From 7497065a08c3a11de9fd25e6c7adab5b5aa5d8b1 Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Thu, 11 May 2017 14:08:36 -0700 Subject: [PATCH] Add static API for creating property fx and minor code refactoring. --- lib/mixins/property-effects.html | 179 ++++++++++++++++++++++++++++--- lib/mixins/template-stamp.html | 5 +- 2 files changed, 169 insertions(+), 15 deletions(-) diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html index 129cd73eb8..48a0084f0f 100644 --- a/lib/mixins/property-effects.html +++ b/lib/mixins/property-effects.html @@ -1026,21 +1026,23 @@ * Element class mixin that provides meta-programming for Polymer's template * binding and data observation (collectively, "property effects") system. * - * This mixin uses provides the following key methods for adding property effects - * to this element: - * - `_createPropertyObserver` - * - `_createMethodObserver` - * - `_createNotifyingProperty` - * - `_createReadOnlyProperty` - * - `_createReflectedProperty` - * - `_createComputedProperty` - * - `_bindTemplate` + * This mixin uses provides the following key static methods for adding + * property effects to an element class: + * - `createPropertyEffect` + * - `createPropertyObserver` + * - `createMethodObserver` + * - `createNotifyingProperty` + * - `createReadOnlyProperty` + * - `createReflectedProperty` + * - `createComputedProperty` + * - `bindTemplate` * * Each method creates one or more property accessors, along with metadata * used by this mixin's implementation of `_propertiesChanged` to perform - * the property effects. These methods may be called on element instances, - * but are designed to be called on element prototypes such that the work to - * set up accessors and effect metadata are done once per element class. + * the property effects. + * + * Underscored versions of the above methods also exist on the element + * prototype for adding property effects on instances at runtime. * * Note that this mixin overrides several `PropertyAccessors` methods, in * many cases to maintain guarantees provided by the Polymer 1.x features; @@ -2004,6 +2006,150 @@ createMethodEffect(this, sig, TYPES.COMPUTE, runComputedEffect, property, dynamicFns); } + // -- static class methods ------------ + + /** + * Ensures an accessor exists for the specified property, and adds + * to a list of "property effects" that will run when the accessor for + * the specified property is set. Effects are grouped by "type", which + * roughly corresponds to a phase in effect processing. The effect + * metadata should be in the following form: + * + * { + * fn: effectFunction, // Reference to function to call to perform effect + * info: { ... } // Effect metadata passed to function + * trigger: { // Optional triggering metadata; if not provided + * name: string // the property is treated as a wildcard + * structured: boolean + * wildcard: boolean + * } + * } + * + * Effects are called from `_propertiesChanged` in the following order by + * type: + * + * 1. COMPUTE + * 2. PROPAGATE + * 3. REFLECT + * 4. OBSERVE + * 5. NOTIFY + * + * Effect functions are called with the following signature: + * + * effectFunction(inst, path, props, oldProps, info, hasPaths) + * + * @param {string} property Property that should trigger the effect + * @param {string} type Effect type, from this.PROPERTY_EFFECT_TYPES + * @param {Object=} effect Effect metadata object + * @protected + */ + static addPropertyEffect(property, type, effect) { + this.prototype._addPropertyEffect(property, type, effect); + } + + /** + * Creates a single-property observer for the given property. + * + * @param {string} property Property name + * @param {string} methodName Name of observer method to call + * @param {boolean=} dynamicFn Whether the method name should be included as + * a dependency to the effect. + * @protected + */ + static createPropertyObserver(property, methodName, dynamicFn) { + this.prototype._createPropertyObserver(property, methodName, dynamicFn); + } + + /** + * Creates a multi-property "method observer" based on the provided + * expression, which should be a string in the form of a normal Javascript + * function signature: `'methodName(arg1, [..., argn])'`. Each argument + * should correspond to a property or path in the context of this + * prototype (or instance), or may be a literal string or number. + * + * @param {string} expression Method expression + * @param {Object=} dynamicFns Map indicating whether method names should + * be included as a dependency to the effect. + * @protected + */ + static createMethodObserver(expression, dynamicFns) { + this.prototype._createMethodObserver(expression, dynamicFns); + } + + /** + * Causes the setter for the given property to dispatch `-changed` + * events to notify of changes to the property. + * + * @param {string} property Property name + * @protected + */ + static createNotifyingProperty(property) { + this.prototype._createNotifyingProperty(property); + } + + /** + * Creates a read-only accessor for the given property. + * + * To set the property, use the protected `_setProperty` API. + * To create a custom protected setter (e.g. `_setMyProp()` for + * property `myProp`), pass `true` for `protectedSetter`. + * + * Note, if the property will have other property effects, this method + * should be called first, before adding other effects. + * + * @param {string} property Property name + * @param {boolean=} protectedSetter Creates a custom protected setter + * when `true`. + * @protected + */ + static createReadOnlyProperty(property, protectedSetter) { + this.prototype._createReadOnlyProperty(property, protectedSetter); + } + + /** + * Causes the setter for the given property to reflect the property value + * to a (dash-cased) attribute of the same name. + * + * @param {string} property Property name + * @protected + */ + static createReflectedProperty(property) { + this.prototype._createReflectedProperty(property); + } + + /** + * Creates a computed property whose value is set to the result of the + * method described by the given `expression` each time one or more + * arguments to the method changes. The expression should be a string + * in the form of a normal Javascript function signature: + * `'methodName(arg1, [..., argn])'` + * + * @param {string} property Name of computed property to set + * @param {string} expression Method expression + * @param {Object=} dynamicFns Map indicating whether method names should + * be included as a dependency to the effect. + * @protected + */ + static createComputedProperty(property, expression, dynamicFns) { + this.prototype._createComputedProperty(property, expression, dynamicFns); + } + + /** + * Parses the provided template to ensure binding effects are created + * for them, and then ensures property accessors are created for any + * dependent properties in the template. Binding effects for bound + * templates are stored in a linked list on the instance so that + * templates can be efficiently stamped and unstamped. + * + * @param {HTMLTemplateElement} template Template containing binding + * bindings + * @return {Object} Template metadata object + * @protected + */ + static bindTemplate(template) { + return this.prototype._bindTemplate(template); + } + // -- binding ---------------------------------------------- /** @@ -2046,8 +2192,10 @@ templateInfo = Object.create(templateInfo); templateInfo.wasPreBound = wasPreBound; if (!wasPreBound && this.__templateInfo) { - templateInfo.nextTemplateInfo = this.__templateInfo; - this.__templateInfo.previousTemplateInfo = templateInfo; + let last = this.__templateInfoLast || this.__templateInfo; + this.__templateInfoLast = last.nextTemplateInfo = templateInfo; + templateInfo.previousTemplateInfo = last; + return templateInfo; } } return this.__templateInfo = templateInfo; @@ -2134,6 +2282,9 @@ templateInfo.nextTemplateInfo.previousTemplateInfo = templateInfo.previousTemplateInfo; } + if (this.__templateInfoLast == templateInfo) { + this.__templateInfoLast = templateInfo.previousTemplateInfo; + } // Remove stamped nodes let nodes = templateInfo.childNodes; for (let i=0; i