UNPKG

reflux-core

Version:

A simple library for uni-directional dataflow application architecture inspired by ReactJS Flux

237 lines (216 loc) 9.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.joinStrict = exports.joinConcat = exports.joinLeading = exports.joinTrailing = exports.fetchInitialState = exports.stopListeningToAll = exports.stopListeningTo = exports.listenTo = exports.validateListening = exports.listenToMany = exports.hasListener = undefined; var _utils = require("./utils"); var _ = _interopRequireWildcard(_utils); var _joins = require("./joins"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } /** * Extract child listenables from a parent from their * children property and return them in a keyed Object * * @param {Object} listenable The parent listenable */ var mapChildListenables = function mapChildListenables(listenable) { var i = 0, children = {}, childName; for (; i < (listenable.children || []).length; ++i) { childName = listenable.children[i]; if (listenable[childName]) { children[childName] = listenable[childName]; } } return children; }; /** * Make a flat dictionary of all listenables including their * possible children (recursively), concatenating names in camelCase. * * @param {Object} listenables The top-level listenables */ var flattenListenables = function flattenListenables(listenables) { var flattened = {}; for (var key in listenables) { var listenable = listenables[key]; var childMap = mapChildListenables(listenable); // recursively flatten children var children = flattenListenables(childMap); // add the primary listenable and chilren flattened[key] = listenable; for (var childKey in children) { var childListenable = children[childKey]; flattened[key + _.capitalize(childKey)] = childListenable; } } return flattened; }; /** * An internal utility function used by `validateListening` * * @param {Action|Store} listenable The listenable we want to search for * @returns {Boolean} The result of a recursive search among `this.subscriptions` */ var hasListener = exports.hasListener = function hasListener(listenable) { var i = 0, j, listener, listenables; for (; i < (this.subscriptions || []).length; ++i) { listenables = [].concat(this.subscriptions[i].listenable); for (j = 0; j < listenables.length; j++) { listener = listenables[j]; if (listener === listenable || listener.hasListener && listener.hasListener(listenable)) { return true; } } } return false; }; /** * A convenience method that listens to all listenables in the given object. * * @param {Object} listenables An object of listenables. Keys will be used as callback method names. */ var listenToMany = exports.listenToMany = function listenToMany(listenables) { var allListenables = flattenListenables(listenables); for (var key in allListenables) { var cbname = _.callbackName(key), localname = this[cbname] ? cbname : this[key] ? key : undefined; if (localname) { this.listenTo(allListenables[key], localname, this[cbname + "Default"] || this[localname + "Default"] || localname); } } }; /** * Checks if the current context can listen to the supplied listenable * * @param {Action|Store} listenable An Action or Store that should be * listened to. * @returns {String|Undefined} An error message, or undefined if there was no problem. */ var validateListening = exports.validateListening = function validateListening(listenable) { if (listenable === this) { return "Listener is not able to listen to itself"; } if (!_.isFunction(listenable.listen)) { return listenable + " is missing a listen method"; } if (listenable.hasListener && listenable.hasListener(this)) { return "Listener cannot listen to this listenable because of circular loop"; } }; /** * Sets up a subscription to the given listenable for the context object * * @param {Action|Store} listenable An Action or Store that should be * listened to. * @param {Function|String} callback The callback to register as event handler * @param {Function|String} defaultCallback The callback to register as default handler * @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is the object being listened to */ var listenTo = exports.listenTo = function listenTo(listenable, callback, defaultCallback) { var desub, unsubscriber, subscriptionobj, subs = this.subscriptions = this.subscriptions || []; _.throwIf(this.validateListening(listenable)); this.fetchInitialState(listenable, defaultCallback); desub = listenable.listen(this[callback] || callback, this); unsubscriber = function unsubscriber() { var index = subs.indexOf(subscriptionobj); _.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!"); subs.splice(index, 1); desub(); }; subscriptionobj = { stop: unsubscriber, listenable: listenable }; subs.push(subscriptionobj); return subscriptionobj; }; /** * Stops listening to a single listenable * * @param {Action|Store} listenable The action or store we no longer want to listen to * @returns {Boolean} True if a subscription was found and removed, otherwise false. */ var stopListeningTo = exports.stopListeningTo = function stopListeningTo(listenable) { var sub, i = 0, subs = this.subscriptions || []; for (; i < subs.length; i++) { sub = subs[i]; if (sub.listenable === listenable) { sub.stop(); _.throwIf(subs.indexOf(sub) !== -1, "Failed to remove listen from subscriptions list!"); return true; } } return false; }; /** * Stops all subscriptions and empties subscriptions array */ var stopListeningToAll = exports.stopListeningToAll = function stopListeningToAll() { var remaining, subs = this.subscriptions || []; while (remaining = subs.length) { subs[0].stop(); _.throwIf(subs.length !== remaining - 1, "Failed to remove listen from subscriptions list!"); } }; /** * Used in `listenTo`. Fetches initial data from a publisher if it has a `getInitialState` method. * @param {Action|Store} listenable The publisher we want to get initial state from * @param {Function|String} defaultCallback The method to receive the data */ var fetchInitialState = exports.fetchInitialState = function fetchInitialState(listenable, defaultCallback) { defaultCallback = defaultCallback && this[defaultCallback] || defaultCallback; var me = this; if (_.isFunction(defaultCallback) && _.isFunction(listenable.getInitialState)) { var data = listenable.getInitialState(); if (data && _.isFunction(data.then)) { data.then(function () { defaultCallback.apply(me, arguments); }); } else { defaultCallback.call(this, data); } } }; /** * The callback will be called once all listenables have triggered at least once. * It will be invoked with the last emission from each listenable. * @param {...Publishers} publishers Publishers that should be tracked. * @param {Function|String} callback The method to call when all publishers have emitted * @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is an array of listenables */ var joinTrailing = exports.joinTrailing = (0, _joins.instanceJoinCreator)("last"); /** * The callback will be called once all listenables have triggered at least once. * It will be invoked with the first emission from each listenable. * @param {...Publishers} publishers Publishers that should be tracked. * @param {Function|String} callback The method to call when all publishers have emitted * @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is an array of listenables */ var joinLeading = exports.joinLeading = (0, _joins.instanceJoinCreator)("first"); /** * The callback will be called once all listenables have triggered at least once. * It will be invoked with all emission from each listenable. * @param {...Publishers} publishers Publishers that should be tracked. * @param {Function|String} callback The method to call when all publishers have emitted * @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is an array of listenables */ var joinConcat = exports.joinConcat = (0, _joins.instanceJoinCreator)("all"); /** * The callback will be called once all listenables have triggered. * If a callback triggers twice before that happens, an error is thrown. * @param {...Publishers} publishers Publishers that should be tracked. * @param {Function|String} callback The method to call when all publishers have emitted * @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is an array of listenables */ var joinStrict = exports.joinStrict = (0, _joins.instanceJoinCreator)("strict");