Skip to content

Commit

Permalink
PropertiesMixin
Browse files Browse the repository at this point in the history
* automatically finalizes superclasses.
* automatically mixes superclass properties.
  • Loading branch information
Steven Orvell committed Nov 11, 2017
1 parent 5846d58 commit 3c50f44
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 61 deletions.
160 changes: 113 additions & 47 deletions lib/mixins/properties-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,49 @@
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
* @extends {base}
* @implements {Polymer_PropertiesMixin}
* @unrestricted
*/
class Properties extends base {
class PropertiesClass extends base {

static get BooleanAttribute() {
return BooleanAttribute;
Expand All @@ -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);
}

/**
Expand All @@ -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
Expand All @@ -164,7 +230,7 @@

}

return Properties;
return PropertiesClass;

});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@
class SubElement extends window.MyElement {

static get properties() {
return Object.assign({
return {
prop2: String
}, super.properties);
};
}

constructor() {
Expand Down Expand Up @@ -118,9 +118,9 @@
return class extends Base {

static get properties() {
return Object.assign({
return {
mixin: Number
}, super.properties);
};
}

constructor() {
Expand All @@ -147,10 +147,10 @@
class SubMixinElement extends MyMixin(window.SubElement) {

static get properties() {
return Object.assign({
return {
prop3: String,
camelCase: String
}, super.properties);
};
}

constructor() {
Expand Down
16 changes: 8 additions & 8 deletions test/unit/polymer.properties-element.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@
class SubElement extends window.MyElement {

static get properties() {
return Object.assign({
return {
prop2: String
}, super.properties);
}
}

constructor() {
Expand Down Expand Up @@ -115,9 +115,9 @@
return class extends Base {

static get properties() {
return Object.assign({
return {
mixin: Number
}, super.properties);
};
}

constructor() {
Expand All @@ -143,10 +143,10 @@
class SubMixinElement extends MyMixin(window.SubElement) {

static get properties() {
return Object.assign({
return {
prop3: String,
camelCase: String
}, super.properties);
};
}

constructor() {
Expand Down Expand Up @@ -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);
});
Expand All @@ -242,7 +242,7 @@

suite('subclass', function() {

var el;
let el;

setup(function() {
el = document.createElement('sub-element');
Expand Down

0 comments on commit 3c50f44

Please sign in to comment.