198 lines
6.2 KiB
JavaScript
198 lines
6.2 KiB
JavaScript
import { dedupeMixin } from './dedupeMixin.js';
|
|
|
|
/**
|
|
*
|
|
* @type {Symbol}
|
|
*/
|
|
const undefinedSymbol = Symbol('this value should actually be undefined when passing on');
|
|
|
|
/**
|
|
* # ObserverMixin
|
|
* `ObserverMixin` warns the developer if something unexpected happens and provides
|
|
* triggerObserversFor() which can be used within a setter to hook into the observer system.
|
|
* It has syncObservers, which call observers immediately when the observed property
|
|
* is changed (newValue !== oldValue) and asyncObservers, which makes only one call
|
|
* to observer even if multiple observed attributes changed.
|
|
*
|
|
* **Deprecated**: Please use LitElement update/updated instead.
|
|
*
|
|
* @deprecated
|
|
* @type {function()}
|
|
* @polymerMixin
|
|
* @mixinFunction
|
|
*/
|
|
export const ObserverMixin = dedupeMixin(
|
|
superclass =>
|
|
// eslint-disable-next-line
|
|
class ObserverMixin extends superclass {
|
|
/**
|
|
* @returns {{}}
|
|
*/
|
|
static get syncObservers() {
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* @returns {{}}
|
|
*/
|
|
static get asyncObservers() {
|
|
return {};
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.__initializeObservers('sync');
|
|
this.__initializeObservers('async');
|
|
this.__asyncObserversQueue = {};
|
|
this.__asyncObserversNewValues = {};
|
|
this.__asyncObserversOldValues = {};
|
|
}
|
|
|
|
/**
|
|
* @param {string} property
|
|
* @param {*} newValue
|
|
* @param {*} oldValue
|
|
*/
|
|
triggerObserversFor(property, newValue, oldValue) {
|
|
this.__executeSyncObserversFor(property, newValue, oldValue);
|
|
this.__addToAsyncObserversQueue(property, newValue, oldValue);
|
|
this.updateComplete.then(() => {
|
|
this.__emptyAsyncObserversQueue();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sync hooks into UpdatingElement mixin
|
|
*
|
|
* @param {string} property
|
|
* @param {any} oldValue
|
|
* @private
|
|
*/
|
|
_requestUpdate(property, oldValue) {
|
|
super._requestUpdate(property, oldValue);
|
|
this.__executeSyncObserversFor(property, this[property], oldValue);
|
|
}
|
|
|
|
/**
|
|
* Async hook into Updating Element
|
|
*
|
|
* @param {Map} changedProperties
|
|
*/
|
|
update(changedProperties) {
|
|
super.update(changedProperties);
|
|
this.__addMultipleToAsyncObserversQueue(changedProperties);
|
|
this.__emptyAsyncObserversQueue();
|
|
}
|
|
|
|
/**
|
|
* @param {string} type
|
|
* @private
|
|
*/
|
|
__initializeObservers(type) {
|
|
this[`__${type}ObserversForProperty`] = {};
|
|
Object.keys(this.constructor[`${type}Observers`]).forEach(observerFunctionName => {
|
|
const propertiesToObserve = this.constructor[`${type}Observers`][observerFunctionName];
|
|
if (typeof this[observerFunctionName] === 'function') {
|
|
propertiesToObserve.forEach(property => {
|
|
if (!this[`__${type}ObserversForProperty`][property]) {
|
|
this[`__${type}ObserversForProperty`][property] = [];
|
|
}
|
|
this[`__${type}ObserversForProperty`][property].push(observerFunctionName);
|
|
});
|
|
} else {
|
|
throw new Error(
|
|
`${this.localName} does not have a function called ${observerFunctionName}`,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} observedProperty
|
|
* @param {*} newValue
|
|
* @param {*} oldValue
|
|
* @private
|
|
*/
|
|
__executeSyncObserversFor(observedProperty, newValue, oldValue) {
|
|
if (newValue === oldValue) return;
|
|
const functionsToCall = {};
|
|
if (this.__syncObserversForProperty[observedProperty]) {
|
|
this.__syncObserversForProperty[observedProperty].forEach(observerFunctionName => {
|
|
functionsToCall[observerFunctionName] = true;
|
|
});
|
|
}
|
|
|
|
Object.keys(functionsToCall).forEach(functionName => {
|
|
const newValues = {};
|
|
const oldValues = {};
|
|
this.constructor.syncObservers[functionName].forEach(property => {
|
|
newValues[property] = observedProperty === property ? newValue : this[property];
|
|
oldValues[property] = observedProperty === property ? oldValue : this[property];
|
|
});
|
|
this[functionName](newValues, oldValues);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} property
|
|
* @param {*} newValue
|
|
* @param {*} oldValue
|
|
* @private
|
|
*/
|
|
__addToAsyncObserversQueue(property, newValue, oldValue) {
|
|
this.__asyncObserversNewValues[property] = newValue;
|
|
if (this.__asyncObserversOldValues[property] === undefined) {
|
|
// only get old value once
|
|
this.__asyncObserversOldValues[property] = oldValue;
|
|
}
|
|
if (oldValue === undefined) {
|
|
// special case for undefined
|
|
this.__asyncObserversOldValues[property] = undefinedSymbol;
|
|
}
|
|
if (this.__asyncObserversForProperty[property]) {
|
|
this.__asyncObserversForProperty[property].forEach(observerFunctionName => {
|
|
this.__asyncObserversQueue[observerFunctionName] = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Map} oldValues
|
|
* @private
|
|
*/
|
|
__addMultipleToAsyncObserversQueue(oldValues) {
|
|
if (!oldValues) return;
|
|
oldValues.forEach((oldValue, property) => {
|
|
this.__addToAsyncObserversQueue(property, this[property], oldValue);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
__emptyAsyncObserversQueue() {
|
|
Object.keys(this.__asyncObserversQueue).forEach(functionName => {
|
|
this[functionName](
|
|
this.__asyncObserversNewValues,
|
|
this.__getOldValuesWithRealUndefined(),
|
|
);
|
|
});
|
|
this.__asyncObserversNewValues = {};
|
|
this.__asyncObserversOldValues = {};
|
|
this.__asyncObserversQueue = {};
|
|
}
|
|
|
|
/**
|
|
* @returns {{}}
|
|
* @private
|
|
*/
|
|
__getOldValuesWithRealUndefined() {
|
|
const result = {};
|
|
Object.keys(this.__asyncObserversOldValues).forEach(key => {
|
|
const value = this.__asyncObserversOldValues[key];
|
|
result[key] = value === undefinedSymbol ? undefined : value;
|
|
});
|
|
return result;
|
|
}
|
|
},
|
|
);
|