@labir/core
Concept of properties

Concept of properties

Properties are used in ThermalManager, ThermalRegistry, ThermalGroup and ThermalFileInstance. Every "property" object encapsulates a complex functionality related to a particular attribute of thermal display.

How it works

Every property is a readonly object with:

  • readonly value holding the current value
  • no default setter
  • individual setters for every property, for every use case, because setters usually have many side effects
  • addListener( key, listener ) method for registering callbacks to value change
  • possible additional data in individual properties

Properties are read only

Properties are added to structure classes like this:

thermalRegistry.ts
 
export class ThermalRegistry {
 
    // ...
 
    public readonly opacity = new OpacityDrive(this, 1);
    public readonly minmax = new MinmaxRegistryProperty(this, undefined);
    public readonly loading = new LoadingState(this, false);
    public readonly range = new RangeDriver(this, undefined);
    public readonly histogram = new HistogramState(this, []);
 
    // ...
 
}
 

AbstractProperty

Properties are based on AbstractProperty (opens in a new tab). Principle is the folllowing:

/** A common basis for all observable properties */
export abstract class AbstractProperty<
    ValueType extends PropertyListenersTypes, // Type of the value
    ParentType extends IBaseProperty // The class to which the property is assigned
> {
 
    public constructor(
        public readonly parent: ParentType,
        public readonly _initial: ValueType
    ) {
        this._value = this.validate(this._initial);
    }
 
    // Internal value
    protected _value: ValueType;
 
    // Public value is readonly
    public get value() {
        return this._value;
    };
 
    // Protected setter calls all necessary effects and triggers event listeners
    protected set value(
        value: ValueType
    ) {
        this._value = this.validate(value);
        this.afterSetEffect(this._value);
        Object.values(this._listeners).forEach(listener => listener(this._value));
    }
 
 
    // Abstract methods
 
    /** Implement the validation method. */
    protected abstract validate(value: ValueType): ValueType;
 
    /** implement the sideeffects method. */
    protected abstract afterSetEffect(value: ValueType): void
 
 
    // Listeners management
 
    protected _listeners: {
        [index: string]: PropertyListenerFn<ValueType>
    } = {}
 
    addListener(
        id: string,
        listener: PropertyListenerFn<ValueType>
    ) {
 
        if (id in this._listeners) {
            delete this._listeners[id];
        }
        this._listeners[id] = listener;
    }
 
    removeListener(
        id: string
    ) {
        if (id in this._listeners) {
            delete this._listeners[id];
        }
    }
 
 

Example of implementation

 
export class PaletteDrive extends AbstractProperty< PaletteId, ThermalManager > {
 
    /** The main functionality is handled by the abstract class. Here. we write only the missing parts. */
 
    /** The public setter */
    public setPalette( key: PaletteId ) {
        // Only store value here. The propagation of new value is in `afterSetEffect`
        this.value = key;
    }
 
    /** What to do after new value is set */
    protected afterSetEffect(value: PaletteId) {
 
        // impose the new value to all child instances
        this.parent.forEveryRegistry( registry => {
 
            registry.forEveryGroup( group => group
                .instances
                .forEveryInstance( instance => instance
                    .recievePalette( value )        
                ) 
            );
 
        } );
            
    }
 
    /** Expose all available palettes */
    public get availablePalettes() {
        return ThermalPalettes;
    }
 
    /** Expose the current palette object (internal value is string, so here are full data) */
    public get currentPalette() {
        return this.availablePalettes[ this.value ];
    }
 
    /** We do not validate the value in this case */
    protected validate(value: PaletteId): PaletteId {
        return value;
    }
 
 
}

Source code

See github repository (opens in a new tab)