Skip to content

Commit

Permalink
Implement basic-element with properties-changed
Browse files Browse the repository at this point in the history
Moves basic property creation and attribute deserialization into properties changed to make it suitable as a base for basic-element.
  • Loading branch information
Steven Orvell committed Sep 16, 2017
1 parent b8fd241 commit d26955b
Show file tree
Hide file tree
Showing 6 changed files with 585 additions and 165 deletions.
70 changes: 38 additions & 32 deletions lib/elements/basic-element.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<link rel="import" href="../utils/boot.html">
<link rel="import" href="../utils/mixin.html">
<link rel="import" href="../mixins/property-accessors.html">
<link rel="import" href="../mixins/properties-changed.html">

<script>
(function() {
Expand All @@ -24,33 +24,33 @@
* @polymer
* @memberof Polymer
* @constructor
* @implements {Polymer_PropertyAccessors}
* @implements {Polymer_PropertiesChanged}
* @extends HTMLElement
* @appliesMixin Polymer.PropertyAccessors
* @appliesMixin Polymer.PropertiesChanged
* @summary Custom element base class that provides minimal starting point
* using PropertyAccessors to create properties
* using PropertiesChanged to create properties
*/
class BasicElement extends Polymer.PropertyAccessors(HTMLElement) {

static _ensureFinalized(name) {
const proto = this.prototype;
if (!proto.hasOwnProperty('__finalized')) {
proto.__finalized = true;
this.finalize(name);
}
}
class BasicElement extends Polymer.PropertiesChanged(HTMLElement) {

/**
* Implements standard custom elements getter to observes the attributes
* listed in `properties`. These are camel to dash cased.
* listed in `properties`.
*/
static get observedAttributes() {
const props = this.properties;
return props ? Object.keys(props).map(p => {
return this.prototype._attributeNameForProperty(p);
return this.prototype._attributeForProperty(p);
}) : [];
}

static _ensureFinalized(name) {
const proto = this.prototype;
if (!proto.hasOwnProperty('__finalized')) {
proto.__finalized = true;
this.finalize(name);
}
}

/**
* Finalizes an element definition. This includes ensuring property
* accessors exist on the element prototype and parsing the element
Expand All @@ -65,6 +65,19 @@
}
}

/**
* 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();
}

/**
* Overrides default behavior and adds a call to `finalize` which lazily
* configures the element's property accessors.
Expand All @@ -76,29 +89,22 @@
}

/**
* Overrides default behavior to use `type` information stored in the
* static `properties` object. This allows attributes to be converted to
* the coressponding type given in `properties`. Atributes are dash to
* camel cased.
* Overrides PropertiesChanged method to return type specified in the
* static `properties` object for the given property.
* @param {string} name Name of property
* @return {*} Type to which to deserialize attribute
*
* @param {string} attribute Name of attribute to deserialize.
* @param {?string} value of the attribute.
* @param {*=} type type to deserialize to.
* @protected
*/
_attributeToProperty(attribute, value, type) {
if (!type) {
const property = this._propertyNameForAttribute(attribute);
const props = this.__propertyInfo;
const info = props && props[property];
type = info && info.type || info;
}
super._attributeToProperty(attribute, value, type);
_typeForProperty(name) {
const props = this.__propertyInfo;
return props && props[name];
}

/**
* Called when the element is added to a document.
* Calls `_enableProperties` to turn on property system from
* `PropertyAccessors`.
* `PropertiesChanged`.
*/
connectedCallback() {
this._enableProperties();
Expand All @@ -112,7 +118,7 @@
}
/**
* @constructor
* @implements {Polymer_PropertyAccessors}
* @implements {Polymer_PropertiesChanged}
* @extends {HTMLElement}
*/
Polymer.BasicElement = BasicElement;
Expand Down
149 changes: 148 additions & 1 deletion lib/mixins/properties-changed.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,61 @@

return class PropertiesChanged extends superClass {

/**
* Creates property accessors for the given property names.
* @param {array} props Array of names of accessors.
*/
static createProperties(props) {
for (let i=0; i < props.length; i++) {
this.prototype._createPropertyAccessor(props[i]);
}
}

/**
* Creates a setter/getter pair for the named property with its own
* local storage. The getter returns the value in the local storage,
* and the setter calls `_setProperty`, which updates the local storage
* for the property and enqueues a `_propertiesChanged` callback.
*
* This method may be called on a prototype or an instance. Calling
* this method may overwrite a property value that already exists on
* the prototype/instance by creating the accessor.
*
* @param {string} property Name of the property
* @param {boolean=} readOnly When true, no setter is created; the
* protected `_setProperty` function must be used to set the property
* @protected
*/
_createPropertyAccessor(property, readOnly) {
if (!this.hasOwnProperty('__dataHasAccessor')) {
this.__dataHasAccessor = Object.assign({}, this.__dataHasAccessor);
}
if (!this.__dataHasAccessor[property]) {
this.__dataHasAccessor[property] = true;
this._generatePropertyAccessor(property, readOnly);
}
}

/**
* Generates a property accessor for the given property.
* @param {string} property Name of the property
* @param {boolean=} readOnly When true, no setter is created
*/
_generatePropertyAccessor(property, readOnly) {
Object.defineProperty(this, property, {
/* eslint-disable valid-jsdoc */
/** @this {PropertiesChanged} */
get: function() {
return this.__data[property];
},
/** @this {PropertiesChanged} */
set: readOnly ? function() {} : function(value) {
this._setProperty(property, value);
}
/* eslint-enable */
});
}

constructor() {
super();
this.__dataEnabled = false;
Expand Down Expand Up @@ -75,12 +130,25 @@
}

/**
* Initializes the local storage for property accessors.
*
* Provided as an override point for performing any setup work prior
* to initializing the property accessor system.
*
* @protected
*/
_initializeProperties() {}
_initializeProperties() {
// Capture instance properties; these will be set into accessors
// during first flush. Don't set them here, since we want
// these to overwrite defaults/constructor assignments
for (let p in this.__dataHasAccessor) {
if (this.hasOwnProperty(p)) {
this.__dataInstanceProps = this.__dataInstanceProps || {};
this.__dataInstanceProps[p] = this[p];
delete this[p];
}
}
}

/**
* Called at ready time with bag of instance properties that overwrote
Expand Down Expand Up @@ -254,6 +322,85 @@
);
}

/**
* 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
*/
attributeChangedCallback(name, old, value) {
if (old !== value) {
this._attributeToProperty(name, value);
}
}

/**
* 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, defaults to the value
* returned from `_typeForProperty`
*/
_attributeToProperty(attribute, value, type) {
const property = this._propertyForAttribute(attribute);
this[property] = this._deserializeValue(value, type ||
this._typeForProperty(property));
}

/**
* Converts a string to a typed JavaScript value.
*
* This method is called when reading HTML attribute values to
* JS properties. Users may override this method to provide
* deserialization for custom `type`s.
*
* @param {?string} value Value to deserialize.
* @param {*=} type Type to deserialize the string to.
* @return {*} Typed value deserialized from the provided string.
*/
_deserializeValue(value, type) {
return typeof type == 'function' ? type(value) : value;
}

/**
* Returns a property name that corresponds to the given attribute.
* By default, converts dash to camel case, e.g. `foo-bar` to `fooBar`.
* @param {string} attribute Attribute to convert
* @return {string} Property name corresponding to the given attribute.
*
* @protected
*/
_propertyForAttribute(attribute) {
return attribute;
}

/**
* Returns an attribute name that corresponds to the given property.
* By default, converts camel to dash case, e.g. `fooBar` to `foo-bar`.
* @param {string} property Property to convert
* @return {string} Attribute name corresponding to the given property.
*
* @protected
*/
_attributeForProperty(property) {
return property;
}

/**
* Override point to provide a type to which to deserialize a value to
* a given property.
* @param {string} name Name of property
*
* @protected
*/
_typeForProperty(name) {} //eslint-disable-line no-unused-vars

};

});
Expand Down
Loading

0 comments on commit d26955b

Please sign in to comment.