- Version 1.4.0 (4 April 2015), added functional tests using Zombie + Mocha.
- Version 1.3.0 (19 March 2015), eslint checks; deploy live demo.
- Version 1.2.0 (23 December 2014), convert app to ES6 using 6to5 transpiler.
- Version 1.1.0 (30 November 2014), persist Todos to LocalStorage.
- Version 1.0.0 (14 November 2014).
This didactic Todo app was written to illustrate the Flux DSV (Dispatch Store View) design pattern.
Flux is a design pattern for building scaleable applications using a Unidirectional Data Flow (it is not a Web development framework), the Flux library implements a Dispatcher. which works well with the Flux design pattern.
The Todo app is implemented using Flux, Backbone and React.
The Flux/React combination results in a highly decoupled declarative application structure. Here is the actual code:
var dispatcher = new Flux.Dispatcher();
var todoStore = new TodoStore([], {dispatcher: dispatcher});
React.render(
<div>
<h3>Todos</h3>
<TodoFormComponent store={todoStore} />
<TodoListComponent store={todoStore} />
<p>
Want a second fully synchronized list? Just declare another list component: no code required,
no events to wire up!
</p>
<TodoListComponent store={todoStore} />
</div>,
document.getElementById('app')
);
Just declare the Dispatcher, Store and View and you're done. The Flux unidirectional Dispatcher->Store->View dependencies are obvious.
-
The
todoStore
is passed thedispatcher
(as a constructor argument) when it is instantiated. -
UI components that display or update the Todo list are passed the
todoStore
(as a property) when they are instantiated.
Displaying a second fully synchronized Todo list is a bit nonsensical but it graphically illustrates the power of the Flux architecture -- try doing this in any other framework.
Data flows unidirectionally in a circular path Dispatcher->Store->View->Dispatcher-> ...:
-
The
todoStore
listens fordispatcher
actions (messages) and updates itself in response to these messages. -
UI components listen for Backbone events from the
todoStore
informing them of changes to the store that need to be displayed in the UI. -
UI components send actions (messages) to the
dispatcher
in response to user input -- components do not mutate the store directly.
- The store is a Black Box containing the application's state and the logic to execute dispatcher actions which update (mutate) the store.
- Externally there is no way to mutate the store other than indirectly via dispatcher actions.
- The store emits change events to subscribers (UI view components).
- The store has no knowledge of its external environment.
Backbone is used to implement a pub/sub data store for the Todos list with Backbone Models and Collections (Backbone is not used as a development framework). Backbone unburdens the app from having to implement a pub/sub event emitter for the store (as well as providing a rich model/collection API).
Storing the component state in a mutative Backbone store instead of
using an immutable component State
object means we need to use the
React
forceUpdate
API which could have performance implications for complex React UIs.
See this excellent
discussion
explains the issue and, if necessary, strategies to resolve it. React
does a great job of DOM update optimization so I would stick with the
simplicity of using forceUpdate
until confronted with a real
use-case to the contrary.
TodoStore
collection and TodoItem
models are passed a Flux
dispatcher when they are instantiated. TodoItem
models are
instantiated by the Backbone Collection add
method which passes the
dispatcher option to the TodoItem
model's initialize
function.
The app is developed and built in a node/npm environment. To install and run:
-
Make sure you have node and npm installed.
-
Clone the Github repo:
git clone https://github.com/srackham/flux-backbone-todo.git
-
Install npm dependencies:
cd flux-backbone-todo npm install
-
Build the app
app/bundle.js
bundle (although JQuery is not required by Backbone I had to include it to satisfy webpack which thought it was a dependency):webpack
-
Start the app in a server:
npm start
-
Open your Web browser at http://localhost:8888/.
-
As always in JavaScript, when you pass a callback you need to ensure that they are bound to the correct context. In the following example the Backbone Model event handler's context is bound to the current object:
this.props.store.on('change', function() { this.forceUpdate(); }.bind(this) );
-
When binding you need to take caller and callee arguments into consideration. The previous example can be simplified by passing
forceUpdate
as the change handler callback, but if you do not remember to explicitly bind theforceUpdate
first argument tonull
the program will throw an error becauseforceUpdate
would be called by the Backbone event dispatcher with a first argument that is not a callback (namely the changed Backbone model):this.props.store.on('change', this.forceUpdate.bind(this, null) );
-
Backbone Model attributes are not model properties -- access them with
get()
andset()
not with the dot syntax. -
I spent far to much time debugging what is a very simple application, most of the time could have been saved if I had been using a language with type checking -- ES6 + JSX + type annotations + type checking would be nice (this is a criticism of JavaScript in general, not of the application architecture or the tools).