diff --git a/lib/mixins/properties-mixin.html b/lib/mixins/properties-mixin.html
index 298ac04e57..d0d76ee375 100644
--- a/lib/mixins/properties-mixin.html
+++ b/lib/mixins/properties-mixin.html
@@ -46,6 +46,41 @@
return value !== null;
}
+ /**
+ * Mixes `moreProps` into `props` but upgrades shorthand type
+ * syntax to { type: Type}.
+ *
+ * @param {Object} props Bag to collect flattened properties into
+ * @param {Object} moreProps Bag of properties to add to `props`
+ * @return {Object} The input `props` bag
+ * @private
+ */
+ function mixProperties(props, moreProps) {
+ for (let p in moreProps) {
+ let o = moreProps[p];
+ if (typeof o == 'function') {
+ o = { type: o };
+ }
+ props[p] = o;
+ }
+ return props;
+ }
+
+ /**
+ * Returns the super class constructor for the given class, if it is an
+ * instance of the PropertiesClass.
+ *
+ * @param {PropertiesClassConstructor} ctor PropertiesClass constructor
+ * @returns {PropertiesClassConstructor} Super class constructor
+ */
+ function superForClass(ctor) {
+ const proto = /** @type {PropertiesClassConstructor} */ (ctor).prototype;
+ const superCtor = Object.getPrototypeOf(proto).constructor;
+ if (superCtor.prototype instanceof PropertiesClass) {
+ return superCtor;
+ }
+ }
+
/**
* @polymer
* @mixinClass
@@ -53,7 +88,7 @@
* @implements {Polymer_PropertiesMixin}
* @unrestricted
*/
- class Properties extends base {
+ class PropertiesClass extends base {
static get BooleanAttribute() {
return BooleanAttribute;
@@ -64,75 +99,96 @@
* listed in `properties`.
*/
static get observedAttributes() {
- this._ensurePropertyInfo();
- const props = this.prototype.__propertyInfo;
+ const props = this._properties;
return props ? Object.keys(props).map(p => {
return this.prototype._attributeForProperty(p);
}) : [];
}
- _propertyForAttribute(name) {
- return this.__attributeInfo[name] || name;
+ /**
+ * Finalizes an element definition, including ensuring any super classes
+ * are also finalized. This includes ensuring property
+ * accessors exist on the element prototype. This method calls
+ * `_finalizeClass` to finalize each constructor in the prototype chain.
+ * @param {string} name Name of the element
+ */
+ static finalize() { // eslint-disable-line no-unused-vars
+ if (!this.hasOwnProperty(JSCompiler_renameProperty('__finalized', this))) {
+ const superCtor = superForClass(this);
+ if (superCtor) {
+ superCtor.finalize();
+ }
+ this.__finalized = true;
+ this._finalizeClass();
+ }
}
- _attributeForProperty(name) {
- const info = this.__propertyInfo[name];
- return info && info.attribute || name.toLowerCase();
+ /**
+ * Finalize an element class. This includes ensuring property
+ * accessors exist on the element prototype. This method is called by
+ * `finalize` and finalizes the class constructor.
+ *
+ * @protected
+ */
+ static _finalizeClass() {
+ const props = this._ownProperties;
+ if (props) {
+ this.createProperties(props);
+ }
}
- static _ensureFinalized(name) {
- if (!this.prototype.hasOwnProperty('__finalized')) {
- this.finalize(name);
+ /**
+ * Returns a memoized version of the `properties` object. Properties
+ * not in object format are converted to at lesat {type}.
+ *
+ * @return {Object} Object containing own properties for this class
+ * @protected
+ */
+ static get _ownProperties() {
+ if (!this.hasOwnProperty(JSCompiler_renameProperty('__ownProperties', this))) {
+ const props = this.properties;
+ this.__ownProperties = props ? mixProperties({}, props) : null;
}
+ return this.__ownProperties;
}
/**
- * Finalizes an element definition. This includes ensuring property
- * accessors exist on the element prototype and parsing the element
- * template.
- * @param {string} name Name of the element
+ * Returns a memoized version of all properties, including those inherited
+ * from super classes. Properties not in object format are converted to
+ * at lesat {type}.
+ *
+ * @return {Object} Object containing properties for this class
+ * @protected
*/
- static finalize(name) { // eslint-disable-line no-unused-vars
- this.prototype.__finalized = true;
- this._ensurePropertyInfo();
- const props = this.prototype.__propertyInfo;
- if (props) {
- this.createProperties(Object.keys(props));
+ static get _properties() {
+ if (!this.hasOwnProperty(JSCompiler_renameProperty('__properties', this))) {
+ const superCtor = superForClass(this);
+ this.__properties = Object.assign({},
+ superCtor && superCtor._properties, this._ownProperties);
}
+ return this.__properties;
}
- static _ensurePropertyInfo() {
- let proto = this.prototype;
- if (!proto.hasOwnProperty('__propertyInfo')) {
- const props = this.prototype.__propertyInfo = this.properties;
- const attrInfo = this.prototype.__attributeInfo = {};
+ static get _attributeInfo() {
+ if (!this.hasOwnProperty(JSCompiler_renameProperty('__attributeInfo', this))) {
+ const proto = this.prototype;
+ const attrInfo = this.__attributeInfo = {};
+ const props = this._properties;
for (let prop in props) {
attrInfo[proto._attributeForProperty(prop)] = prop;
}
}
+ return this.__attributeInfo;
}
- /**
- * Overrides implementation in PropertiesChanged to immediately process
- * any pending changes to properties and ensure that
- * `_propertiesChanged` is called.
- *
- * @public
- */
- ready() {
- super.ready();
- this._invalidateProperties();
- this._validateProperties();
+ _propertyForAttribute(name) {
+ return this.constructor._attributeInfo[name] ||
+ super._propertyForAttribute(name);
}
- /**
- * Overrides default behavior and adds a call to `finalize` which lazily
- * configures the element's property accessors.
- * @override
- */
- _initializeProperties() {
- this.constructor._ensureFinalized(this.localName);
- super._initializeProperties();
+ _attributeForProperty(name) {
+ const info = this.constructor._properties[name];
+ return info && info.attribute || super._attributeForProperty(name);
}
/**
@@ -144,10 +200,20 @@
* @protected
*/
_typeForProperty(name) {
- const info = this.__propertyInfo[name];
+ const info = this.constructor._properties[name];
return info.type || info;
}
+ /**
+ * Overrides default behavior and adds a call to `finalize` which lazily
+ * configures the element's property accessors.
+ * @override
+ */
+ _initializeProperties() {
+ this.constructor.finalize(this.localName);
+ super._initializeProperties();
+ }
+
/**
* Called when the element is added to a document.
* Calls `_enableProperties` to turn on property system from
@@ -164,7 +230,7 @@
}
- return Properties;
+ return PropertiesClass;
});
diff --git a/test/unit/polymer.properties-element-with-property-accessors.html b/test/unit/polymer.properties-element-with-property-accessors.html
index f72042bdcf..7d55b9ec53 100644
--- a/test/unit/polymer.properties-element-with-property-accessors.html
+++ b/test/unit/polymer.properties-element-with-property-accessors.html
@@ -77,9 +77,9 @@
class SubElement extends window.MyElement {
static get properties() {
- return Object.assign({
+ return {
prop2: String
- }, super.properties);
+ };
}
constructor() {
@@ -118,9 +118,9 @@
return class extends Base {
static get properties() {
- return Object.assign({
+ return {
mixin: Number
- }, super.properties);
+ };
}
constructor() {
@@ -147,10 +147,10 @@
class SubMixinElement extends MyMixin(window.SubElement) {
static get properties() {
- return Object.assign({
+ return {
prop3: String,
camelCase: String
- }, super.properties);
+ };
}
constructor() {
diff --git a/test/unit/polymer.properties-element.html b/test/unit/polymer.properties-element.html
index d3cc759b96..f73b36f4ca 100644
--- a/test/unit/polymer.properties-element.html
+++ b/test/unit/polymer.properties-element.html
@@ -75,9 +75,9 @@
class SubElement extends window.MyElement {
static get properties() {
- return Object.assign({
+ return {
prop2: String
- }, super.properties);
+ }
}
constructor() {
@@ -115,9 +115,9 @@
return class extends Base {
static get properties() {
- return Object.assign({
+ return {
mixin: Number
- }, super.properties);
+ };
}
constructor() {
@@ -143,10 +143,10 @@
class SubMixinElement extends MyMixin(window.SubElement) {
static get properties() {
- return Object.assign({
+ return {
prop3: String,
camelCase: String
- }, super.properties);
+ };
}
constructor() {
@@ -233,7 +233,7 @@
});
test('attributes', function() {
- var fixtureEl = fixture('my-element-attr');
+ const fixtureEl = fixture('my-element-attr');
assert.equal(fixtureEl.prop, 'attr');
assert.equal(fixtureEl._callAttributeChangedCallback, 1);
});
@@ -242,7 +242,7 @@
suite('subclass', function() {
- var el;
+ let el;
setup(function() {
el = document.createElement('sub-element');