UNPKG

simple-bound

Version:

A simple and customizable reactive data-binding library.

164 lines 5.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const boundError_1 = require("./boundError"); const config_1 = require("./config"); /** * Responsible for binding objects' properties together, storing their values inside and updating subscribers. * * It helps to manipulate bindings on the lowest possible level. * * It only binds a SINGLE property at a time! * * @template T captures a type of property to bind. Once the class is initialied - only properties of types that extend T are allowed for binding. */ class Binding { /** * Creates an instance of Binding. * @param twoWay defines if a binding should always be 2-way and ignore roles. * @param value initial value to assign to slave bindings. * @param [plugins] to call on events. */ constructor(twoWay, value, plugins) { this.twoWay = twoWay; this.value = value; this.plugins = plugins; /** * Stores subscribers for further manipulations. */ this.subscribers = []; } /** * Responsible for executing the plugins synchronyously, * * @param type describes the type of action to be transmitted to a plugin */ callPlugins(type) { if (this.plugins) { this.plugins.forEach(plugin => plugin && plugin(this.value, Object.freeze({ type, subscribers: this.subscribers }))); } } /** * Adds a subscriber to the list of subscribers. * * @param subscriber to add */ bind(subscriber) { if (this.subscribers.every(b => !Binding.subscriptionsEqual(b, subscriber))) { this.subscribers.push(subscriber); } else if (Binding.config.debug) { throw new boundError_1.default(`Binding for ${subscriber.prop} is already declared.`); } return subscriber; } /** * A generic get function that is applied to subscribers. * * Can also be used to get the current binding value. */ get() { this.callPlugins('get'); return this.value; } /** * A generic set function that is applied to subscribers. * * Can also be used to set the current binding value. */ set(newValue) { // Bind value for all masters at once this.value = newValue; // Then notify all slaves about the change this.notify(newValue); // Then call plugins this.callPlugins('set'); } /** * Asynchroniously notifies the subscribers about the value change. * * @param newValue is the value to set to subscribers' properties. */ notify(newValue) { return new Promise((resolve, _) => { this.subscribers.forEach(subscriber => { if (subscriber.role !== 'master') { // Set value for each slave subscriber.obj[subscriber.prop] = newValue; } }); resolve(); }); } addSubscriber(obj, prop, role) { if (this.twoWay || role === 'master') { if (obj[prop] !== undefined) { // Bind value for all masters at once this.value = obj[prop]; // Then notify all slaves about the change this.notify(this.value); } else { obj[prop] = this.value; } this.bind({ obj, prop, role: 'master' }); // TODO: account for a case of having enumerable get/set on a prop instead of normal value Object.defineProperty(obj, prop, { get: this.get.bind(this), set: this.set.bind(this), enumerable: true }); } else { obj[prop] = this.value; this.bind({ obj, prop, role: 'slave' }); } return this; } addMasterSubscriber(obj, prop) { return this.addSubscriber(obj, prop, 'master'); } addSlaveSubscriber(obj, prop) { return this.addSubscriber(obj, prop, 'slave'); } removeSubscriber() { let index = -1; if (typeof arguments[0] === 'number') { index = arguments[0]; } else { const obj = arguments[0]; const prop = arguments[1]; index = this.subscribers.findIndex(b => Binding.subscriptionsEqual(b, { obj, prop })); } if (index !== -1) { // Also remove getters and setters if (this.subscribers[index].role === 'master') { Object.defineProperty(this.subscribers[index].obj, this.subscribers[index].prop, { value: this.value, writable: true }); } this.subscribers.splice(index, 1); } return this; } /** * Clears all subscribers from the binding. */ clearSubscribers() { this.subscribers.forEach((_, index) => this.removeSubscriber(index)); return this; } /** * Global binding config. Changes affect all instances. */ static get config() { return config_1.default; } } /** * Checks subscribers' objects for reference equality. */ Binding.subscriptionsEqual = (src1, src2) => !!src1 && !!src2 && src1.prop === src2.prop && src1.obj === src2.obj; exports.default = Binding; //# sourceMappingURL=binding.js.map