-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Factor PropertiesChanged out of PropertyAccessors
To improve code hygiene and extensibility, factors the basic batched `_propertiesChanged` mechanism out of `PropertyAccessors` which deals with creating accessors and sync'ing with attributes.
- Loading branch information
Steven Orvell
committed
Sep 15, 2017
1 parent
c7b43f7
commit aa4f186
Showing
3 changed files
with
412 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
<!-- | ||
@license | ||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved. | ||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt | ||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | ||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt | ||
Code distributed by Google as part of the polymer project is also | ||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | ||
--> | ||
|
||
<link rel="import" href="../utils/boot.html"> | ||
<link rel="import" href="../utils/mixin.html"> | ||
<link rel="import" href="../utils/async.html"> | ||
|
||
<script> | ||
(function() { | ||
|
||
'use strict'; | ||
|
||
let microtask = Polymer.Async.microTask; | ||
|
||
/** | ||
* Element class mixin that provides basic meta-programming for creating one | ||
* or more property accessors (getter/setter pair) that enqueue an async | ||
* (batched) `_propertiesChanged` callback. | ||
* | ||
* For basic usage of this mixin, simply declare attributes to observe via | ||
* the standard `static get observedAttributes()`, implement `_propertiesChanged` | ||
* on the class, and then call `MyClass.createPropertiesForAttributes()` once | ||
* on the class to generate property accessors for each observed attribute | ||
* prior to instancing. Last, call `this._enableProperties()` in the element's | ||
* `connectedCallback` to enable the accessors. | ||
* | ||
* Any `observedAttributes` will automatically be | ||
* deserialized via `attributeChangedCallback` and set to the associated | ||
* property using `dash-case`-to-`camelCase` convention. | ||
* | ||
* @mixinFunction | ||
* @polymer | ||
* @memberof Polymer | ||
* @summary Element class mixin for reacting to property changes from | ||
* generated property accessors. | ||
*/ | ||
Polymer.PropertiesChanged = Polymer.dedupingMixin(superClass => { | ||
|
||
return class PropertiesChanged extends superClass { | ||
|
||
constructor() { | ||
super(); | ||
this.__dataEnabled = false; | ||
this.__dataReady = false; | ||
this.__dataInvalid = false; | ||
this.__data = {}; | ||
this.__dataPending = null; | ||
this.__dataOld = null; | ||
this.__dataInstanceProps = null; | ||
this._initializeProperties(); | ||
} | ||
|
||
/** | ||
* Lifecycle callback called when properties are enabled via | ||
* `_enableProperties`. | ||
* | ||
* Users may override this function to implement behavior that is | ||
* dependent on the element having its property data initialized, e.g. | ||
* from defaults (initialized from `constructor`, `_initializeProperties`), | ||
* `attributeChangedCallback`, or values propagated from host e.g. via | ||
* bindings. `super.ready()` must be called to ensure the data system | ||
* becomes enabled. | ||
* | ||
* @public | ||
*/ | ||
ready() { | ||
this.__dataReady = true; | ||
} | ||
|
||
/** | ||
* Provided as an override point for performing any setup work prior | ||
* to initializing the property accessor system. | ||
* | ||
* @protected | ||
*/ | ||
_initializeProperties() {} | ||
|
||
/** | ||
* Called at ready time with bag of instance properties that overwrote | ||
* accessors when the element upgraded. | ||
* | ||
* The default implementation sets these properties back into the | ||
* setter at ready time. This method is provided as an override | ||
* point for customizing or providing more efficient initialization. | ||
* | ||
* @param {Object} props Bag of property values that were overwritten | ||
* when creating property accessors. | ||
* @protected | ||
*/ | ||
_initializeInstanceProperties(props) { | ||
Object.assign(this, props); | ||
} | ||
|
||
/** | ||
* Updates the local storage for a property (via `_setPendingProperty`) | ||
* and enqueues a `_proeprtiesChanged` callback. | ||
* | ||
* @param {string} property Name of the property | ||
* @param {*} value Value to set | ||
* @protected | ||
*/ | ||
_setProperty(property, value) { | ||
if (this._setPendingProperty(property, value)) { | ||
this._invalidateProperties(); | ||
} | ||
} | ||
|
||
_getProperty(property) { | ||
return this.__data[property]; | ||
} | ||
|
||
/** | ||
* Updates the local storage for a property, records the previous value, | ||
* and adds it to the set of "pending changes" that will be passed to the | ||
* `_propertiesChanged` callback. This method does not enqueue the | ||
* `_propertiesChanged` callback. | ||
* | ||
* @param {string} property Name of the property | ||
* @param {*} value Value to set | ||
* @return {boolean} Returns true if the property changed | ||
* @protected | ||
*/ | ||
_setPendingProperty(property, value) { | ||
let old = this.__data[property]; | ||
let changed = this._shouldPropertyChange(property, value, old) | ||
if (changed) { | ||
if (!this.__dataPending) { | ||
this.__dataPending = {}; | ||
this.__dataOld = {}; | ||
} | ||
// Ensure old is captured from the last turn | ||
if (this.__dataOld && !(property in this.__dataOld)) { | ||
this.__dataOld[property] = old; | ||
} | ||
this.__data[property] = value; | ||
this.__dataPending[property] = value; | ||
} | ||
return changed; | ||
} | ||
|
||
/** | ||
* Marks the properties as invalid, and enqueues an async | ||
* `_propertiesChanged` callback. | ||
* | ||
* @protected | ||
*/ | ||
_invalidateProperties() { | ||
if (!this.__dataInvalid && this.__dataReady) { | ||
this.__dataInvalid = true; | ||
microtask.run(() => this._validateProperties()); | ||
} | ||
} | ||
|
||
_validateProperties() { | ||
if (this.__dataInvalid) { | ||
this.__dataInvalid = false; | ||
this._flushProperties(); | ||
} | ||
} | ||
|
||
/** | ||
* Call to enable property accessor processing. Before this method is | ||
* called accessor values will be set but side effects are | ||
* queued. When called, any pending side effects occur immediately. | ||
* For elements, generally `connectedCallback` is a normal spot to do so. | ||
* It is safe to call this method multiple times as it only turns on | ||
* property accessors once. | ||
*/ | ||
_enableProperties() { | ||
if (!this.__dataEnabled) { | ||
this.__dataEnabled = true; | ||
if (this.__dataInstanceProps) { | ||
this._initializeInstanceProperties(this.__dataInstanceProps); | ||
this.__dataInstanceProps = null; | ||
} | ||
this.ready() | ||
} | ||
} | ||
|
||
/** | ||
* Calls the `_propertiesChanged` callback with the current set of | ||
* pending changes (and old values recorded when pending changes were | ||
* set), and resets the pending set of changes. Generally, this method | ||
* should not be called in user code. | ||
* | ||
* | ||
* @protected | ||
*/ | ||
_flushProperties() { | ||
let changedProps = this.__dataPending; | ||
this.__dataPending = null; | ||
this._propertiesChanged(this.__data, changedProps, this.__dataOld); | ||
} | ||
|
||
/** | ||
* Callback called when any properties with accessors created via | ||
* `_createPropertyAccessor` have been set. | ||
* | ||
* @param {!Object} currentProps Bag of all current accessor values | ||
* @param {!Object} changedProps Bag of properties changed since the last | ||
* call to `_propertiesChanged` | ||
* @param {!Object} oldProps Bag of previous values for each property | ||
* in `changedProps` | ||
* @protected | ||
*/ | ||
_propertiesChanged(currentProps, changedProps, oldProps) { // eslint-disable-line no-unused-vars | ||
} | ||
|
||
/** | ||
* Method called to determine whether a property value should be | ||
* considered as a change and cause the `_propertiesChanged` callback | ||
* to be enqueued. | ||
* | ||
* The default implementation returns `true` for primitive types if a | ||
* strict equality check fails, and returns `true` for all Object/Arrays. | ||
* The method always returns false for `NaN`. | ||
* | ||
* Override this method to e.g. provide stricter checking for | ||
* Objects/Arrays when using immutable patterns. | ||
* | ||
* @param {string} property Property name | ||
* @param {*} value New property value | ||
* @param {*} old Previous property value | ||
* @return {boolean} Whether the property should be considered a change | ||
* and enqueue a `_proeprtiesChanged` callback | ||
* @protected | ||
*/ | ||
_shouldPropertyChange(property, value, old) { | ||
return ( | ||
// Strict equality check | ||
(old !== value && | ||
// This ensures (old==NaN, value==NaN) always returns false | ||
(old === old || value === value)) | ||
); | ||
} | ||
|
||
} | ||
|
||
return PropertiesChanged; | ||
|
||
}); | ||
|
||
|
||
})(); | ||
</script> |
Oops, something went wrong.