UNPKG

marbles

Version:

Front-end framework for routing, http, and data handling

326 lines (285 loc) 7.96 kB
"use strict"; var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { "default": obj }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* @flow weak */ var _Utils = require("./utils"); var _Utils2 = _interopRequireWildcard(_Utils); var _State = require("./state"); var _State2 = _interopRequireWildcard(_State); /** * @memberof Marbles * @class * @param {*} id Anything serializable as JSON * @desc This class is meant to be sub-classed using Store.createClass */ var Store = function Store(id) { this.id = id; this.constructor.__trackInstance(this); this.__changeListeners = []; this.willInitialize.apply(this, Array.prototype.slice.call(arguments, 1)); this.state = this.getInitialState(); this.didInitialize.apply(this, Array.prototype.slice.call(arguments, 1)); }; Store.displayName = "Marbles.Store"; _Utils2["default"].extend(Store.prototype, _State2["default"], { /** * @memberof Marbles.Store * @instance * @method * @returns {Object} Initial state object */ getInitialState: function getInitialState() { return {}; }, /** * @memberof Marbles.Store * @instance * @method * @desc Called before state is initialized */ willInitialize: function willInitialize() {}, /** * @memberof Marbles.Store * @instance * @method * @desc Called after state is initialized */ didInitialize: function didInitialize() {}, /** * @memberof Marbles.Store * @instance * @method * @desc Called when first change listener is added */ didBecomeActive: function didBecomeActive() {}, /** * @memberof Marbles.Store * @instance * @method * @desc Called when last change listener is removed and when the instance is otherwise perceived as inactive */ didBecomeInactive: function didBecomeInactive() {}, /** * @memberof Marbles.Store * @instance * @method * @param {Object} event * @desc Called with Dispatcher events */ handleEvent: function handleEvent() {} }); // Call didBecomeActive when first change listener added Store.prototype.addChangeListener = function () { this.__changeListenerExpected = false; _State2["default"].addChangeListener.apply(this, arguments); if (!this.__active && this.__changeListeners.length === 1) { this.__active = true; this.didBecomeActive(); } }; // Call didBecomeInactive when last change listener removed Store.prototype.removeChangeListener = function () { var done = (function () { this.__active = false; }).bind(this); _State2["default"].removeChangeListener.apply(this, arguments); if (this.__changeListeners.length === 0 && !this.__changeListenerExpected) { Promise.resolve(this.didBecomeInactive()).then(done); } }; Store.prototype.expectChangeListener = function () { this.__changeListenerExpected = true; }; Store.prototype.unexpectChangeListener = function () { this.__changeListenerExpected = false; if (this.__changeListeners.length === 0) { Promise.resolve(this.didBecomeInactive()).then((function () { this.__active = false; }).bind(this)); } }; Store.__instances = {}; function stringifyStoreId(id) { if (id && typeof id === "object" && !Array.isArray(id)) { var keys = Object.keys(id); var values = keys.map(function (k) { return id[k]; }); return JSON.stringify(keys.sort().concat(values.sort())); } else if (Array.isArray(id)) { return "[" + id.map(stringifyStoreId).join(",") + "]"; } else { return JSON.stringify(id); } } Store.__getInstance = function (id, opts) { opts = opts || {}; var key = stringifyStoreId(id); return this.__instances[key] || (opts.allowNull ? null : new this(id)); }; Store.__trackInstance = function (instance) { var key = stringifyStoreId(instance.id); this.__instances[key] = instance; }; /** * @memberof Marbles.Store * @func * @param {Marbles.Store} store * @desc Give Store instance up for garbage collection */ Store.discardInstance = function (instance) { var key = stringifyStoreId(instance.id); delete this.__instances[key]; }; /** * @memberof Marbles.Store * @func * @param {Store#id} id */ Store.addChangeListener = function (id) { var instance = this.__getInstance(id); return instance.addChangeListener.apply(instance, Array.prototype.slice.call(arguments, 1)); }; /** * @memberof Marbles.Store * @func * @param {Store#id} id */ Store.removeChangeListener = function (id) { var instance = this.__getInstance(id); return instance.removeChangeListener.apply(instance, Array.prototype.slice.call(arguments, 1)); }; /** * @memberof Marbles.Store * @func * @param {Store#id} id * @desc Force store to remain active until the next change listener is added */ Store.expectChangeListener = function (id) { var instance = this.__getInstance(id, { allowNull: true }); if (instance) { instance.expectChangeListener(); } }; /** * @memberof Marbles.Store * @func * @param {Store#id} id * @desc Undo expectation from expectChangeListener */ Store.unexpectChangeListener = function (id) { var instance = this.__getInstance(id, { allowNull: true }); if (instance) { instance.unexpectChangeListener(); } }; /** * @memberof Marbles.Store * @prop {Number} */ Store.dispatcherIndex = null; /** * @memberof Marbles.Store * @func * @param {Marbles.Dispatcher} dispatcher */ Store.registerWithDispatcher = function (dispatcher) { this.dispatcherIndex = dispatcher.register((function (event) { if (event.storeId && (!this.isValidId || this.isValidId(event.storeId))) { var instance = this.__getInstance(event.storeId); var res = Promise.resolve(instance.handleEvent(event)); var after = function after(isError, args) { if (instance.__changeListeners.length === 0) { instance.didBecomeInactive(); } if (isError) { return Promise.reject(args); } else { return Promise.resolve(args); } }; res.then(after.bind(null, false), after.bind(null, true)); return res; } else { return Promise.all(Object.keys(this.__instances).sort().map((function (key) { var instance = this.__instances[key]; return new Promise(function (resolve) { resolve(instance.handleEvent(event)); }); }).bind(this))); } }).bind(this)); }; /** * @memberof Marbles.Store * @func * @param {Object} proto Prototype of new child class * @desc Creates a new class that inherits from Store * @example * var MyStore = Marbles.Store.createClass({ * displayName: "MyStore", * * getInitialState: function () { * return { my: "state" }; * }, * * willInitialize: function () { * // do something * }, * * didInitialize: function () { * // do something * }, * * didBecomeActive: function () { * // do something * }, * * didBecomeInactive: function () { * // do something * }, * * handleEvent: function (event) { * // do something * } * }); * */ Store.createClass = function (proto) { var parent = this; var store = _Utils2["default"].inheritPrototype(function () { parent.apply(this, arguments); }, parent); store.__instances = {}; if (proto.hasOwnProperty("displayName")) { store.displayName = proto.displayName; delete proto.displayName; } _Utils2["default"].extend(store.prototype, proto); function wrappedFn(name, id) { var instance = this.__getInstance(id); var res = instance[name].apply(instance, Array.prototype.slice.call(arguments, 2)); var after = function after(isError, args) { if (instance.__changeListeners.length === 0) { instance.didBecomeInactive(); } if (isError) { return Promise.reject(args); } else { return Promise.resolve(args); } }; Promise.resolve(res).then(after.bind(null, false), after.bind(null, true)); return res; } for (var k in proto) { if (proto.hasOwnProperty(k) && k.slice(0, 1) !== "_" && typeof proto[k] === "function") { store[k] = wrappedFn.bind(store, k); } } return store; }; exports["default"] = Store; module.exports = exports["default"];