├── examples
└── todomvc
│ ├── asset
│ ├── css
│ │ └── app.css
│ ├── jsx
│ │ └── index.jsx
│ └── js
│ │ └── index.js
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ └── Gruntfile.coffee
├── asset
└── delorean-logo.png
├── src
├── delorean.coffee
├── store.coffee
├── flux.coffee
├── dispatcher.coffee
└── mixin.coffee
├── package.json
├── bower.json
├── .gitignore
├── Gruntfile.coffee
├── README.md
└── dist
├── delorean.min.js
└── delorean.js
/examples/todomvc/asset/css/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/asset/delorean-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krasimir/delorean/master/asset/delorean-logo.png
--------------------------------------------------------------------------------
/examples/todomvc/README.md:
--------------------------------------------------------------------------------
1 | # DeLorean.js TodoMVC Example
2 |
3 | ```bash
4 | grunt
5 | open index.html
6 | ```
7 |
--------------------------------------------------------------------------------
/src/delorean.coffee:
--------------------------------------------------------------------------------
1 | # Exporting flux as `DeLorean.Flux`
2 | # This file is the entry point of the library.
3 | DeLorean =
4 | Flux: require './flux.coffee'
5 |
6 | module.exports = DeLorean
7 |
--------------------------------------------------------------------------------
/examples/todomvc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React + Flux with DeLorean.js
6 |
7 |
8 | Add Random Todo
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/todomvc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "delorean-todomvc",
3 | "version": "0.0.0",
4 | "description": "DeLorean.js TodoMVC Example",
5 | "author": "Fatih Kadir Akin",
6 | "license": "MIT",
7 | "devDependencies": {
8 | "grunt": "^0.4.5",
9 | "grunt-browserify": "^2.1.4",
10 | "grunt-react": "^0.9.0"
11 | },
12 | "dependencies": {
13 | "director": "^1.2.3",
14 | "react": "^0.11.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/todomvc/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (grunt)->
2 |
3 | grunt.loadNpmTasks 'grunt-react'
4 | grunt.loadNpmTasks 'grunt-browserify'
5 |
6 | grunt.initConfig
7 |
8 | react:
9 | example:
10 | files:
11 | 'asset/js/index.js': 'asset/jsx/index.jsx'
12 |
13 | browserify:
14 | example:
15 | files:
16 | 'asset/js/app.js': ['asset/js/index.js']
17 |
18 | grunt.registerTask 'default', ['react:example', 'browserify:example']
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "delorean.js",
3 | "version": "0.2.4",
4 | "description": "Flux Library",
5 | "main": "dist/delorean.min.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Fatih Kadir Akin",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "coffeeify": "^0.7.0",
13 | "grunt": "^0.4.5",
14 | "grunt-browserify": "^2.1.4",
15 | "grunt-contrib-uglify": "^0.5.1",
16 | "grunt-react": "^0.9.0"
17 | },
18 | "dependencies": {
19 | "es6-promise": "^1.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "delorean",
3 | "version": "0.0.0",
4 | "homepage": "https://github.com/f/delorean",
5 | "authors": [
6 | "f "
7 | ],
8 | "description": "Flux library",
9 | "main": "dist/delorean.min.js",
10 | "moduleType": [
11 | "node"
12 | ],
13 | "keywords": [
14 | "flux",
15 | "delorean",
16 | "react"
17 | ],
18 | "license": "MIT",
19 | "ignore": [
20 | "**/.*",
21 | "node_modules",
22 | "bower_components",
23 | "test",
24 | "tests",
25 | "**/*.coffee",
26 | "src",
27 | "example"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/store.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 |
3 | # Stores are simple observable structures.
4 | class Store extends EventEmitter
5 |
6 | constructor: (@store)->
7 | super
8 | @bindActions store.actions
9 |
10 | bindActions: (actions)->
11 | @store.emit = @emit.bind this
12 |
13 | for own actionName, callback of actions
14 | @on "action:#{actionName}", @store[callback].bind @store
15 |
16 | dispatchAction: (actionName, data)->
17 | @emit "action:#{actionName}", data
18 |
19 | onChange: (callback)->
20 | @on 'change', callback
21 |
22 | module.exports = Store
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Deployed apps should consider commenting this line out:
24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 | node_modules
26 |
--------------------------------------------------------------------------------
/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (grunt)->
2 |
3 | grunt.loadNpmTasks 'grunt-contrib-uglify'
4 | grunt.loadNpmTasks 'grunt-react'
5 | grunt.loadNpmTasks 'grunt-browserify'
6 |
7 | grunt.initConfig
8 |
9 | # Uglification
10 | uglify:
11 | build:
12 | files:
13 | 'dist/delorean.min.js': ['dist/delorean.js']
14 |
15 | browserify:
16 | coffeeify:
17 | options:
18 | bundleOptions:
19 | standalone: 'DeLorean'
20 | transform: ['coffeeify']
21 |
22 | files:
23 | 'dist/delorean.js': ['src/delorean.coffee']
24 |
25 | grunt.registerTask 'default', ['browserify:coffeeify', 'uglify']
26 |
--------------------------------------------------------------------------------
/src/flux.coffee:
--------------------------------------------------------------------------------
1 | Store = require './store.coffee'
2 | Dispatcher = require './dispatcher.coffee'
3 |
4 | class Flux
5 |
6 | # Generates new store using Store class
7 | # Stores are global and not connected directly
8 | # to the Views
9 | @createStore: (store)->
10 | new Store store
11 |
12 | # Dispatchers are actually Action sets, it has getStores
13 | # function. There should be stores for a complete
14 | # Flux structure.
15 | @createDispatcher: (actions)->
16 | dispatcher = new Dispatcher actions.getStores?()
17 | for own action, callback of actions
18 | unless action is 'getStores'
19 | dispatcher.registerAction action, callback
20 | dispatcher
21 |
22 | # Mixins can be defined in `mixin.coffee` file.
23 | Flux.mixins = require './mixin.coffee'
24 | module.exports = Flux
25 |
--------------------------------------------------------------------------------
/src/dispatcher.coffee:
--------------------------------------------------------------------------------
1 | # Dispatchers are actually Action sets, it has getStores
2 | # function. There should be stores for a complete
3 | # Flux structure.
4 | {EventEmitter} = require 'events'
5 | {Promise} = require 'es6-promise'
6 |
7 | class Dispatcher extends EventEmitter
8 |
9 | constructor: (@stores)->
10 |
11 | # This method can be called from an action method
12 | # Sends action to the all related stores.
13 | dispatch: (actionName, data)->
14 | deferred = @waitFor (store for storeName, store of @stores)
15 | for own storeName, store of @stores
16 | store.dispatchAction actionName, data
17 |
18 | return deferred
19 |
20 | waitFor: (stores)->
21 | promises = for store in stores
22 | new Promise (resolve, reject)->
23 | store.once 'change', resolve
24 |
25 | Promise.all(promises).then =>
26 | @emit 'change:all'
27 |
28 | # Generates new method on the instance
29 | registerAction: (actionName, callback)->
30 | @[actionName] = callback.bind this
31 |
32 | module.exports = Dispatcher
33 |
--------------------------------------------------------------------------------
/src/mixin.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | # It should be inserted to the React components which
3 | # used in Flux.
4 | # Simply `mixin: [Flux.mixins.storeListener]` will work.
5 | storeListener:
6 | # After the component mounted, listen changes of the related stores
7 | componentDidMount: ->
8 | for own storeName, store of @stores
9 | do (store, storeName) =>
10 | store.onChange =>
11 | # call the components `storeDidChanged` method
12 | @storeDidChanged? storeName
13 | # change state
14 | if state = store.store.getState?()
15 | @state.stores[storeName] = state
16 | @forceUpdate()
17 |
18 | getInitialState: ->
19 | # Some shortcuts
20 | @dispatcher = @props.dispatcher
21 | @dispatcher.on 'change:all', =>
22 | @storesDidChanged?()
23 |
24 | @stores = @dispatcher.stores
25 |
26 | state = stores: {}
27 | # more shortcuts for the state
28 | for own storeName of @stores
29 | state.stores[storeName] = @stores[storeName].store.getState?()
30 | state
31 |
--------------------------------------------------------------------------------
/examples/todomvc/asset/jsx/index.jsx:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | var React = require('react');
4 | var Flux = require('../../../../').Flux;
5 | var Router = require('director').Router;
6 |
7 | /* Generate Generic Store */
8 |
9 | var TodoStore = Flux.createStore({
10 |
11 | todos: [
12 | {text: 'hello'},
13 | {text: 'world'}
14 | ],
15 |
16 | actions: {
17 | 'todo:add': 'addTodo',
18 | 'todo:remove': 'removeTodo'
19 | },
20 |
21 | addTodo: function (todo) {
22 | this.todos.push({text: todo.text});
23 | this.emit('change');
24 | },
25 |
26 | removeTodo: function (todoToComplete) {
27 | this.todos = this.todos.filter(function (todo) {
28 | return todoToComplete.text !== todo.text
29 | });
30 | this.emit('change');
31 | },
32 |
33 | getState: function () {
34 | return {
35 | todos: this.todos
36 | }
37 | }
38 | });
39 |
40 | /* Generate List dispatcher with TodoStore. */
41 |
42 | var TodoListDispatcher = Flux.createDispatcher({
43 |
44 | removeTodo: function (todo) {
45 | if (confirm('Do you really want to delete this todo?')) {
46 | this.dispatch('todo:remove', todo)
47 | .then(function () {
48 | alert('Item is deleted successfully.');
49 | });
50 | }
51 | },
52 |
53 | getStores: function () {
54 | return {
55 | todoStore: TodoStore
56 | }
57 | }
58 |
59 | });
60 |
61 | /* Generate Todo Form dispatcher with TodoStore. */
62 |
63 | var TodoFormDispatcher = Flux.createDispatcher({
64 |
65 | addTodo: function (todo) {
66 | this.dispatch('todo:add', todo);
67 | },
68 |
69 | getStores: function () {
70 | return {
71 | todoStore: TodoStore
72 | }
73 | }
74 |
75 | });
76 |
77 | /* Static Dispatcher */
78 |
79 | var TodoDispatcher = Flux.createDispatcher({
80 |
81 | getStores: function () {
82 | return {
83 | todoStore: TodoStore
84 | }
85 | }
86 |
87 | });
88 |
89 | /* React Components */
90 |
91 | var TodoItemView = React.createClass({
92 |
93 | render: function (todo) {
94 | return {this.props.todo.text}
95 | },
96 |
97 | handleClick: function () {
98 | this.props.dispatcher.removeTodo(this.props.todo);
99 | }
100 |
101 | });
102 |
103 | var TodoListView = React.createClass({
104 |
105 | mixins: [Flux.mixins.storeListener],
106 |
107 | render: function () {
108 | var self = this;
109 |
110 | return
111 | {this.stores.todoStore.store.todos.map(function (todo) {
112 | return
113 | })}
114 |
115 | }
116 |
117 | });
118 |
119 | var TodoFormView = React.createClass({
120 |
121 | mixins: [Flux.mixins.storeListener],
122 |
123 | render: function () {
124 | var self = this;
125 | return
128 | },
129 |
130 | handleChange: function (e) {
131 | this.setState({todo: e.target.value});
132 | },
133 |
134 | handleSubmit: function (e) {
135 | e.preventDefault();
136 | this.dispatcher.addTodo({text: this.state.todo});
137 | this.setState({todo: ''});
138 | }
139 |
140 | });
141 |
142 | var ApplicationView = React.createClass({
143 |
144 | mixins: [Flux.mixins.storeListener],
145 |
146 | render: function () {
147 | var self = this;
148 | return
149 |
150 |
151 | There are {this.stores.todoStore.store.todos.length} todos.
152 |
153 | }
154 |
155 | });
156 |
157 | var mainView = React.renderComponent(,
158 | document.getElementById('main'))
159 |
160 | var appRouter = new Router({
161 | '/random': function () {
162 | mainView.dispatcher.dispatch('todo:add', {text: Math.random()});
163 | location.hash = '/';
164 | }
165 | });
166 |
167 | appRouter.init('/');
168 |
--------------------------------------------------------------------------------
/examples/todomvc/asset/js/index.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | var React = require('react');
4 | var Flux = require('../../../../').Flux;
5 | var Router = require('director').Router;
6 |
7 | /* Generate Generic Store */
8 |
9 | var TodoStore = Flux.createStore({
10 |
11 | todos: [
12 | {text: 'hello'},
13 | {text: 'world'}
14 | ],
15 |
16 | actions: {
17 | 'todo:add': 'addTodo',
18 | 'todo:remove': 'removeTodo'
19 | },
20 |
21 | addTodo: function (todo) {
22 | this.todos.push({text: todo.text});
23 | this.emit('change');
24 | },
25 |
26 | removeTodo: function (todoToComplete) {
27 | this.todos = this.todos.filter(function (todo) {
28 | return todoToComplete.text !== todo.text
29 | });
30 | this.emit('change');
31 | },
32 |
33 | getState: function () {
34 | return {
35 | todos: this.todos
36 | }
37 | }
38 | });
39 |
40 | /* Generate List dispatcher with TodoStore. */
41 |
42 | var TodoListDispatcher = Flux.createDispatcher({
43 |
44 | removeTodo: function (todo) {
45 | if (confirm('Do you really want to delete this todo?')) {
46 | this.dispatch('todo:remove', todo)
47 | .then(function () {
48 | alert('Item is deleted successfully.');
49 | });
50 | }
51 | },
52 |
53 | getStores: function () {
54 | return {
55 | todoStore: TodoStore
56 | }
57 | }
58 |
59 | });
60 |
61 | /* Generate Todo Form dispatcher with TodoStore. */
62 |
63 | var TodoFormDispatcher = Flux.createDispatcher({
64 |
65 | addTodo: function (todo) {
66 | this.dispatch('todo:add', todo);
67 | },
68 |
69 | getStores: function () {
70 | return {
71 | todoStore: TodoStore
72 | }
73 | }
74 |
75 | });
76 |
77 | /* Static Dispatcher */
78 |
79 | var TodoDispatcher = Flux.createDispatcher({
80 |
81 | getStores: function () {
82 | return {
83 | todoStore: TodoStore
84 | }
85 | }
86 |
87 | });
88 |
89 | /* React Components */
90 |
91 | var TodoItemView = React.createClass({displayName: 'TodoItemView',
92 |
93 | render: function (todo) {
94 | return React.DOM.li({onClick: this.handleClick}, this.props.todo.text)
95 | },
96 |
97 | handleClick: function () {
98 | this.props.dispatcher.removeTodo(this.props.todo);
99 | }
100 |
101 | });
102 |
103 | var TodoListView = React.createClass({displayName: 'TodoListView',
104 |
105 | mixins: [Flux.mixins.storeListener],
106 |
107 | render: function () {
108 | var self = this;
109 |
110 | return React.DOM.ul(null,
111 | this.stores.todoStore.store.todos.map(function (todo) {
112 | return TodoItemView({dispatcher: self.dispatcher, todo: todo})
113 | })
114 | )
115 | }
116 |
117 | });
118 |
119 | var TodoFormView = React.createClass({displayName: 'TodoFormView',
120 |
121 | mixins: [Flux.mixins.storeListener],
122 |
123 | render: function () {
124 | var self = this;
125 | return React.DOM.form({onSubmit: this.handleSubmit},
126 | React.DOM.input({value: this.state.todo, onChange: this.handleChange})
127 | )
128 | },
129 |
130 | handleChange: function (e) {
131 | this.setState({todo: e.target.value});
132 | },
133 |
134 | handleSubmit: function (e) {
135 | e.preventDefault();
136 | this.dispatcher.addTodo({text: this.state.todo});
137 | this.setState({todo: ''});
138 | }
139 |
140 | });
141 |
142 | var ApplicationView = React.createClass({displayName: 'ApplicationView',
143 |
144 | mixins: [Flux.mixins.storeListener],
145 |
146 | render: function () {
147 | var self = this;
148 | return React.DOM.div(null,
149 | TodoListView({dispatcher: TodoListDispatcher}),
150 | TodoFormView({dispatcher: TodoFormDispatcher}),
151 | React.DOM.span(null, "There are ", this.stores.todoStore.store.todos.length, " todos.")
152 | )
153 | }
154 |
155 | });
156 |
157 | var mainView = React.renderComponent(ApplicationView({dispatcher: TodoDispatcher}),
158 | document.getElementById('main'))
159 |
160 | var appRouter = new Router({
161 | '/random': function () {
162 | mainView.dispatcher.dispatch('todo:add', {text: Math.random()});
163 | location.hash = '/';
164 | }
165 | });
166 |
167 | appRouter.init('/');
168 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # DeLorean.js
4 |
5 | DeLorean is a tiny Flux pattern implementation.
6 |
7 | ## What is Flux
8 |
9 | Data in a Flux application flows in a single direction, in a cycle:
10 |
11 | ```
12 | Views ---> (actions) ----> Dispatcher ---> (registered callback) ---> Stores -------+
13 | Ʌ |
14 | | V
15 | +-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+
16 | ```
17 |
18 | ## Install
19 |
20 | You can install **DeLorean** with Bower:
21 |
22 | ```bash
23 | bower install delorean
24 | ```
25 |
26 | You can also install by NPM to use with **Browserify** *(recommended)*
27 |
28 | ```bash
29 | npm install delorean.js
30 | ```
31 |
32 | ## Usage
33 |
34 | ```js
35 | var Flux = require('delorean.js').Flux;
36 | ```
37 |
38 | ## Stores
39 |
40 | > Stores contain the application state and logic. Their role is somewhat similar
41 | > to a model in a traditional MVC, but they manage the state of many objects —
42 | > they are not instances of one object. Nor are they the same as Backbone's
43 | > collections. More than simply managing a collection of ORM-style objects,
44 | > stores manage the application state for a particular domain within the application.
45 |
46 | ### `Flux.createStore`
47 |
48 | ```js
49 | var TodoStore = Flux.createStore({
50 |
51 | todos: [
52 | {text: 'hello'},
53 | {text: 'world'}
54 | ],
55 |
56 | actions: {
57 | 'todo:add': 'addTodo',
58 | 'todo:remove': 'removeTodo'
59 | },
60 |
61 | addTodo: function (todo) {
62 | this.todos.push({text: todo.text});
63 | this.emit('change');
64 | },
65 |
66 | removeTodo: function (todoToComplete) {
67 | this.todos = this.todos.filter(function (todo) {
68 | return todoToComplete.text !== todo.text
69 | });
70 | this.emit('change');
71 | },
72 |
73 | getState: function () {
74 | return {
75 | todos: this.todos
76 | }
77 | }
78 | });
79 | ```
80 |
81 | ## Dispatcher
82 |
83 | > The dispatcher is the central hub that manages all data flow in a Flux application.
84 | > It is essentially a registry of callbacks into the stores. Each store registers
85 | > itself and provides a callback. When the dispatcher responds to an action,
86 | > all stores in the application are sent the data payload provided by the
87 | > action via the callbacks in the registry.
88 |
89 | ### `Flux.createDispatcher`
90 |
91 | ```js
92 | var TodoListApp = Flux.createDispatcher({
93 |
94 | removeTodo: function (todo) {
95 | if (confirm('Do you really want to delete this todo?')) {
96 | this.dispatch('todo:remove', todo);
97 | }
98 | },
99 |
100 | getStores: function () {
101 | return {
102 | todoStore: TodoStore
103 | }
104 | }
105 |
106 | });
107 | ```
108 |
109 | #### Action `dispatch`
110 |
111 | When an action is dispatched, all the stores know about the status and they
112 | process the data asynchronously. When all of them are finished the dispatcher
113 | emits `change:all` event, also `dispatch` method returns a promise.
114 |
115 | ```js
116 | var TodoListApp = Flux.createDispatcher({
117 |
118 | removeTodo: function (todo) {
119 | if (confirm('Do you really want to delete this todo?')) {
120 | this.dispatch('todo:remove', todo)
121 | .then(function () {
122 | // All of the stores finished the process
123 | // about 'todo:remove' action
124 | alert('Item removed successfully');
125 | });
126 | }
127 | },
128 |
129 | getStores: function () {
130 | return {
131 | todoStore: TodoStore
132 | }
133 | }
134 |
135 | });
136 | ```
137 |
138 | ## Combining to React
139 |
140 | You may bring all the flow together with the Views, actually *the Action generators*.
141 | You should use **`Flux.mixins.storeListener`** mixin to get a view into the Flux system.
142 | Also you should pass `dispatcher={DispatcherName}` attribute to React view.
143 |
144 | ```js
145 | // Child views don't have to have storeListener.
146 |
147 | var TodoItemView = React.createClass({
148 |
149 | render: function (todo) {
150 | return {this.props.todo.text}
151 | },
152 |
153 | handleClick: function () {
154 | this.props.dispatcher.removeTodo(this.props.todo);
155 | }
156 |
157 | });
158 |
159 | var TodoListView = React.createClass({
160 |
161 | mixins: [Flux.mixins.storeListener],
162 |
163 | render: function () {
164 | var self = this;
165 | return
166 | {this.stores.todoStore.store.todos.map(function (todo) {
167 | return
168 | })}
169 |
170 | }
171 |
172 | });
173 | ```
174 |
175 | ### `storeDidChanged` and `storesDidChanged`
176 |
177 | Two functions are triggered when a store changed and all stores are changed. You can use
178 | these functions if your application needs.
179 |
180 | ```js
181 | var TodoListView = React.createClass({
182 |
183 | mixins: [Flux.mixins.storeListener],
184 |
185 | // when all stores are updated
186 | storesDidChanged: function () {
187 | console.log("All stores are now updated.");
188 | },
189 |
190 | // when a store updates
191 | storeDidChanged: function (storeName) {
192 | console.log(storeName + " store is now updated.");
193 | },
194 |
195 | render: function () {
196 | // ...
197 | }
198 |
199 | });
200 | ```
201 |
202 | ## Routing
203 |
204 | You can use any Router tool with DeLorean. In the example I use `director` as the router.
205 |
206 | ```js
207 | var Router = require('director').Router;
208 | ```
209 |
210 | You may trig the action from View. So you can just do something like that:
211 |
212 | ```js
213 | var mainView = React.renderComponent(,
214 | document.getElementById('main'))
215 |
216 | var appRouter = new Router({
217 | '/random': function () {
218 | mainView.dispatcher.dispatch('todo:add', {text: Math.random()});
219 | location.hash = '/';
220 | }
221 | });
222 | ```
223 |
224 | ## Running the TodoMVC example
225 |
226 | There is a simple TodoMVC example working with DeLorean.js
227 |
228 | ```bash
229 | cd examples/todomvc
230 | grunt
231 | open index.html
232 | ```
233 |
234 | ## Todo
235 |
236 | - Improve Readme.
237 |
238 | ## Name
239 |
240 | The **flux capacitor** was the core component of Doctor Emmett Brown's time traveling **DeLorean time machine**
241 |
242 | ## License
243 |
244 | [MIT License](http://f.mit-license.org)
245 |
--------------------------------------------------------------------------------
/dist/delorean.min.js:
--------------------------------------------------------------------------------
1 | !function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.DeLorean=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;ga||isNaN(a))throw TypeError("n must be a positive number");return this._maxListeners=a,this},c.prototype.emit=function(a){var b,c,e,h,i,j;if(this._events||(this._events={}),"error"===a&&(!this._events.error||f(this._events.error)&&!this._events.error.length))throw b=arguments[1],b instanceof Error?b:TypeError('Uncaught, unspecified "error" event.');if(c=this._events[a],g(c))return!1;if(d(c))switch(arguments.length){case 1:c.call(this);break;case 2:c.call(this,arguments[1]);break;case 3:c.call(this,arguments[1],arguments[2]);break;default:for(e=arguments.length,h=new Array(e-1),i=1;e>i;i++)h[i-1]=arguments[i];c.apply(this,h)}else if(f(c)){for(e=arguments.length,h=new Array(e-1),i=1;e>i;i++)h[i-1]=arguments[i];for(j=c.slice(),e=j.length,i=0;e>i;i++)j[i].apply(this,h)}return!0},c.prototype.addListener=function(a,b){var e;if(!d(b))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",a,d(b.listener)?b.listener:b),this._events[a]?f(this._events[a])?this._events[a].push(b):this._events[a]=[this._events[a],b]:this._events[a]=b,f(this._events[a])&&!this._events[a].warned){var e;e=g(this._maxListeners)?c.defaultMaxListeners:this._maxListeners,e&&e>0&&this._events[a].length>e&&(this._events[a].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[a].length),"function"==typeof console.trace&&console.trace())}return this},c.prototype.on=c.prototype.addListener,c.prototype.once=function(a,b){function c(){this.removeListener(a,c),e||(e=!0,b.apply(this,arguments))}if(!d(b))throw TypeError("listener must be a function");var e=!1;return c.listener=b,this.on(a,c),this},c.prototype.removeListener=function(a,b){var c,e,g,h;if(!d(b))throw TypeError("listener must be a function");if(!this._events||!this._events[a])return this;if(c=this._events[a],g=c.length,e=-1,c===b||d(c.listener)&&c.listener===b)delete this._events[a],this._events.removeListener&&this.emit("removeListener",a,b);else if(f(c)){for(h=g;h-->0;)if(c[h]===b||c[h].listener&&c[h].listener===b){e=h;break}if(0>e)return this;1===c.length?(c.length=0,delete this._events[a]):c.splice(e,1),this._events.removeListener&&this.emit("removeListener",a,b)}return this},c.prototype.removeAllListeners=function(a){var b,c;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[a]&&delete this._events[a],this;if(0===arguments.length){for(b in this._events)"removeListener"!==b&&this.removeAllListeners(b);return this.removeAllListeners("removeListener"),this._events={},this}if(c=this._events[a],d(c))this.removeListener(a,c);else for(;c.length;)this.removeListener(a,c[c.length-1]);return delete this._events[a],this},c.prototype.listeners=function(a){var b;return b=this._events&&this._events[a]?d(this._events[a])?[this._events[a]]:this._events[a].slice():[]},c.listenerCount=function(a,b){var c;return c=a._events&&a._events[b]?d(a._events[b])?1:a._events[b].length:0}},{}],12:[function(a,b){function c(){}var d=b.exports={};d.nextTick=function(){var a="undefined"!=typeof window&&window.setImmediate,b="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(a)return function(a){return window.setImmediate(a)};if(b){var c=[];return window.addEventListener("message",function(a){var b=a.source;if((b===window||null===b)&&"process-tick"===a.data&&(a.stopPropagation(),c.length>0)){var d=c.shift();d()}},!0),function(a){c.push(a),window.postMessage("process-tick","*")}}return function(a){setTimeout(a,0)}}(),d.title="browser",d.browser=!0,d.env={},d.argv=[],d.on=c,d.addListener=c,d.once=c,d.off=c,d.removeListener=c,d.removeAllListeners=c,d.emit=c,d.binding=function(){throw new Error("process.binding is not supported")},d.cwd=function(){return"/"},d.chdir=function(){throw new Error("process.chdir is not supported")}},{}],13:[function(a,b){var c;c={Flux:a("./flux.coffee")},b.exports=c},{"./flux.coffee":15}],14:[function(a,b){var c,d,e,f={}.hasOwnProperty,g=function(a,b){function c(){this.constructor=a}for(var d in b)f.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};d=a("events").EventEmitter,e=a("es6-promise").Promise,c=function(a){function b(a){this.stores=a}return g(b,a),b.prototype.dispatch=function(a,b){var c,d,e,g;c=this.waitFor(function(){var a,b;a=this.stores,b=[];for(e in a)d=a[e],b.push(d);return b}.call(this)),g=this.stores;for(e in g)f.call(g,e)&&(d=g[e],d.dispatchAction(a,b));return c},b.prototype.waitFor=function(a){var b,c;return b=function(){var b,d,f;for(f=[],b=0,d=a.length;d>b;b++)c=a[b],f.push(new e(function(a){return c.once("change",a)}));return f}(),e.all(b).then(function(a){return function(){return a.emit("change:all")}}(this))},b.prototype.registerAction=function(a,b){return this[a]=b.bind(this)},b}(d),b.exports=c},{"es6-promise":1,events:11}],15:[function(a,b){var c,d,e,f={}.hasOwnProperty;e=a("./store.coffee"),c=a("./dispatcher.coffee"),d=function(){function a(){}return a.createStore=function(a){return new e(a)},a.createDispatcher=function(a){var b,d,e;e=new c("function"==typeof a.getStores?a.getStores():void 0);for(b in a)f.call(a,b)&&(d=a[b],"getStores"!==b&&e.registerAction(b,d));return e},a}(),d.mixins=a("./mixin.coffee"),b.exports=d},{"./dispatcher.coffee":14,"./mixin.coffee":16,"./store.coffee":17}],16:[function(a,b){var c={}.hasOwnProperty;b.exports={storeListener:{componentDidMount:function(){var a,b,d,e;d=this.stores,e=[];for(b in d)c.call(d,b)&&(a=d[b],e.push(function(a){return function(b,c){return b.onChange(function(){var d,e;return"function"==typeof a.storeDidChanged&&a.storeDidChanged(c),(d="function"==typeof(e=b.store).getState?e.getState():void 0)?(a.state.stores[c]=d,a.forceUpdate()):void 0})}}(this)(a,b)));return e},getInitialState:function(){var a,b,d,e;this.dispatcher=this.props.dispatcher,this.dispatcher.on("change:all",function(a){return function(){return"function"==typeof a.storesDidChanged?a.storesDidChanged():void 0}}(this)),this.stores=this.dispatcher.stores,a={stores:{}},e=this.stores;for(b in e)c.call(e,b)&&(a.stores[b]="function"==typeof(d=this.stores[b].store).getState?d.getState():void 0);return a}}}},{}],17:[function(a,b){var c,d,e={}.hasOwnProperty,f=function(a,b){function c(){this.constructor=a}for(var d in b)e.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};c=a("events").EventEmitter,d=function(a){function b(a){this.store=a,b.__super__.constructor.apply(this,arguments),this.bindActions(a.actions)}return f(b,a),b.prototype.bindActions=function(a){var b,c,d;this.store.emit=this.emit.bind(this),d=[];for(b in a)e.call(a,b)&&(c=a[b],d.push(this.on("action:"+b,this.store[c].bind(this.store))));return d},b.prototype.dispatchAction=function(a,b){return this.emit("action:"+a,b)},b.prototype.onChange=function(a){return this.on("change",a)},b}(c),b.exports=d},{events:11}]},{},[13])(13)});
--------------------------------------------------------------------------------
/dist/delorean.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.DeLorean=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._events[type].length > m) {
754 | this._events[type].warned = true;
755 | console.error('(node) warning: possible EventEmitter memory ' +
756 | 'leak detected. %d listeners added. ' +
757 | 'Use emitter.setMaxListeners() to increase limit.',
758 | this._events[type].length);
759 | if (typeof console.trace === 'function') {
760 | // not supported in IE 10
761 | console.trace();
762 | }
763 | }
764 | }
765 |
766 | return this;
767 | };
768 |
769 | EventEmitter.prototype.on = EventEmitter.prototype.addListener;
770 |
771 | EventEmitter.prototype.once = function(type, listener) {
772 | if (!isFunction(listener))
773 | throw TypeError('listener must be a function');
774 |
775 | var fired = false;
776 |
777 | function g() {
778 | this.removeListener(type, g);
779 |
780 | if (!fired) {
781 | fired = true;
782 | listener.apply(this, arguments);
783 | }
784 | }
785 |
786 | g.listener = listener;
787 | this.on(type, g);
788 |
789 | return this;
790 | };
791 |
792 | // emits a 'removeListener' event iff the listener was removed
793 | EventEmitter.prototype.removeListener = function(type, listener) {
794 | var list, position, length, i;
795 |
796 | if (!isFunction(listener))
797 | throw TypeError('listener must be a function');
798 |
799 | if (!this._events || !this._events[type])
800 | return this;
801 |
802 | list = this._events[type];
803 | length = list.length;
804 | position = -1;
805 |
806 | if (list === listener ||
807 | (isFunction(list.listener) && list.listener === listener)) {
808 | delete this._events[type];
809 | if (this._events.removeListener)
810 | this.emit('removeListener', type, listener);
811 |
812 | } else if (isObject(list)) {
813 | for (i = length; i-- > 0;) {
814 | if (list[i] === listener ||
815 | (list[i].listener && list[i].listener === listener)) {
816 | position = i;
817 | break;
818 | }
819 | }
820 |
821 | if (position < 0)
822 | return this;
823 |
824 | if (list.length === 1) {
825 | list.length = 0;
826 | delete this._events[type];
827 | } else {
828 | list.splice(position, 1);
829 | }
830 |
831 | if (this._events.removeListener)
832 | this.emit('removeListener', type, listener);
833 | }
834 |
835 | return this;
836 | };
837 |
838 | EventEmitter.prototype.removeAllListeners = function(type) {
839 | var key, listeners;
840 |
841 | if (!this._events)
842 | return this;
843 |
844 | // not listening for removeListener, no need to emit
845 | if (!this._events.removeListener) {
846 | if (arguments.length === 0)
847 | this._events = {};
848 | else if (this._events[type])
849 | delete this._events[type];
850 | return this;
851 | }
852 |
853 | // emit removeListener for all listeners on all events
854 | if (arguments.length === 0) {
855 | for (key in this._events) {
856 | if (key === 'removeListener') continue;
857 | this.removeAllListeners(key);
858 | }
859 | this.removeAllListeners('removeListener');
860 | this._events = {};
861 | return this;
862 | }
863 |
864 | listeners = this._events[type];
865 |
866 | if (isFunction(listeners)) {
867 | this.removeListener(type, listeners);
868 | } else {
869 | // LIFO order
870 | while (listeners.length)
871 | this.removeListener(type, listeners[listeners.length - 1]);
872 | }
873 | delete this._events[type];
874 |
875 | return this;
876 | };
877 |
878 | EventEmitter.prototype.listeners = function(type) {
879 | var ret;
880 | if (!this._events || !this._events[type])
881 | ret = [];
882 | else if (isFunction(this._events[type]))
883 | ret = [this._events[type]];
884 | else
885 | ret = this._events[type].slice();
886 | return ret;
887 | };
888 |
889 | EventEmitter.listenerCount = function(emitter, type) {
890 | var ret;
891 | if (!emitter._events || !emitter._events[type])
892 | ret = 0;
893 | else if (isFunction(emitter._events[type]))
894 | ret = 1;
895 | else
896 | ret = emitter._events[type].length;
897 | return ret;
898 | };
899 |
900 | function isFunction(arg) {
901 | return typeof arg === 'function';
902 | }
903 |
904 | function isNumber(arg) {
905 | return typeof arg === 'number';
906 | }
907 |
908 | function isObject(arg) {
909 | return typeof arg === 'object' && arg !== null;
910 | }
911 |
912 | function isUndefined(arg) {
913 | return arg === void 0;
914 | }
915 |
916 | },{}],12:[function(_dereq_,module,exports){
917 | // shim for using process in browser
918 |
919 | var process = module.exports = {};
920 |
921 | process.nextTick = (function () {
922 | var canSetImmediate = typeof window !== 'undefined'
923 | && window.setImmediate;
924 | var canPost = typeof window !== 'undefined'
925 | && window.postMessage && window.addEventListener
926 | ;
927 |
928 | if (canSetImmediate) {
929 | return function (f) { return window.setImmediate(f) };
930 | }
931 |
932 | if (canPost) {
933 | var queue = [];
934 | window.addEventListener('message', function (ev) {
935 | var source = ev.source;
936 | if ((source === window || source === null) && ev.data === 'process-tick') {
937 | ev.stopPropagation();
938 | if (queue.length > 0) {
939 | var fn = queue.shift();
940 | fn();
941 | }
942 | }
943 | }, true);
944 |
945 | return function nextTick(fn) {
946 | queue.push(fn);
947 | window.postMessage('process-tick', '*');
948 | };
949 | }
950 |
951 | return function nextTick(fn) {
952 | setTimeout(fn, 0);
953 | };
954 | })();
955 |
956 | process.title = 'browser';
957 | process.browser = true;
958 | process.env = {};
959 | process.argv = [];
960 |
961 | function noop() {}
962 |
963 | process.on = noop;
964 | process.addListener = noop;
965 | process.once = noop;
966 | process.off = noop;
967 | process.removeListener = noop;
968 | process.removeAllListeners = noop;
969 | process.emit = noop;
970 |
971 | process.binding = function (name) {
972 | throw new Error('process.binding is not supported');
973 | }
974 |
975 | // TODO(shtylman)
976 | process.cwd = function () { return '/' };
977 | process.chdir = function (dir) {
978 | throw new Error('process.chdir is not supported');
979 | };
980 |
981 | },{}],13:[function(_dereq_,module,exports){
982 | var DeLorean;
983 |
984 | DeLorean = {
985 | Flux: _dereq_('./flux.coffee')
986 | };
987 |
988 | module.exports = DeLorean;
989 |
990 |
991 |
992 | },{"./flux.coffee":15}],14:[function(_dereq_,module,exports){
993 | var Dispatcher, EventEmitter, Promise,
994 | __hasProp = {}.hasOwnProperty,
995 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
996 |
997 | EventEmitter = _dereq_('events').EventEmitter;
998 |
999 | Promise = _dereq_('es6-promise').Promise;
1000 |
1001 | Dispatcher = (function(_super) {
1002 | __extends(Dispatcher, _super);
1003 |
1004 | function Dispatcher(stores) {
1005 | this.stores = stores;
1006 | }
1007 |
1008 | Dispatcher.prototype.dispatch = function(actionName, data) {
1009 | var deferred, store, storeName, _ref;
1010 | deferred = this.waitFor((function() {
1011 | var _ref, _results;
1012 | _ref = this.stores;
1013 | _results = [];
1014 | for (storeName in _ref) {
1015 | store = _ref[storeName];
1016 | _results.push(store);
1017 | }
1018 | return _results;
1019 | }).call(this));
1020 | _ref = this.stores;
1021 | for (storeName in _ref) {
1022 | if (!__hasProp.call(_ref, storeName)) continue;
1023 | store = _ref[storeName];
1024 | store.dispatchAction(actionName, data);
1025 | }
1026 | return deferred;
1027 | };
1028 |
1029 | Dispatcher.prototype.waitFor = function(stores) {
1030 | var promises, store;
1031 | promises = (function() {
1032 | var _i, _len, _results;
1033 | _results = [];
1034 | for (_i = 0, _len = stores.length; _i < _len; _i++) {
1035 | store = stores[_i];
1036 | _results.push(new Promise(function(resolve, reject) {
1037 | return store.once('change', resolve);
1038 | }));
1039 | }
1040 | return _results;
1041 | })();
1042 | return Promise.all(promises).then((function(_this) {
1043 | return function() {
1044 | return _this.emit('change:all');
1045 | };
1046 | })(this));
1047 | };
1048 |
1049 | Dispatcher.prototype.registerAction = function(actionName, callback) {
1050 | return this[actionName] = callback.bind(this);
1051 | };
1052 |
1053 | return Dispatcher;
1054 |
1055 | })(EventEmitter);
1056 |
1057 | module.exports = Dispatcher;
1058 |
1059 |
1060 |
1061 | },{"es6-promise":1,"events":11}],15:[function(_dereq_,module,exports){
1062 | var Dispatcher, Flux, Store,
1063 | __hasProp = {}.hasOwnProperty;
1064 |
1065 | Store = _dereq_('./store.coffee');
1066 |
1067 | Dispatcher = _dereq_('./dispatcher.coffee');
1068 |
1069 | Flux = (function() {
1070 | function Flux() {}
1071 |
1072 | Flux.createStore = function(store) {
1073 | return new Store(store);
1074 | };
1075 |
1076 | Flux.createDispatcher = function(actions) {
1077 | var action, callback, dispatcher;
1078 | dispatcher = new Dispatcher(typeof actions.getStores === "function" ? actions.getStores() : void 0);
1079 | for (action in actions) {
1080 | if (!__hasProp.call(actions, action)) continue;
1081 | callback = actions[action];
1082 | if (action !== 'getStores') {
1083 | dispatcher.registerAction(action, callback);
1084 | }
1085 | }
1086 | return dispatcher;
1087 | };
1088 |
1089 | return Flux;
1090 |
1091 | })();
1092 |
1093 | Flux.mixins = _dereq_('./mixin.coffee');
1094 |
1095 | module.exports = Flux;
1096 |
1097 |
1098 |
1099 | },{"./dispatcher.coffee":14,"./mixin.coffee":16,"./store.coffee":17}],16:[function(_dereq_,module,exports){
1100 | var __hasProp = {}.hasOwnProperty;
1101 |
1102 | module.exports = {
1103 | storeListener: {
1104 | componentDidMount: function() {
1105 | var store, storeName, _ref, _results;
1106 | _ref = this.stores;
1107 | _results = [];
1108 | for (storeName in _ref) {
1109 | if (!__hasProp.call(_ref, storeName)) continue;
1110 | store = _ref[storeName];
1111 | _results.push((function(_this) {
1112 | return function(store, storeName) {
1113 | return store.onChange(function() {
1114 | var state, _base;
1115 | if (typeof _this.storeDidChanged === "function") {
1116 | _this.storeDidChanged(storeName);
1117 | }
1118 | if (state = typeof (_base = store.store).getState === "function" ? _base.getState() : void 0) {
1119 | _this.state.stores[storeName] = state;
1120 | return _this.forceUpdate();
1121 | }
1122 | });
1123 | };
1124 | })(this)(store, storeName));
1125 | }
1126 | return _results;
1127 | },
1128 | getInitialState: function() {
1129 | var state, storeName, _base, _ref;
1130 | this.dispatcher = this.props.dispatcher;
1131 | this.dispatcher.on('change:all', (function(_this) {
1132 | return function() {
1133 | return typeof _this.storesDidChanged === "function" ? _this.storesDidChanged() : void 0;
1134 | };
1135 | })(this));
1136 | this.stores = this.dispatcher.stores;
1137 | state = {
1138 | stores: {}
1139 | };
1140 | _ref = this.stores;
1141 | for (storeName in _ref) {
1142 | if (!__hasProp.call(_ref, storeName)) continue;
1143 | state.stores[storeName] = typeof (_base = this.stores[storeName].store).getState === "function" ? _base.getState() : void 0;
1144 | }
1145 | return state;
1146 | }
1147 | }
1148 | };
1149 |
1150 |
1151 |
1152 | },{}],17:[function(_dereq_,module,exports){
1153 | var EventEmitter, Store,
1154 | __hasProp = {}.hasOwnProperty,
1155 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
1156 |
1157 | EventEmitter = _dereq_('events').EventEmitter;
1158 |
1159 | Store = (function(_super) {
1160 | __extends(Store, _super);
1161 |
1162 | function Store(store) {
1163 | this.store = store;
1164 | Store.__super__.constructor.apply(this, arguments);
1165 | this.bindActions(store.actions);
1166 | }
1167 |
1168 | Store.prototype.bindActions = function(actions) {
1169 | var actionName, callback, _results;
1170 | this.store.emit = this.emit.bind(this);
1171 | _results = [];
1172 | for (actionName in actions) {
1173 | if (!__hasProp.call(actions, actionName)) continue;
1174 | callback = actions[actionName];
1175 | _results.push(this.on("action:" + actionName, this.store[callback].bind(this.store)));
1176 | }
1177 | return _results;
1178 | };
1179 |
1180 | Store.prototype.dispatchAction = function(actionName, data) {
1181 | return this.emit("action:" + actionName, data);
1182 | };
1183 |
1184 | Store.prototype.onChange = function(callback) {
1185 | return this.on('change', callback);
1186 | };
1187 |
1188 | return Store;
1189 |
1190 | })(EventEmitter);
1191 |
1192 | module.exports = Store;
1193 |
1194 |
1195 |
1196 | },{"events":11}]},{},[13])
1197 | (13)
1198 | });
--------------------------------------------------------------------------------