From 44ff5efa209e741d26e431d475178a2123a9757d Mon Sep 17 00:00:00 2001 From: Michael Ridgway Date: Thu, 14 Aug 2014 21:40:44 -0700 Subject: [PATCH] [resolves #16] Added utils for creating stores --- README.md | 31 ++++++++++++------ lib/Dispatcher.js | 3 +- tests/mock/DelayedStore.js | 65 +++++++++++++++----------------------- tests/mock/Store.js | 13 +++++--- utils/BaseStore.js | 48 ++++++++++++++++++++++++++++ utils/createStore.js | 47 +++++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 utils/BaseStore.js create mode 100644 utils/createStore.js diff --git a/README.md b/README.md index 0af9234..d8304b3 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,12 @@ Takes an object representing the state of the Dispatchr instance (usually retrie ## Store Interface -Dispatchr expects that your stores use the following interface: +We have provided utilities for creating stores in a couple of forms: + +* `require('dispatchr/utils/createStore')` returns a `React.createClass`-like function for creating stores. +* `require('dispatchr/utils/BaseStore')` returns an extendable class. + +You are not required to use these if you want to keep your stores completely decoupled from the dispatcher. Dispatchr expects that your stores use the following interface: ### Constructor @@ -70,10 +75,14 @@ The store should have a constructor function that will be used to instantiate yo * `dispatcherInterface.getStore(store)` * `dispatcherInterface.waitFor(store[], callback)` + The constructor is also where the initial state of the store should be initialized. + ```js function ExampleStore(dispatcher) { this.dispatcher = dispatcher; - this.getInitialState(); + if (this.initialize) { + this.initialize(); + } } ``` @@ -113,25 +122,27 @@ ExampleStore.prototype.handleNavigate = function (payload) { }; ``` -### getState() +### dehydrate() -The store should implement this function that will return a serializable data object that can be transferred from the server to the client and also be used by your components. +The store should define this function to dehydrate the store if it will be shared between server and client. It should return a serializable data object that will be passed to the client. ```js -ExampleStore.prototype.getState = function () { +ExampleStore.prototype.dehydrate = function () { return { navigating: this.navigating }; }; ``` -### dehydrate() - -The store can optionally define this function to customize the dehydration of the store. It should return a serializable data object that will be passed to the client. - ### rehydrate(state) -The store can optionally define this function to customize the rehydration of the store. It should restore the store to the original state using the passed `state`. +The store should define this function to rehydrate the store if it will be shared between server and client. It should restore the store to the original state using the passed `state`. + +```js +ExampleStore.prototype.rehydrate = function (state) { + this.navigating = state.navigating; +}; +``` ## License diff --git a/lib/Dispatcher.js b/lib/Dispatcher.js index 04335cc..0171c16 100644 --- a/lib/Dispatcher.js +++ b/lib/Dispatcher.js @@ -141,6 +141,7 @@ module.exports = function () { debug(actionName + ' does not have any registered handlers'); return; } + debug('dispatching ' + actionName, payload); this.currentAction = new Action(actionName, payload); var self = this, handlerFns = {}; @@ -169,8 +170,6 @@ module.exports = function () { var store = self.storeInstances[storeName]; if (store.dehydrate) { stores[storeName] = store.dehydrate(); - } else { - stores[storeName] = store.getState(); } }); return { diff --git a/tests/mock/DelayedStore.js b/tests/mock/DelayedStore.js index 4481db5..33a7489 100644 --- a/tests/mock/DelayedStore.js +++ b/tests/mock/DelayedStore.js @@ -2,42 +2,29 @@ * Copyright 2014, Yahoo! Inc. * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ -var util = require('util'), - EventEmitter = require('events').EventEmitter; - -function DelayedStore(dispatcher) { - this.dispatcher = dispatcher; - this.getInitialState(); -} - -DelayedStore.storeName = 'DelayedStore'; -util.inherits(DelayedStore, EventEmitter); - -DelayedStore.prototype.getInitialState = function () { - this.state = {}; -}; - -DelayedStore.prototype.delay = function (payload) { - var self = this; - self.called = true; - self.state.page = 'delay'; - self.state.final = true; -}; - -DelayedStore.prototype.getState = function () { - return this.state; -}; - -DelayedStore.prototype.dehydrate = function () { - return this.state; -}; - -DelayedStore.prototype.rehydrate = function (state) { - this.state = state; -}; - -DelayedStore.handlers = { - 'DELAY': 'delay' -}; - -module.exports = DelayedStore; +var createStore = require('../../utils/createStore'); + +module.exports = createStore({ + storeName: 'DelayedStore', + handlers: { + 'DELAY': 'delay' + }, + initialize: function () { + this.state = {}; + }, + delay: function (payload) { + var self = this; + self.called = true; + self.state.page = 'delay'; + self.state.final = true; + }, + getState: function () { + return this.state; + }, + dehydrate: function () { + return this.state; + }, + rehydrate: function (state) { + this.state = state; + } +}); diff --git a/tests/mock/Store.js b/tests/mock/Store.js index 2c6326e..868ae73 100644 --- a/tests/mock/Store.js +++ b/tests/mock/Store.js @@ -3,18 +3,17 @@ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ var util = require('util'), - EventEmitter = require('events').EventEmitter, + BaseStore = require('../../utils/BaseStore'), DelayedStore = require('./DelayedStore'); function Store(dispatcher) { - this.dispatcher = dispatcher; - this.getInitialState(); + BaseStore.call(this, dispatcher); } Store.storeName = 'Store'; -util.inherits(Store, EventEmitter); +util.inherits(Store, BaseStore); -Store.prototype.getInitialState = function () { +Store.prototype.initialize = function () { this.state = { called: false }; @@ -41,6 +40,10 @@ Store.prototype.getState = function () { return this.state; }; +Store.prototype.dehydrate = function () { + return this.state; +}; + Store.prototype.rehydrate = function (state) { this.state = state; }; diff --git a/utils/BaseStore.js b/utils/BaseStore.js new file mode 100644 index 0000000..3f8eb98 --- /dev/null +++ b/utils/BaseStore.js @@ -0,0 +1,48 @@ +/** + * Copyright 2014, Yahoo! Inc. + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +'use strict'; + +var util = require('util'), + EventEmitter = require('events').EventEmitter, + CHANGE_EVENT = 'change'; + +/** + * @class BaseStore + * @param dispatcher The dispatcher interface + * @constructor + */ +function BaseStore(dispatcher) { + this.dispatcher = dispatcher; + if (this.initialize) { + this.initialize(); + } +} + +util.inherits(BaseStore, EventEmitter); + +/** + * Add a listener for the change event + * @param {Function} callback + */ +BaseStore.prototype.addChangeListener = function(callback) { + this.on(CHANGE_EVENT, callback); +}; + +/** + * Remove a listener for the change event + * @param {Function} callback + */ +BaseStore.prototype.removeChangeListener = function(callback) { + this.removeListener(CHANGE_EVENT, callback); +}; + +/** + * Emit a change event + */ +BaseStore.prototype.emitChange = function() { + this.emit(CHANGE_EVENT); +}; + +module.exports = BaseStore; \ No newline at end of file diff --git a/utils/createStore.js b/utils/createStore.js new file mode 100644 index 0000000..40f0e3d --- /dev/null +++ b/utils/createStore.js @@ -0,0 +1,47 @@ +/** + * Copyright 2014, Yahoo! Inc. + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +'use strict'; + +var util = require('util'), + BaseStore = require('./BaseStore'), + IGNORE_ON_PROTOTYPE = ['statics', 'storeName', 'handlers']; + +/** + * Helper for creating a store class + * @param {Object} spec + * @param {String} spec.storeName The name of the store + * @param {Object} spec.handlers Hash of action name to method name of action handlers + * @param {Function} spec.initialize Function called during construction for setting the default state + * @param {Function} spec.dehydrate Function that returns serializable data to send to the client + * @param {Function} spec.rehydrate Function that takes in serializable data to rehydrate the store + */ +module.exports = function createStore(spec) { + spec.statics = spec.statics || {}; + if (!spec.storeName && !spec.statics.storeName) { + throw new Error('createStore called without a storeName'); + } + var Store = function (dispatcher) { + BaseStore.call(this, dispatcher); + }; + + util.inherits(Store, BaseStore); + + if (spec.statics) { + Object.keys(spec.statics).forEach(function (prop) { + Store[prop] = spec.statics[prop]; + }); + } + Store.storeName = spec.storeName; + Store.handlers = spec.handlers; + + Object.keys(spec).forEach(function (prop) { + if (-1 !== IGNORE_ON_PROTOTYPE.indexOf(prop)) { + return; + } + Store.prototype[prop] = spec[prop]; + }); + + return Store; +}; \ No newline at end of file