From a96e1c20a7239254690822dac249c03a789ebd47 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 22 Aug 2020 17:50:56 -0400 Subject: Dynamic property (#749) * Add DynamicProperty class * Add tests for DynamicProperty --- ext/mixed/js/core.js | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) (limited to 'ext/mixed/js') diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 9142a846..5bee4670 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -260,7 +260,7 @@ function promiseTimeout(delay, resolveValue) { /* - * Common events + * Common classes */ class EventDispatcher { @@ -348,3 +348,106 @@ class EventListenerCollection { this._eventListeners = []; } } + +/** + * Class representing a generic value with an override stack. + * Changes can be observed by listening to the 'change' event. + */ +class DynamicProperty extends EventDispatcher { + /** + * Creates a new instance with the specified value. + * @param value The value to assign. + */ + constructor(value) { + super(); + this._value = value; + this._defaultValue = value; + this._overrides = []; + } + + /** + * Gets the default value for the property, which is assigned to the + * public value property when no overrides are present. + */ + get defaultValue() { + return this._defaultValue; + } + + /** + * Assigns the default value for the property. If no overrides are present + * and if the value is different than the current default value, + * the 'change' event will be triggered. + * @param value The value to assign. + */ + set defaultValue(value) { + this._defaultValue = value; + if (this._overrides.length === 0) { this._updateValue(); } + } + + /** + * Gets the current value for the property, taking any overrides into account. + */ + get value() { + return this._value; + } + + /** + * Gets the number of overrides added to the property. + */ + get overrideCount() { + return this._overrides.length; + } + + /** + * Adds an override value with the specified priority to the override stack. + * Values with higher priority will take precedence over those with lower. + * For tie breaks, the override value added first will take precedence. + * If the newly added override has the highest priority of all overrides + * and if the override value is different from the current value, + * the 'change' event will be fired. + * @param value The override value to assign. + * @param priority The priority value to use, as a number. + * @returns A string token which can be passed to the clearOverride function + * to remove the override. + */ + setOverride(value, priority=0) { + const overridesCount = this._overrides.length; + let i = 0; + for (; i < overridesCount; ++i) { + if (priority > this._overrides[i].priority) { break; } + } + const token = generateId(16); + this._overrides.splice(i, 0, {value, priority, token}); + if (i === 0) { this._updateValue(); } + return token; + } + + /** + * Removes a specific override value. If the removed override + * had the highest priority, and the new value is different from + * the previous value, the 'change' event will be fired. + * @param token The token for the corresponding override which is to be removed. + * @returns true if an override was returned, false otherwise. + */ + clearOverride(token) { + for (let i = 0, ii = this._overrides.length; i < ii; ++i) { + if (this._overrides[i].token === token) { + this._overrides.splice(i, 1); + if (i === 0) { this._updateValue(); } + return true; + } + } + return false; + } + + /** + * Updates the current value using the current overrides and default value. + * If the new value differs from the previous value, the 'change' event will be fired. + */ + _updateValue() { + const value = this._overrides.length > 0 ? this._overrides[0].value : this._defaultValue; + if (this._value === value) { return; } + this._value = value; + this.trigger('change', {value}); + } +} -- cgit v1.2.3