Skip to content
This repository has been archived by the owner on Jul 15, 2019. It is now read-only.

Commit

Permalink
Merge pull request #60 from yahoo/factoryClass
Browse files Browse the repository at this point in the history
[resolves #53] Change API to factory methods rather than exporting class
  • Loading branch information
mridgway committed Mar 30, 2015
2 parents d9fc7d0 + 8ceab0d commit d697710
Show file tree
Hide file tree
Showing 17 changed files with 616 additions and 487 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ A [Flux](http://facebook.github.io/flux/docs/overview.html) dispatcher for appli
For a more detailed example, see our [example application](https://github.com/yahoo/flux-example).

```js
var Dispatchr = require('dispatchr')(),
ExampleStore = require('./example-store.js'),
context = {};

Dispatchr.registerStore(ExampleStore);
var ExampleStore = require('./stores/ExampleStore.js');
var dispatcher = require('dispatchr').createDispatcher({
stores: [ExampleStore]
});

var dispatcher = new Dispatchr(context);
var contextOptions = {};
var dispatcherContext = dispatcher.createContext(contextOptions);

dispatcher.dispatch('NAVIGATE', {});
// Action has been handled fully
Expand All @@ -41,15 +41,15 @@ These utilities make creating stores less verbose and provide some `change` rela

### BaseStore

`require('dispatchr/utils/BaseStore')` provides a base store class for extending. Provides `getContext`, `emitChange`, `addChangeListener`, and `removeChangeListener` functions. Example:
`require('dispatchr/addons/BaseStore')` provides a base store class for extending. Provides `getContext`, `emitChange`, `addChangeListener`, and `removeChangeListener` functions. Example:

```js
var util = require('util');
var BaseStore = require('dispatchr/utils/BaseStore');
var inherits = require('inherits');
var BaseStore = require('dispatchr/addons/BaseStore');
var MyStore = function (dispatcherInterface) {
BaseStore.apply(this, arguments);
};
util.inherits(MyStore, BaseStore);
inherits(MyStore, BaseStore);
MyStore.storeName = 'MyStore';
MyStore.handlers = {
'NAVIGATE': function (payload) { ... this.emitChange() ... }
Expand All @@ -61,10 +61,10 @@ module.exports = MyStore;

### createStore

`require('dispatchr/utils/createStore')` provides a helper function for creating stores similar to React's `createClass` function. The created store class will extend BaseStore and have the same built-in functions. Example:
`require('dispatchr/addons/createStore')` provides a helper function for creating stores similar to React's `createClass` function. The created store class will extend BaseStore and have the same built-in functions. Example:

```js
var createStore = require('dispatchr/utils/createStore');
var createStore = require('dispatchr/addons/createStore');
var MyStore = createStore({
initialize: function () {}, // Called immediately after instantiation
storeName: 'MyStore',
Expand Down
75 changes: 75 additions & 0 deletions addons/BaseStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright 2014, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
'use strict';

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var CHANGE_EVENT = 'change';

/**
* @class BaseStore
* @extends EventEmitter
* @param dispatcher The dispatcher interface
* @constructor
*/
function BaseStore(dispatcher) {
this.dispatcher = dispatcher;
this._hasChanged = false;
if (this.initialize) {
this.initialize();
}
}

util.inherits(BaseStore, EventEmitter);

/**
* Convenience method for getting the store context object.
* @method getContext
* @return {Object} Returns the store context object.
*/
BaseStore.prototype.getContext = function getContext() {
return this.dispatcher.getContext();
};

/**
* Add a listener for the change event
* @method addChangeListener
* @param {Function} callback
*/
BaseStore.prototype.addChangeListener = function addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
};

/**
* Remove a listener for the change event
* @method removeChangeListener
* @param {Function} callback
*/
BaseStore.prototype.removeChangeListener = function removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
};

/**
* Determines whether the store should dehydrate or not. By default, only dehydrates
* if the store has emitted an update event. If no update has been emitted, it is assumed
* that the store is in its default state and therefore does not need to dehydrate.
* @method shouldDehydrate
* @returns {boolean}
*/
BaseStore.prototype.shouldDehydrate = function shouldDehydrate() {
return this._hasChanged;
};

/**
* Emit a change event
* @method emitChange
* @param {*} param=this
*/
BaseStore.prototype.emitChange = function emitChange(param) {
this._hasChanged = true;
this.emit(CHANGE_EVENT, param || this);
};

module.exports = BaseStore;
75 changes: 75 additions & 0 deletions addons/createStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* 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', 'mixins'];

function createChainedFunction(one, two) {
return function chainedFunction() {
one.apply(this, arguments);
two.apply(this, arguments);
};
}

function mixInto(dest, src) {
Object.keys(src).forEach(function (prop) {
if (-1 !== IGNORE_ON_PROTOTYPE.indexOf(prop)) {
return;
}
if ('initialize' === prop) {
if (!dest[prop]) {
dest[prop] = src[prop];
} else {
dest[prop] = createChainedFunction(dest[prop], src[prop]);
}
} else {
if (dest.hasOwnProperty(prop)) {
throw new Error('Mixin property collision for property "' + prop + '"');
}
dest[prop] = src[prop];
}
});
}

/**
* Helper for creating a store class
* @method createStore
* @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);

Object.keys(spec.statics).forEach(function (prop) {
Store[prop] = spec.statics[prop];
});

Store.storeName = spec.storeName || Store.storeName;
Store.handlers = spec.handlers || Store.handlers;
Store.mixins = spec.mixins || Store.mixins;

if (Store.mixins) {
Store.mixins.forEach(function(mixin) {
mixInto(Store.prototype, mixin);
});
}
mixInto(Store.prototype, spec);

return Store;
};
4 changes: 4 additions & 0 deletions addons/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
BaseStore: require('./BaseStore'),
createStore: require('./createStore')
};
36 changes: 20 additions & 16 deletions docs/dispatchr.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# Dispatcher API
# Dispatchr API

## Constructor(context)
Dispatchr has one main function that is exported: `createDispatcher(options)`. This returns a new [`Dispatcher`](#dispatcher-api) instance. `createDispatcher` takes the following `options`:

Creates a new Dispatcher instance with the following parameters:
* `options.stores`: An array of stores to register automatically

* `context`: A context object that will be made available to all stores. Useful for request or session level settings.

## Static Methods
## Dispatcher API

### registerStore(storeClass)

A static method to register stores to the Dispatcher class making them available to handle actions and be accessible through `getStore` on Dispatchr instances.
A static method to register stores to the dispatcher making them available to handle actions and be accessible through `getStore` on the dispatcher context.

### createContext(contextOptions)

Creates a new dispatcher [context](#context-api) that isolates stores and dispatches with the following parameters:

* `contextOptions`: A context object that will be made available to all stores. Useful for request or session level settings.

## Instance Methods
## Context API

### dispatch(actionName, payload)

Expand All @@ -21,27 +25,27 @@ Dispatches an action, in turn calling all stores that have registered to handle
* `actionName`: The name of the action to handle (should map to store action handlers)
* `payload`: An object containing action information.

### getStore(storeClass)
#### getStore(storeClass)

Retrieve a store instance by class. Allows access to stores from components or stores from other stores.

```js
var store = require('./stores/MessageStore');
dispatcher.getStore(store);
var MessageStore = require('./stores/MessageStore');
dispatcher.getStore(MessageStore);
```

### waitFor(storeClasses, callback)
#### waitFor(storeClasses, callback)

Waits for another store's handler to finish before calling the callback. This is useful from within stores if they need to wait for other stores to finish first.

* `storeClasses`: An array of store classes to wait for
* `callback`: Called after all stores have fully handled the action

### dehydrate()
#### dehydrate()

Returns a serializable object containing the state of the Dispatchr instance as well as all stores that have been used since instantiation. This is useful for serializing the state of the application to send it to the client.
Returns a serializable object containing the state of the dispatcher context as well as all stores that have been used since instantiation. This is useful for serializing the state of the application to send it to the client.

### rehydrate(dispatcherState)
#### rehydrate(dispatcherState)

Takes an object representing the state of the Dispatchr instance (usually retrieved from dehydrate) to rehydrate the instance as well as the store instance state.
Takes an object representing the state of the dispatcher context (usually retrieved from dehydrate) to rehydrate the instance as well as the store instance state.

89 changes: 89 additions & 0 deletions docs/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,92 @@ ExampleStore.prototype.shouldDehydrate = function () {
return true;
}
```

## Helper Utilities

### BaseStore

A base class that you can extend to reduce boilerplate when creating stores.

```js
var BaseStore = require('fluxible/addons').BaseStore;
```

#### Built-In Methods

* `emitChange()` - emits a 'change' event
* `getContext()` - returns the [store context](FluxibleContext.md#store-context)
* `addChangeListener(callback)` - simple method to add a change listener
* `removeChangeListener(callback)` - removes a change listener
* `shouldDehydrate()` - default implementation that returns true if a `change` event has been emitted

```js
var BaseStore = require('fluxible/addons').BaseStore;
var util = require('util');

function ApplicationStore(dispatcher) {
BaseStore.apply(this, arguments);
this.currentPageName = null;
}

ApplicationStore.storeName = 'ApplicationStore';
ApplicationStore.handlers = {
'RECEIVE_PAGE': 'handleReceivePage'
};

util.inherits(ApplicationStore, BaseStore);

ApplicationStore.prototype.handleReceivePage = function (payload) {
this.currentPageName = payload.pageName;
this.emitChange();
};

ApplicationStore.prototype.getCurrentPageName = function () {
return this.currentPageName;
};

// For sending state to the client
ApplicationStore.prototype.dehydrate = function () {
return {
currentPageName: this.currentPageName
};
};

// For rehydrating server state
ApplicationStore.prototype.rehydrate = function (state) {
this.currentPageName = state.currentPageName;
};

module.exports = ApplicationStore;

```

### createStore

A helper method similar to `React.createClass` but for creating stores that extend `BaseStore`. Also supports mixins.

```js
var createStore = require('fluxible/addons').createStore;

module.exports = createStore({
storeName: 'ApplicationStore',
handlers: {
'RECEIVE_PAGE': 'handleReceivePage'
},
handleReceivePage: function (payload) {
this.currentPageName = payload.pageName;
this.emitChange();
},
getCurrentPage: function () {
return this.currentPageName;
},
dehydrate: function () {
return {
currentPageName: this.currentPageName
};
},
rehydrate: function (state) {
this.currentPageName = state.currentPageName;
}
});

Loading

0 comments on commit d697710

Please sign in to comment.