lib/Store.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Store = void 0;
/**
* Created by sam_g on 2/7/2017.
*/
var _ = require("lodash");
// import * as areEqual from 'fbjs/lib/areEqual';s
/** Simple data store with history recording and on change callbacks */
var Store = /** @class */ (function () {
/***
* Creates a store and sets its initial state to the optional value passed in
* @param state
*/
function Store(state) {
/***
*
* @ignore
* @type {any[]}
* @private
*/
this._boundStates = [];
this.maxHistoryLength = 32;
/** @private {function[]} */
this.onChangeCallbacks = {
__anyChange: []
};
/** @property {object} [state={}] state - An object that represents the present state */
this.state = state || {};
/** @private */ // @ts-ignore
this.state.__time = Date.now();
/** @private An array of states from the first to the last, excluding the present state */
this.stateHistory = [];
}
/**
* Get an array of states from the first to the last
* @return {object[]} An array of states
*/
Store.prototype.getFullHistory = function () {
return _.concat(this.stateHistory, [this.state]);
};
/**
* Trims the history down to the last 'x' number of entries
* @param {number} length - max number of state history entries
*/
Store.prototype.trimHistory = function (length) {
if (length === void 0) { length = 32; }
this.stateHistory = this.stateHistory.slice(-length);
};
/***
* Automaticly updates the state of stateParent
* @example <caption>Binding to a React component</caption>
* componentDidMount(){
* // Don't forget to unbind on unmount
* mainStore.bindState(this);
* }
* @param stateParent
* @param {function} mapping - (string)=>string
* @param {boolean} forward
*/
Store.prototype.bindState = function (stateParent, mapping, forward) {
if (forward === void 0) { forward = true; }
this._boundStates.push({ boundState: stateParent, mapper: mapping });
if (forward)
this.forwardState(stateParent);
};
/***
* Unbinds the state of stateParent
* @example <caption>Unbinding to a React component</caption>
* componentWillUnmount(){
* mainStore.unbindState(this);
* }
* @param stateParent
*/
Store.prototype.unbindState = function (stateParent) {
var index = -1;
var boundStates = this._boundStates;
for (var i = 0; i < boundStates.length; i++) {
if (boundStates[i].boundState === stateParent)
index = i;
}
if (index > -1) {
boundStates.splice(index, 1);
}
};
Store.prototype.forwardState = function (receivingStateParent, mapping) {
var _this = this;
// the new state that will be transferred to the receiving state
var newState = {};
var receivingKeys = Object.keys(receivingStateParent.state);
// Loop through all of the values in this store's state
Object.keys(this.state).forEach(function (key) {
// Map the sender's key to the receiver
var mappedKey = mapping ? mapping(key) : key;
// If the mappedKey can be found in the receiver add the value to the new state
if (receivingKeys.indexOf(mappedKey) != -1)
newState[mappedKey] = _this.state[key];
});
// Send the new state to the receiving store
receivingStateParent.setState(newState);
};
Store.prototype.forwardToBoundStates = function () {
var _this = this;
this._boundStates.forEach(function (info) { return _this.forwardState(info.boundState, info.mapper); });
};
/**
* Get the previous state of the store
* @return {object} previous state
*/
Store.prototype.getPreviousState = function () {
var index = this.stateHistory.length - 1;
return this.stateHistory[index];
};
/**
* Set the store's state and optionally save the last state to history
* @param {object} newState
* @param {boolean} historyMode
* @example <caption>Set state and record previous state to history</caption>
* myStore.setState({userName: "John Doe"});
* @example <caption>Set state and forget about recording previous state to history</caption>
* myStore.setState({userName: "Billy Bob", false});
*/
Store.prototype.setState = function (newState, historyMode) {
var _this = this;
if (historyMode === void 0) { historyMode = true; }
// Save the old state
var oldState = this.state;
// Update the state
this.state = _.assign({}, oldState, newState);
var changes = Store.getChangeList(oldState, this.state);
this.trimHistory(this.maxHistoryLength);
// Loop through list of changes and trigger their respective callbacks
changes.forEach(function (o) {
// Get the name of the changed property
var property = o.property;
// Get the new and old values of the property
var newProperty = _this.state[property];
var oldProperty = oldState[property];
// Trigger the property's callback
_this._triggerChangeCallbacks(property, newProperty, oldProperty);
});
if (changes.length) {
// @ts-ignore
this.state.__time = Date.now();
if (historyMode == true) {
this.stateHistory.push(oldState);
}
this._triggerChangeCallbacks("__anyChange", this.state, oldState);
this.forwardToBoundStates();
}
};
/**
* Subscribe to changes in the state
* @param {function(newValue: *, oldValue: *)} callback
* @param {string} property - Name of the property to watch
* @example <caption>Trigger callback when userName is updated</caption>
* myStore.subscribeToChanges((newVal)=>{ console.log("userName changed to...", newVal)}, "userName");
*/
Store.prototype.subscribeToChanges = function (callback, property) {
if (property === void 0) { property = "__anyChange"; }
if (this.onChangeCallbacks[property] == undefined)
this.onChangeCallbacks[property] = [];
if (typeof callback != "function") {
console.error("Callback given is not a function as expected");
}
else {
this.onChangeCallbacks[property].push(callback);
}
};
/**
* Remove subscription to changes in the state
* @param {function} callback
* @param {string} property - Name of the property to unsubscribe from
*/
Store.prototype.unSubscribeToChanges = function (callback, property) {
if (property === void 0) { property = "__anyChange"; }
var callbackList = this.onChangeCallbacks[property];
if (callbackList == undefined)
return;
this.onChangeCallbacks[property] = _.pull(callbackList, callback);
};
/**
*
* @param {object} oldState
* @param {object} newState
* @return {Array}
*/
Store.getChangeList = function (oldState, newState) {
// Loop through state properties looking for changes
var changes = [];
for (var property in newState) {
var newProperty = newState[property];
var oldProperty = oldState[property];
if (property == "__time")
continue;
// Generate change list
if (!_.isEqual(newProperty, oldProperty)) {
changes.push({
property: property,
isNew: oldProperty == undefined || oldProperty == null
});
}
}
return changes;
};
/**
* Returns difference between two states in human-readable form
* @param {object} oldState
* @param {object} newState
* @return {string}
*/
Store.getChangeLog = function (oldState, newState) {
var changes = this.getChangeList(oldState, newState);
var log = changes.map(function (o) {
if (o.isNew)
return "Added new property \"".concat(o.property, "\"");
else
return "Changed value of \"".concat(o.property, "\"");
});
return log.join("\n");
};
// TODO: Should this be private
/***
*
* @ignore
* @param property
* @param newValue
* @param oldValue
* @private
*/
Store.prototype._triggerChangeCallbacks = function (property, newValue, oldValue) {
if (!this.onChangeCallbacks[property])
return;
this.onChangeCallbacks[property].forEach(function (callback) { return callback(newValue, oldValue, property); });
};
/**
* Returns change log of entire history in human-readable form
* @return {string}
*/
Store.prototype.getChangeLog = function () {
var fullHistory = this.getFullHistory();
var changes = [];
fullHistory.forEach(function (o, i) {
if (i == 0)
return;
changes.push(Store.getChangeLog(fullHistory[i - 1], o));
});
return changes.join("\n------------\n");
};
return Store;
}());
exports.Store = Store;
exports.default = Store;
//# sourceMappingURL=Store.js.map