Home Reference Source Test

source/Model.js

import { walkPrototype } from './utility/index';


const model_observer = Symbol('Model observer');


/**
 * @abstract
 */
export default class Model extends Map {
    /**
     * @param {Object} [data={}]
     */
    constructor(data = {}) {

        if (super().constructor === Model)
            throw TypeError('Model() is an Abstract Class');

        this[model_observer] = { };

        this.forEach((value, key)  =>  (this[key] = data[key]));
    }

    /**
     * @param {ModelValueHandler} callback
     */
    forEach(callback) {

        for (const prototype  of  walkPrototype( this )) {

            if (prototype === Model.prototype)  break;

            const scheme = Object.getOwnPropertyDescriptors( prototype );

            for (let key in scheme)
                if ( scheme[key].set )  callback(this.get(key), key, this);
        }
    }

    /**
     * @return {Object}
     */
    valueOf() {

        const data = { };

        this.forEach((value, key)  =>  {

            if (this.has( key ))  data[key] = value ? value.valueOf() : value;
        });

        return data;
    }

    /**
     * @param {Object} keyHandler - Keys for Property names, Values for {@link ValueChangedHandler}
     */
    observe(keyHandler) {

        const map = this[model_observer];

        for (let key in keyHandler)
            (map[key] = map[key] || [ ]).push( keyHandler[key] );
    }

    /**
     * @param {Object} keyHandler - Keys for Property names, Values for {@link ValueChangedHandler}
     */
    unobserve(keyHandler) {

        const map = this[model_observer];

        for (let key in keyHandler)
            if ( map[key] ) {

                const index = map[key].indexOf( keyHandler[key] );

                if (index > -1)  map[key].splice(index, 1);
            }
    }

    /**
     * @param {*} key
     * @param {*} value
     *
     * @return {Model}
     */
    set(key, value) {

        const old = this.get( key );

        if (old !== value) {

            super.set(key, value);

            /**
             * @typedef {Function} ValueChangedHandler
             *
             * @this {Model}
             *
             * @param {*} value
             * @param {*} oldValue
             */
            (this[model_observer][key]  ||  [ ]).forEach(
                handler  =>  handler.call(this, value, old)
            );
        }

        return this;
    }
}


/**
 * Class Decorator to set same name Getters of existed Setters
 *
 * @param {DecoratorDescriptor} meta
 */
export function mapGetter({ elements }) {

    elements.forEach(({ key, placement, descriptor })  =>  {

        if ((placement === 'prototype')  &&  descriptor.set  &&  !descriptor.get)
            descriptor.get = function () {

                return  this.get( key );
            };
    });
}