reflux-core
Version:
A simple library for uni-directional dataflow application architecture inspired by ReactJS Flux
237 lines (216 loc) • 9.51 kB
JavaScript
"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");