diff --git a/lib/dispatcher.js b/lib/dispatcher.js index a5cc50c..da4cd31 100644 --- a/lib/dispatcher.js +++ b/lib/dispatcher.js @@ -9,11 +9,16 @@ var _clone = require("lodash-node/modern/lang/clone"), _findKey = require("lodash-node/modern/object/findKey"), _uniq = require("lodash-node/modern/array/uniq"); +var defaultBatchingFunction = function(callback) { + callback(); +}; + var Dispatcher = function(stores) { this.stores = {}; this.currentDispatch = null; this.currentActionType = null; this.waitingToDispatch = []; + this.batchingFunction = defaultBatchingFunction; for (var key in stores) { if (stores.hasOwnProperty(key)) { @@ -28,6 +33,12 @@ Dispatcher.prototype.addStore = function(name, store) { }; Dispatcher.prototype.dispatch = function(action) { + this.batchingFunction(function() { + this._dispatch(action); + }.bind(this)); +}; + +Dispatcher.prototype._dispatch = function(action) { if (!action || !action.type) { throw new Error("Can only dispatch actions with a 'type' property"); } @@ -141,4 +152,12 @@ Dispatcher.prototype.waitForStores = function(store, stores, fn) { dispatch.waitCallback = fn; }; +Dispatcher.prototype.setBatchingFunction = function(fn) { + if (fn) { + this.batchingFunction = fn; + } else { + this.batchingFunction = defaultBatchingFunction; + } +}; + module.exports = Dispatcher; diff --git a/lib/flux.js b/lib/flux.js index 052e98d..8efa65d 100644 --- a/lib/flux.js +++ b/lib/flux.js @@ -116,4 +116,8 @@ Flux.prototype.addStores = function(stores) { } }; +Flux.prototype.setBatchingFunction = function(fn) { + this.dispatcher.setBatchingFunction(fn); +}; + module.exports = Flux; diff --git a/test/unit/test_batched_updates.js b/test/unit/test_batched_updates.js new file mode 100644 index 0000000..20e2061 --- /dev/null +++ b/test/unit/test_batched_updates.js @@ -0,0 +1,139 @@ +var Fluxxor = require("../../"), + jsdom = require("jsdom"); + +var chai = require("chai"), + expect = chai.expect; + +var Store = Fluxxor.createStore({ + actions: { + "ACTIVATE": "handleActivate", + "LOAD_INITIAL_VALUE": "handleLoadInitialValue" + }, + + initialize: function() { + this.activated = false; + this.value = null; + }, + + handleActivate: function() { + this.activated = true; + this.emit("change"); + }, + + handleLoadInitialValue: function() { + this.value = "testing"; + this.emit("change"); + } +}); + +var actions = { + activate: function(callback) { + setTimeout(function() { + try { + this.dispatch("ACTIVATE"); + callback(); + } catch (ex) { + callback(ex); + } + }.bind(this)); + }, + + loadInitialValue: function() { + this.dispatch("LOAD_INITIAL_VALUE"); + } +}; + +describe("Batching updates", function() { + var React, TestUtils; + var flux, App, ComponentA, ComponentB; + + beforeEach(function() { + global.window = jsdom.jsdom().createWindow(""); + global.document = window.document; + global.navigator = window.navigator; + React = require("react/addons"); + TestUtils = React.addons.TestUtils; + + flux = new Fluxxor.Flux({store: new Store()}, actions); + + App = React.createClass({ + mixins: [Fluxxor.FluxMixin(React), Fluxxor.StoreWatchMixin("store")], + + getStateFromFlux: function() { + return { + activated: this.getFlux().store("store").activated + }; + }, + + render: function() { + if (!this.state.activated) { + return React.createElement(ComponentA); + } else { + return React.createElement(ComponentB); + } + } + }); + + ComponentA = React.createClass({ + mixins: [ + Fluxxor.FluxMixin(React) + ], + + render: function() { + return React.DOM.div(); + } + }); + + ComponentB = React.createClass({ + mixins: [ + Fluxxor.FluxMixin(React), + Fluxxor.StoreWatchMixin("store") + ], + + getStateFromFlux: function() { + return { + value: this.getFlux().store("store").value + }; + }, + + componentWillMount: function() { + this.getFlux().actions.loadInitialValue(); + }, + + render: function() { + return React.DOM.div(); + }, + }); + }); + + afterEach(function() { + delete global.window; + delete global.document; + delete global.navigator; + for (var i in require.cache) { + if (require.cache.hasOwnProperty(i)) { + delete require.cache[i]; // ugh react why + } + } + }); + + it("doesn't batch by default", function(done) { + /* jshint expr:true */ + TestUtils.renderIntoDocument(React.createElement(App, {flux: flux})); + flux.actions.activate(function(err) { + expect(err).to.match(/dispatch.*another action/); + done(); + }); + }); + + it("allows batching", function(done) { + /* jshint expr:true */ + flux.setBatchingFunction(React.addons.batchedUpdates); + + TestUtils.renderIntoDocument(React.createElement(App, {flux: flux})); + flux.actions.activate(function(err) { + expect(err).to.be.undefined; + done(); + }); + }); +});