matthewrobb
11/29/2018 - 10:20 PM

boxed-property.js

;(function() {
"use strict";

// Utility class to enable extension of objects that cannot use
// normal class based inheritance e.g. custom functions
//
// Simply put, it adds a mixin static that contains all the property
// descriptors that class intends to pass on
class Mixable {
    static mixin(target) {
        if (!this.hasOwnProperty("mixin")) {
            this.mixin = Object.assign(
                Mixable.mixin.bind(this),
                this.mixin,
                Object.getOwnPropertyDescriptors(this.prototype)
            );
        }

        return Object.defineProperties(target, this.mixin);
    }
}

// Creates basic value storage functions
class BoxedValue extends Mixable {
    static create(currValue) {
      return this.init(function boxedValue(nextValue) {
        return arguments.length > 0 ? (currValue = nextValue) : currValue;
      });
    }

    static init(box) {
        return this.mixin(box);
    }
}

// Building on BoxedValue this includes the concepts for using boxes as properties
// Since a box is just a function that takes 0 or 1 arguments this uses the box
// as both the setter and the getter for a property
class BoxedProperty extends BoxedValue {

    // The box itself is also a property-descriptor which can be used in
    // Object.defineProperty etc.
    static init(box) {
        return Object.assign(super.init(box), {
            enumerable   : true,
            configurable : true,
            
            get : box,
            set : box
        });
    }
    
    // defines an own property on target(arg1) of name(arg2) which uses
    // the box(arg3) as both the setter and the getter
    // Ref: https://www.ecma-international.org/ecma-262/9.0/index.html#table-3
    static define(target, name, box = this.create()) {
        if (!(box instanceof BoxedProperty)) {
            this.init(box);
        }
        Object.defineProperty(target, name, box);
        return box;
    }

    // This will look-up a BoxedProperty from a getter property of an object
    static get(target, name) {
        const { get } = Object.getOwnPropertyDescriptor(target, name) || false;
        if (get instanceof BoxedProperty) {
            return get;
        }
    }

    // This will either return an existing BoxedProperty of an object or define one
    static ensure(target, name, box) {
        return this.get(...arguments) || this.define(...arguments);
    }

    // Link two BoxedProperties from the same or different objects
    static link(source, name, target, targetName = name) {
        return this.ensure(source, name).attach(target, targetName);
    }

    // Attach a boxed property to an object
    attach(target, name) {
        return this.constructor.define(target, name, this);
    }
}

Object.assign(
    typeof module !== "undefined" ? module["exports"] : window,
    {
        Mixable,
        BoxedValue,
        BoxedProperty
    }
);

}());