From c91b9d19dbffcf827b7921bb38c955405689df3c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 6 Oct 2017 16:42:31 -0700 Subject: [PATCH] Better attribute suppport * Moves _propertyToAttribute to PropertiesChanged * PropertiesMixin supports `attribute` in the `properties` object to define an attribute name. --- lib/mixins/properties-changed.html | 43 +++++++++++++++++++++++-- lib/mixins/properties-mixin.html | 44 +++++++++++++++++++++----- lib/mixins/property-accessors.html | 50 +++--------------------------- 3 files changed, 80 insertions(+), 57 deletions(-) diff --git a/lib/mixins/properties-changed.html b/lib/mixins/properties-changed.html index 80b595c4ad..c7bce993ba 100644 --- a/lib/mixins/properties-changed.html +++ b/lib/mixins/properties-changed.html @@ -117,6 +117,7 @@ this.__dataPending = null; this.__dataOld = null; this.__dataInstanceProps = null; + this.__serializing = false; this._initializeProperties(); } @@ -356,9 +357,45 @@ * returned from `_typeForProperty` */ _attributeToProperty(attribute, value, type) { - const property = this._propertyForAttribute(attribute); - this[property] = this._deserializeValue(value, type || - this._typeForProperty(property)); + if (!this.__serializing) { + const property = this._propertyForAttribute(attribute); + this[property] = this._deserializeValue(value, type || + this._typeForProperty(property)); + } + } + + /** + * Serializes a property to its associated attribute. + * + * @suppress {invalidCasts} Closure can't figure out `this` is an element. + * + * @param {string} property Property name to reflect. + * @param {string=} attribute Attribute name to reflect to. + * @param {*=} value Property value to refect. + */ + _propertyToAttribute(property, attribute, value) { + this.__serializing = true; + value = (arguments.length < 3) ? this[property] : value; + this._valueToNodeAttribute(/** @type {!HTMLElement} */(this), value, + attribute || this._attributeForProperty(property)); + this.__serializing = false; + } + + /** + * Sets a typed value to an HTML attribute on a node. + * + * If the value is `undefined`, the attribute will be removed. + * + * @param {Element} node Element to set attribute to. + * @param {*} value Value to serialize. + * @param {string} attribute Attribute name to serialize to. + */ + _valueToNodeAttribute(node, value, attribute) { + if (!value && value !== '' && value !== 0) { + node.removeAttribute(attribute); + } else { + node.setAttribute(attribute, value); + } } /** diff --git a/lib/mixins/properties-mixin.html b/lib/mixins/properties-mixin.html index 06c6359402..298ac04e57 100644 --- a/lib/mixins/properties-mixin.html +++ b/lib/mixins/properties-mixin.html @@ -42,6 +42,10 @@ */ const base = Polymer.PropertiesChanged(superClass); + function BooleanAttribute(value) { + return value !== null; + } + /** * @polymer * @mixinClass @@ -51,21 +55,33 @@ */ class Properties extends base { + static get BooleanAttribute() { + return BooleanAttribute; + } + /** * Implements standard custom elements getter to observes the attributes * listed in `properties`. */ static get observedAttributes() { - const props = this.properties; + this._ensurePropertyInfo(); + const props = this.prototype.__propertyInfo; return props ? Object.keys(props).map(p => { return this.prototype._attributeForProperty(p); }) : []; } + _propertyForAttribute(name) { + return this.__attributeInfo[name] || name; + } + + _attributeForProperty(name) { + const info = this.__propertyInfo[name]; + return info && info.attribute || name.toLowerCase(); + } + static _ensureFinalized(name) { - const proto = this.prototype; - if (!proto.hasOwnProperty('__finalized')) { - proto.__finalized = true; + if (!this.prototype.hasOwnProperty('__finalized')) { this.finalize(name); } } @@ -77,13 +93,25 @@ * @param {string} name Name of the element */ static finalize(name) { // eslint-disable-line no-unused-vars - const props = this.properties; + this.prototype.__finalized = true; + this._ensurePropertyInfo(); + const props = this.prototype.__propertyInfo; if (props) { - this.prototype.__propertyInfo = props; this.createProperties(Object.keys(props)); } } + static _ensurePropertyInfo() { + let proto = this.prototype; + if (!proto.hasOwnProperty('__propertyInfo')) { + const props = this.prototype.__propertyInfo = this.properties; + const attrInfo = this.prototype.__attributeInfo = {}; + for (let prop in props) { + attrInfo[proto._attributeForProperty(prop)] = prop; + } + } + } + /** * Overrides implementation in PropertiesChanged to immediately process * any pending changes to properties and ensure that @@ -116,8 +144,8 @@ * @protected */ _typeForProperty(name) { - const props = this.__propertyInfo; - return props && props[name]; + const info = this.__propertyInfo[name]; + return info.type || info; } /** diff --git a/lib/mixins/property-accessors.html b/lib/mixins/property-accessors.html index 366fa05d82..6ce056d38f 100644 --- a/lib/mixins/property-accessors.html +++ b/lib/mixins/property-accessors.html @@ -127,8 +127,6 @@ constructor() { super(); - /** @type {boolean} */ - this.__serializing; /** @type {number} */ this.__dataCounter; } @@ -209,44 +207,9 @@ } /** - * Deserializes an attribute to its associated property. - * - * This method calls the `_deserializeValue` method to convert the string to - * a typed value. - * - * @param {string} attribute Name of attribute to deserialize. - * @param {?string} value of the attribute. - * @param {*=} type type to deserialize to. - */ - _attributeToProperty(attribute, value, type) { - // Don't deserialize back to property if currently reflecting - if (!this.__serializing) { - super._attributeToProperty(attribute, value, type); - } - } - - /** - * Serializes a property to its associated attribute. - * - * @suppress {invalidCasts} Closure can't figure out `this` is an element. - * - * @param {string} property Property name to reflect. - * @param {string=} attribute Attribute name to reflect. - * @param {*=} value Property value to refect. - */ - _propertyToAttribute(property, attribute, value) { - this.__serializing = true; - value = (arguments.length < 3) ? this[property] : value; - this._valueToNodeAttribute(/** @type {!HTMLElement} */(this), value, - attribute || this._attributeForProperty(property)); - this.__serializing = false; - } - - /** - * Sets a typed value to an HTML attribute on a node. - * - * This method calls the `_serializeValue` method to convert the typed - * value to a string. If the `_serializeValue` method returns `undefined`, + * Overrides PropertiesChanged implementation to calls the + * `_serializeValue` method to convert the typed value to a string. + * If the `_serializeValue` method returns `undefined`, * the attribute will be removed (this is the default for boolean * type `false`). * @@ -255,12 +218,7 @@ * @param {string} attribute Attribute name to serialize to. */ _valueToNodeAttribute(node, value, attribute) { - let str = this._serializeValue(value); - if (str === undefined) { - node.removeAttribute(attribute); - } else { - node.setAttribute(attribute, str); - } + super._valueToNodeAttribute(node, this._serializeValue(value), attribute); } /**