├── .travis.yml ├── examples ├── q-flux-router │ ├── lib │ │ ├── window.js │ │ ├── q-flux-instance.js │ │ ├── jsx │ │ │ ├── view.jsx │ │ │ ├── about.jsx │ │ │ ├── home.jsx │ │ │ ├── nav.jsx │ │ │ └── page.jsx │ │ ├── q-flux-demo.js │ │ ├── q-flux-routeDataStore.js │ │ └── q-flux-router.js │ ├── index.html │ └── package.json ├── index.html ├── dispatch_order.js └── dispatch_order_results.txt ├── .gitignore ├── lib ├── constants.json ├── flux.js ├── sys.js ├── store.js ├── task_queue.js ├── events.js └── dispatcher.js ├── spec ├── tests │ ├── example.js │ ├── sys_test.js │ └── events_test.js └── mocha-phantomjs │ └── index.html ├── package.json ├── gulpfile.js └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | 6 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/window.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * window.js */ 4 | 5 | module.exports = window; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .module-cache/ 3 | spec/mocha-phantomjs/tests 4 | docs/ 5 | jsx2js/ 6 | .tmp/ 7 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/constants.json: -------------------------------------------------------------------------------- 1 | { 2 | "PROCESS_STATUS": { 3 | "BUSY": 1, 4 | "HALT":2, 5 | "WAIT": 3, 6 | "IDLE": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/q-flux-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/q-flux-instance.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * q-flux-instance.js */ 4 | 5 | var quantum = require('quantum-flux'); 6 | 7 | var flux = new quantum.FluxInstance(); 8 | 9 | module.exports = flux; 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/jsx/view.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | 5 | var View = React.createClass({ 6 | render: function () { 7 | return ( 8 |
9 |
10 | ); 11 | } 12 | }); 13 | 14 | 15 | module.exports = View; 16 | -------------------------------------------------------------------------------- /spec/tests/example.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * example/test.js */ 4 | 5 | /*jslint sloppy:true */ 6 | /*global describe, it */ 7 | 8 | describe('Array', function () { 9 | describe('#indexOf()', function () { 10 | it('should return -1 when the value is not present', function () { 11 | [1, 2, 3].indexOf(5).should.equal(-1); 12 | [1, 2, 3].indexOf(0).should.equal(-1); 13 | }); 14 | }); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/jsx/about.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | 5 | var About = React.createClass({ 6 | render: function () { 7 | return ( 8 |
9 |

Find out more about `quantum-flux` here:

10 | visit quantum-flux on github 11 |
12 | ); 13 | } 14 | }); 15 | 16 | module.exports = About; 17 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/q-flux-demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * q-flux-demo.js */ 4 | 5 | var React = require('react'), 6 | router = require('./q-flux-router.js'), 7 | window = require('./window.js'), 8 | Page = require('./jsx2js/page.js'), 9 | Home = require('./jsx2js/home.js'), 10 | About = require('./jsx2js/about.js'); 11 | 12 | window.React = window.React || React; 13 | window.router = window.router || router; 14 | 15 | router.addRoute('/', Home); 16 | router.addRoute('/about', About); 17 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/jsx/home.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | 5 | var Home = React.createClass({ 6 | render: function () { 7 | return ( 8 |
9 |

Welcome to the `quantum-flux` home page

10 |

This spartan page demonstrates using React and quantum-flux to build a url router.

11 |

Checkout the about page for the links

12 |
13 | ); 14 | } 15 | }); 16 | 17 | module.exports = Home; 18 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/jsx/nav.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'), 4 | _ = require('lodash'); 5 | 6 | var Navbar = React.createClass({ 7 | render: function () { 8 | var navOptions = _.map(this.props.data, function (option) { 9 | return ( 10 |
  • 11 | {option.name} 12 |
  • 13 | ); 14 | }); 15 | 16 | return ( 17 |
    18 | 21 |
    22 | ); 23 | } 24 | }); 25 | 26 | module.exports = Navbar; 27 | -------------------------------------------------------------------------------- /examples/q-flux-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "q-flux-router", 3 | "version": "0.0.0", 4 | "description": "An example router using q-flux dispatcher/stores.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "postinstall": "jsx -x jsx lib/jsx lib/jsx2js;node_modules/browserify/bin/cmd.js lib/q-flux-demo.js -o q-flux-bundle.js " 9 | }, 10 | "author": "sterpe (https://github.com/sterpe)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "react": "^0.11.1", 14 | "browserify": "^5.9.1", 15 | "lodash": "^2.4.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spec/mocha-phantomjs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
    9 | 10 | 11 | 12 | 17 | 18 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/jsx/page.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'), 4 | window = require('./../window.js'), 5 | document = window.document, 6 | Navbar = require('./nav.js'), 7 | View = require('./view.js'); 8 | 9 | var navigationOptions = [ 10 | { 11 | href: "#/", 12 | name: "home" 13 | }, 14 | { 15 | href: "#/about", 16 | name: "about" 17 | } 18 | ]; 19 | 20 | var Page = React.createClass({ 21 | render: function () { 22 | return ( 23 |
    24 |

    quantum-flux for React

    25 | 26 | 27 |
    28 | ); 29 | } 30 | }); 31 | 32 | React.renderComponent( 33 | , 34 | document.getElementById('content') 35 | ); 36 | 37 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/q-flux-routeDataStore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * lib/q-flux-routeDataStore.js */ 4 | 5 | var _ = require('lodash'), 6 | flux = require('./q-flux-instance.js'); 7 | 8 | var routeDataStore = new flux.Store({ 9 | 10 | history: [], 11 | 12 | handler: function (payload) { 13 | "use strict"; 14 | switch (payload.actionType) { 15 | case 'window.hashchange': 16 | this.history.push(payload.data.newHash); 17 | this.emit('change', payload.data.newHash, payload.data.oldHash); 18 | break; 19 | default: 20 | return; 21 | } 22 | }, 23 | 24 | currentRoute: function () { 25 | "use strict"; 26 | 27 | return this.history[this.history.length - 1]; 28 | } 29 | 30 | }); 31 | 32 | routeDataStore.register(routeDataStore.handler); 33 | 34 | module.exports = routeDataStore; 35 | 36 | -------------------------------------------------------------------------------- /spec/tests/sys_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * test/sys/nextTick_test.js */ 4 | 5 | /*jslint sloppy:true */ 6 | /*global describe, it */ 7 | 8 | var sys = require('../../lib/sys.js'); 9 | 10 | describe('Sys', function () { 11 | 12 | describe('(global) window', function () { 13 | it('should exist for this test', function () { 14 | (typeof window).should.not.equal("undefined"); 15 | }); 16 | describe('(global) window#postMessage()', function () { 17 | it('should be a function for this to work', function () { 18 | window.postMessage.should.not.be.a.Function; 19 | }); 20 | }); 21 | }); 22 | 23 | describe('#nextTick()', function () { 24 | it('should execute a function after a delay', function (done) { 25 | var flag1 = false; 26 | window.addEventListener('message', function (e) { 27 | e.source.should.equal(window); 28 | e.origin.should.equal(window.location.origin); 29 | e.data.indexOf('@sys-next-tick').should.not.equal(-1); 30 | }); 31 | sys.nextTick(function () { 32 | flag.should.be.true; 33 | done(); 34 | }); 35 | flag = true; 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /lib/flux.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * @module lib/flux.js 4 | */ 5 | 6 | var _ = require('lodash'), 7 | Dispatcher = require('./dispatcher.js').Dispatcher; 8 | 9 | /** 10 | * @class 11 | * @extends Dispatcher 12 | */ 13 | 14 | function Flux() { 15 | "use strict"; 16 | Dispatcher.prototype.constructor.call(this); 17 | 18 | return { 19 | dispatch: _.bind(this.dispatch, this), 20 | register: _.bind(this.register, this), 21 | unregister: _.bind(this.unregister, this), 22 | setImmediate: _.bind(this.setImmediate, this), 23 | interlace: _.bind(this.interlace, this), 24 | deInterlace: _.bind(this.deinterlace, this) 25 | } 26 | } 27 | 28 | _.extend(Flux.prototype, Dispatcher.prototype); 29 | 30 | module.exports = Flux; 31 | /* 32 | module.exports = { 33 | 34 | FluxInstance: function () { 35 | "use strict"; 36 | var flux = new Flux(); 37 | //StoreFactory = _.bind(flux.Store, flux); 38 | 39 | //StoreFactory.prototype = flux.Store.prototype; 40 | 41 | return { 42 | dispatch: _.bind(flux.dispatch, flux), 43 | //Store: StoreFactory 44 | Dispatcher: Dispatcher 45 | }; 46 | } 47 | }; 48 | 49 | */ 50 | 51 | window.Quantum = module.exports; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quantum-flux", 3 | "version": "0.1.1", 4 | "description": "A non-blocking Flux/React dispatcher and stores.", 5 | "keywords": [ 6 | "flux", 7 | "react", 8 | "dispatcher", 9 | "stores", 10 | "async", 11 | "facebook" 12 | ], 13 | "author": "sterpe ", 14 | "license": "MIT", 15 | "main": "lib/flux.js", 16 | "scripts": { 17 | "test": "tape test/*.js" 18 | }, 19 | "dependencies": { 20 | "lodash": "^2.4.1", 21 | "q": "^1.0.1", 22 | "node-uuid": "^1.4.1" 23 | }, 24 | "devDependencies": { 25 | "jsdoc": "^3.3.0-alpha9", 26 | "ink-docstrap": "^0.4.12", 27 | "gulp": "^3.8.6", 28 | "mocha": "^1.21.3", 29 | "should": "^4.0.4", 30 | "chai": "^1.9.1", 31 | "http-server": "^0.6.1", 32 | "browserify": "^5.9.1", 33 | "phantomjs": "^1.9.7-15", 34 | "mocha-phantomjs": "^3.5.0", 35 | "tape": "^2.13.4", 36 | "testling": "^1.7.0" 37 | }, 38 | "testling": { 39 | "files": "test/*.js", 40 | "browsers": [ 41 | "ie/6..latest", 42 | "chrome/22..latest", 43 | "firefox/16..latest", 44 | "safari/latest", 45 | "opera/11.0..latest", 46 | "iphone/6", 47 | "ipad/6", 48 | "android-browser/latest" 49 | ] 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "https://github.com/sterpe/quantum-flux.git" 54 | }, 55 | "bugs": { 56 | "url": "https://github.com/sterpe/quantum-flux/issues" 57 | }, 58 | "homepage": "http://sterpe.github.io/quantum-flux" 59 | } 60 | -------------------------------------------------------------------------------- /lib/sys.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Generate a fast, platform specific `nextTick(fn)` 4 | * If window.postMessage is available we use that, else we use 5 | * the slower, clamped window.setTimeout. 6 | * In node environments use the actual process.nextTick(). 7 | * 8 | * @class 9 | * @internal 10 | */ 11 | 12 | var _ = require('lodash'), 13 | uuid = require('node-uuid'); 14 | 15 | function NextTick() { 16 | "use strict"; 17 | 18 | var instanceToken = uuid.v1() + '@sys-next-tick:', 19 | onMessage; 20 | 21 | if ("undefined" !== typeof window) { 22 | 23 | if (window.postMessage && 24 | _.isFunction(window.postMessage)) { 25 | 26 | onMessage = function (e) { 27 | if (!e || e.source !== this.window || 28 | e.origin !== this.origin || 29 | e.data !== this.currentToken) { 30 | return; 31 | } 32 | window.removeEventListener('message', this.listener, false); 33 | this.fn.call(); 34 | }; 35 | 36 | return function (fn) { 37 | 38 | var self = { 39 | window: window, 40 | origin: window.location.origin, 41 | currentToken: instanceToken + 42 | (Math.random() * 1e17).toString(10), 43 | fn: fn 44 | }; 45 | 46 | self.listener = _.bind(onMessage, self); 47 | window.addEventListener('message', self.listener, false); 48 | window.postMessage(self.currentToken, window.location); 49 | }; 50 | } 51 | 52 | return _.partialRight(setTimeout, 0); 53 | } 54 | 55 | return _.partial(process.nextTick); 56 | } 57 | 58 | module.exports = { 59 | nextTick: new NextTick(), 60 | NextTickFactory: NextTick 61 | }; 62 | -------------------------------------------------------------------------------- /examples/q-flux-router/lib/q-flux-router.js: -------------------------------------------------------------------------------- 1 | 2 | var React = require('react'), 3 | flux = require('./q-flux-instance.js'), 4 | routeDataStore = require('./q-flux-routeDataStore.js'), 5 | window = require('./window.js'), 6 | _ = require('lodash'); 7 | 8 | 9 | function Router(options) { 10 | "use strict"; 11 | this.dataStore = this.dataStore || options.dataStore; 12 | if (!this.dataStore) { 13 | throw new Error("No dataStore specified for the Router!"); 14 | } 15 | this._routes = this._routes || {}; 16 | } 17 | 18 | _.extend(Router.prototype, { 19 | 20 | addRoute: function (route, view) { 21 | "use strict"; 22 | 23 | Router.prototype.constructor.call(this); 24 | //Complex route matching/storing logic goes here. 25 | this._routes[route] = view; 26 | } 27 | 28 | }); 29 | 30 | var router = new Router({ 31 | dataStore: routeDataStore, 32 | viewComponent: null 33 | }); 34 | 35 | //the dataStore will only be exposed via the router.dataStore 36 | //this guarantees that the router gets first listenership of all payloads. 37 | router.dataStore.addListener('change', _.bind(function (newHash, oldHash) { 38 | "use strict"; 39 | var node; 40 | //TODO: get the component. 41 | 42 | if (!newHash) { 43 | window.location.hash = '/'; 44 | } 45 | if (newHash.indexOf('#') === 0) { 46 | newHash = newHash.slice(1); 47 | } 48 | console.log(arguments); 49 | console.log(this._routes); 50 | node = document.getElementById('view'); 51 | console.log(node); 52 | this.viewComponent = this._routes[newHash] || this.viewComponent; 53 | React.renderComponent( 54 | this.viewComponent && this.viewComponent(null), 55 | node 56 | ); 57 | }, router)); 58 | 59 | window.addEventListener('hashchange', function (e) { 60 | "use strict"; 61 | var regex = /.*\#(.*)/; 62 | 63 | flux.dispatch({ 64 | actionType: 'window.hashchange', 65 | data: { 66 | newHash: e.newURL.replace(regex, '$1'), 67 | oldHash: e.oldURL.replace(regex, '$1') 68 | } 69 | }); 70 | }); 71 | 72 | window.addEventListener('load', function () { 73 | "use strict"; 74 | 75 | var regex = /.*\#(.*)/; 76 | 77 | flux.dispatch({ 78 | actionType: 'window.hashchange', 79 | data: { 80 | newHash: window.location.hash, 81 | oldHash: null 82 | } 83 | }); 84 | }); 85 | 86 | module.exports = router; 87 | -------------------------------------------------------------------------------- /lib/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @module lib/store.js 4 | */ 5 | 6 | var _ = require('lodash'), 7 | uuid = require('node-uuid'), 8 | Q = require('q'), 9 | EventEmitter = require('./events.js').EventEmitter; 10 | 11 | 12 | 13 | 14 | function Store() { 15 | "use strict"; 16 | 17 | this.__store = this.__store || (_.bind(function () { 18 | 19 | var guid = uuid.v1(); 20 | 21 | _.extend(this, Store.prototype); 22 | console.log('**CREATED NEW STORE** :: ' + guid); 23 | 24 | return { 25 | guid: guid 26 | }; 27 | 28 | }, this)()); 29 | } 30 | 31 | _.extend(Store.prototype, EventEmitter.prototype); 32 | 33 | _.extend(Store.prototype, { 34 | 35 | guid: function () { 36 | "use strict"; 37 | var self; 38 | 39 | Store.prototype.constructor.call(this); 40 | 41 | self = this.__store; 42 | return self.guid; 43 | }, 44 | 45 | addChangeListener: function (f) { 46 | "use strict"; 47 | 48 | Store.prototype.constructor.call(this); 49 | this.on('change', f); 50 | }, 51 | 52 | removeChangeListener: function (f) { 53 | "use strict"; 54 | 55 | Store.prototype.constructor.call(this); 56 | this.off('change', f); 57 | }, 58 | 59 | changed: function () { 60 | "use strict"; 61 | 62 | var args = Array.prototype.slice.call(arguments); 63 | args.unshift('change'); 64 | this.emit.apply(this, args); 65 | }, 66 | 67 | waitFor: function (targets, onFulfilled, onRejected) { 68 | "use strict"; 69 | 70 | var deferred = Q.defer(), 71 | emit = _.bind(EventEmitter.prototype.emit, this); 72 | 73 | Store.prototype.constructor.call(this); 74 | 75 | emit('store#waitFor', { 76 | store: this, 77 | targets: targets, 78 | onFulfilled: onFulfilled, 79 | onRejected: onRejected, 80 | deferred: deferred 81 | }); 82 | 83 | return deferred.promise; 84 | }, 85 | emit: function () { 86 | var args = Array.prototype.slice.call(arguments), 87 | emit = _.bind(EventEmitter.prototype.emit, this); 88 | 89 | args.unshift(this); 90 | args.unshift(EventEmitter.prototype.emit); 91 | 92 | emit('store#emit', _.bind.apply(_, args)); 93 | } 94 | }); 95 | 96 | _.extend(Store.prototype, { 97 | onChange: Store.prototype.addChangeListener, 98 | offChange: Store.prototype.removeChangeListener 99 | }); 100 | module.exports = Store; 101 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * gulpfile.js */ 4 | 5 | var gulp = require('gulp'), 6 | sys = require('sys'), 7 | child_process = require('child_process'), 8 | fs = require('fs'); 9 | 10 | gulp.task('mocha-clean', [], function (cb) { 11 | "use strict"; 12 | 13 | fs.exists('spec/mocha-phantomjs/tests', function (exists) { 14 | if (!exists) { 15 | fs.mkdir('spec/mocha-phantomjs/tests', function (error) { 16 | return error ? cb(error) : 17 | (fs.exists('spec/mocha-phantomjs/tests/mocha-bundle.js') && 18 | fs.unlink('spec/mocha-phantomjs/tests/mocha-bundle.js', 19 | function (error) { 20 | cb(error); 21 | })); 22 | }); 23 | } else { 24 | if (fs.exists('spec/mocha-phantomjs/tests/mocha-bundle.js')) { 25 | fs.unlink('spec/mocha-phantomjs/tests/mocha-bundle.js', 26 | function (error) { 27 | cb(error); 28 | }); 29 | } else { 30 | cb(); 31 | } 32 | } 33 | }); 34 | }); 35 | gulp.task('mocha-bundle', ['mocha-clean'], function (cb) { 36 | "use strict"; 37 | 38 | /* 39 | * Can't seem to get browserifying multiple input files working 40 | * with gulp in a nice way...neither can anybody else apparently: 41 | * 42 | * https://github.com/gulpjs/plugins/issues/47 43 | * 44 | * This solution is a bit of a sledgehammer, but at least it works. 45 | */ 46 | 47 | var exec = child_process.exec; 48 | exec('node node_modules/browserify/bin/cmd.js' + 49 | ' spec/tests/*.js' + 50 | ' -o spec/mocha-phantomjs/tests/mocha-bundle.js', 51 | function (error, stdout, stderr) { 52 | sys.print('stdout: ' + stdout + '\n'); 53 | sys.print('stderr: ' + stderr + '\n'); 54 | cb(error); 55 | }); 56 | 57 | }); 58 | 59 | gulp.task('mocha', ['mocha-bundle'], function (cb) { 60 | "use strict"; 61 | 62 | var spawn = child_process.spawn, 63 | address = 'localhost', 64 | port = '8081', 65 | http, 66 | mocha_phantomjs; 67 | 68 | http = spawn('node_modules/http-server/bin/http-server', 69 | [ '-a', address, '-p', port], { 70 | stdio: "inherit" 71 | }); 72 | 73 | mocha_phantomjs = 74 | spawn('node_modules/mocha-phantomjs/bin/mocha-phantomjs', 75 | ['http://' + address + ':' + port + '/spec/mocha-phantomjs'], { 76 | stdio: "inherit" 77 | }); 78 | 79 | mocha_phantomjs.on('close', function (code) { 80 | http.kill(code ? 'SIGTERM' : 'SIGHUP'); 81 | }); 82 | 83 | http.on('close', function (code) { 84 | cb(code); 85 | }); 86 | 87 | }); 88 | 89 | -------------------------------------------------------------------------------- /examples/dispatch_order.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * dispatch_order.js */ 4 | 5 | //This file gives a sense 6 | var quantum = require('./../lib/flux.js'), 7 | flux = new quantum.FluxInstance(), 8 | Store = flux.Store, 9 | Zero, 10 | A, 11 | B, 12 | C, 13 | D, 14 | E; 15 | Zero = new Store(); 16 | A = new Store({ name: "A"}); 17 | B = new Store({ name: "B"}); 18 | C = new Store({ name: "C"}); 19 | D = new Store({ name: "D"}); 20 | E = new Store({ name: "E"}); 21 | 22 | Zero.register(function (payload) { 23 | console.log('\n', 'NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS:', payload, '\n'); 24 | }); 25 | 26 | var f = function (name) { 27 | "use strict"; 28 | return function (x) { 29 | console.log('Listener of ' + name + ' reporting. Payload was: ', x); 30 | if (name === 'E') { 31 | console.log('\n'); 32 | } 33 | }; 34 | }; 35 | 36 | A.register(function (x) { 37 | "use strict"; 38 | var self = this; 39 | 40 | this.waitFor(B, function (x) { 41 | console.log('\n', 'A waited for B'); 42 | if (x === 1) { 43 | flux.dispatch(8); 44 | console.log('\n', 'The payload was "1" so B dispatched payload "8"'); 45 | } 46 | self.emit('change', x); 47 | }); 48 | }); 49 | 50 | A.addListener('change', f('A')); 51 | 52 | B.register(function (x) { 53 | "use strict"; 54 | var self = this; 55 | this.waitFor([D], function (x) { 56 | console.log('\n', 'B waited for D'); 57 | if (x === 1) { 58 | flux.dispatch(7); 59 | console.log('\n',"The payload was '1' so B dispatched the payload '7'"); 60 | } 61 | self.emit('change', x); 62 | }); 63 | }); 64 | 65 | B.addListener('change', f('B')); 66 | 67 | C.register(function (x) { 68 | "use strict"; 69 | if (x === 3) { 70 | flux.dispatch(6); 71 | console.log('\n', 'The payload was "3" so C dispatched the payload "6"'); 72 | } 73 | this.emit('change', x); 74 | }); 75 | 76 | C.addListener('change', f('C')); 77 | 78 | D.register(function (x) { 79 | "use strict"; 80 | if (x === 1) { 81 | flux.dispatch(9); 82 | console.log('\n', "The payload was \"1\" so D dispatched the payload \"9\""); 83 | } 84 | this.emit('change', x); 85 | }); 86 | 87 | D.addListener('change', f('D')); 88 | 89 | E.register(function (x) { 90 | "use strict"; 91 | var self = this; 92 | this.waitFor([D, B, C], function (x) { 93 | console.log('\n', 'E waited for D,B,C', '\n'); 94 | self.emit('change', x); 95 | }); 96 | }); 97 | 98 | E.addListener('change', f('E')); 99 | 100 | console.log('\n'); 101 | 102 | for (var i = 1; i < 6; ++i) { 103 | console.log('Dispatching payload: ', i); 104 | flux.dispatch(i); 105 | } 106 | console.log('\n'); 107 | 108 | -------------------------------------------------------------------------------- /examples/dispatch_order_results.txt: -------------------------------------------------------------------------------- 1 | **CREATED NEW STORE** :: 97f68250-18f0-11e4-b902-bd789b968062 2 | **CREATED NEW STORE** :: 97f83000-18f0-11e4-b902-bd789b968062 3 | **CREATED NEW STORE** :: 97f83001-18f0-11e4-b902-bd789b968062 4 | **CREATED NEW STORE** :: 97f83002-18f0-11e4-b902-bd789b968062 5 | **CREATED NEW STORE** :: 97f83003-18f0-11e4-b902-bd789b968062 6 | **CREATED NEW STORE** :: 97f83004-18f0-11e4-b902-bd789b968062 7 | 8 | 9 | Dispatching payload: 1 10 | Dispatching payload: 2 11 | Dispatching payload: 3 12 | Dispatching payload: 4 13 | Dispatching payload: 5 14 | 15 | 16 | 17 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 1 18 | 19 | 20 | The payload was "1" so D dispatched the payload "9" 21 | 22 | B waited for D 23 | 24 | The payload was '1' so B dispatched the payload '7' 25 | 26 | A waited for B 27 | 28 | The payload was "1" so B dispatched payload "8" 29 | 30 | E waited for D,B,C 31 | 32 | Listener of A reporting. Payload was: 1 33 | Listener of B reporting. Payload was: 1 34 | Listener of C reporting. Payload was: 1 35 | Listener of D reporting. Payload was: 1 36 | Listener of E reporting. Payload was: 1 37 | 38 | 39 | 40 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 9 41 | 42 | 43 | B waited for D 44 | 45 | A waited for B 46 | 47 | E waited for D,B,C 48 | 49 | Listener of A reporting. Payload was: 9 50 | Listener of B reporting. Payload was: 9 51 | Listener of C reporting. Payload was: 9 52 | Listener of D reporting. Payload was: 9 53 | Listener of E reporting. Payload was: 9 54 | 55 | 56 | 57 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 7 58 | 59 | 60 | B waited for D 61 | 62 | A waited for B 63 | 64 | E waited for D,B,C 65 | 66 | Listener of A reporting. Payload was: 7 67 | Listener of B reporting. Payload was: 7 68 | Listener of C reporting. Payload was: 7 69 | Listener of D reporting. Payload was: 7 70 | Listener of E reporting. Payload was: 7 71 | 72 | 73 | 74 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 8 75 | 76 | 77 | B waited for D 78 | 79 | A waited for B 80 | 81 | E waited for D,B,C 82 | 83 | Listener of A reporting. Payload was: 8 84 | Listener of B reporting. Payload was: 8 85 | Listener of C reporting. Payload was: 8 86 | Listener of D reporting. Payload was: 8 87 | Listener of E reporting. Payload was: 8 88 | 89 | 90 | 91 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 2 92 | 93 | 94 | B waited for D 95 | 96 | A waited for B 97 | 98 | E waited for D,B,C 99 | 100 | Listener of A reporting. Payload was: 2 101 | Listener of B reporting. Payload was: 2 102 | Listener of C reporting. Payload was: 2 103 | Listener of D reporting. Payload was: 2 104 | Listener of E reporting. Payload was: 2 105 | 106 | 107 | 108 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 3 109 | 110 | 111 | The payload was "3" so C dispatched the payload "6" 112 | 113 | B waited for D 114 | 115 | A waited for B 116 | 117 | E waited for D,B,C 118 | 119 | Listener of A reporting. Payload was: 3 120 | Listener of B reporting. Payload was: 3 121 | Listener of C reporting. Payload was: 3 122 | Listener of D reporting. Payload was: 3 123 | Listener of E reporting. Payload was: 3 124 | 125 | 126 | 127 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 6 128 | 129 | 130 | B waited for D 131 | 132 | A waited for B 133 | 134 | E waited for D,B,C 135 | 136 | Listener of A reporting. Payload was: 6 137 | Listener of B reporting. Payload was: 6 138 | Listener of C reporting. Payload was: 6 139 | Listener of D reporting. Payload was: 6 140 | Listener of E reporting. Payload was: 6 141 | 142 | 143 | 144 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 4 145 | 146 | 147 | B waited for D 148 | 149 | A waited for B 150 | 151 | E waited for D,B,C 152 | 153 | Listener of A reporting. Payload was: 4 154 | Listener of B reporting. Payload was: 4 155 | Listener of C reporting. Payload was: 4 156 | Listener of D reporting. Payload was: 4 157 | Listener of E reporting. Payload was: 4 158 | 159 | 160 | 161 | NEXT DISPATCH PHASE BEGINS -- PAYLOAD IS: 5 162 | 163 | 164 | B waited for D 165 | 166 | A waited for B 167 | 168 | E waited for D,B,C 169 | 170 | Listener of A reporting. Payload was: 5 171 | Listener of B reporting. Payload was: 5 172 | Listener of C reporting. Payload was: 5 173 | Listener of D reporting. Payload was: 5 174 | Listener of E reporting. Payload was: 5 175 | 176 | 177 | -------------------------------------------------------------------------------- /lib/task_queue.js: -------------------------------------------------------------------------------- 1 | 2 | var _ = require('lodash'), 3 | sys = require('./sys.js'), 4 | EventEmitter = require('./events.js').EventEmitter, 5 | PROCESS_STATUS = require('./constants.json').PROCESS_STATUS; 6 | 7 | 8 | function _process() { 9 | "use strict"; 10 | 11 | var self = this.__taskProcessQueue, 12 | task; 13 | 14 | if (self.status === PROCESS_STATUS.WAIT) { 15 | self.status = PROCESS_STATUS.BUSY; 16 | this.emit('processAwake'); 17 | } 18 | 19 | this.emit('processStart'); 20 | 21 | while (self.immediate_q.length) { 22 | self.stage_q.unshift(self.immediate_q.pop()); 23 | } 24 | while (self.work_q.length) { 25 | self.stage_q.push(self.work_q.pop()); 26 | } 27 | while (self.stage_q.length) { 28 | 29 | while (self.immediate_q.length) { 30 | self.stage_q.unshift(self.immediate_q.pop()); 31 | } 32 | while (self.stage_q.length) { 33 | self.work_q.push(self.stage_q.pop()); 34 | } 35 | 36 | this.emit('taskStart'); 37 | 38 | while (self.immediate_q.length) { 39 | self.stage_q.unshift(self.immediate_q.pop()); 40 | } 41 | 42 | while (self.stage_q.length) { 43 | self.work_q.push(self.stage_q.pop()); 44 | } 45 | 46 | task = self.work_q.pop(); 47 | task.call(); 48 | this.emit('taskEnd'); 49 | 50 | if (self.interlace || self.status === PROCESS_STATUS.HALT) { 51 | this.emit('processHalt'); 52 | self.status = PROCESS_STATUS.WAIT; 53 | this.emit('processSleep'); 54 | return sys.nextTick(self.process); 55 | } 56 | 57 | while (self.work_q.length) { 58 | self.stage_q.push(self.work_q.pop()); 59 | } 60 | 61 | if (!self.stage_q.length) { 62 | this.emit('taskClear'); 63 | 64 | while (self.immediate_q.length) { 65 | self.stage_q.unshift(self.immediate_q.pop()); 66 | } 67 | } 68 | } 69 | 70 | self.status = PROCESS_STATUS.IDLE; 71 | this.emit('processEnd'); 72 | } 73 | 74 | 75 | function TaskProcessQueue() { 76 | "use strict"; 77 | 78 | this.__taskProcessQueue = this.__taskProcessQueue || { 79 | stage_q: [], 80 | work_q: [], 81 | immediate_q: [], 82 | status: PROCESS_STATUS.IDLE, 83 | interlace: false, 84 | process: _.bind(_process, this) 85 | }; 86 | 87 | } 88 | 89 | _.extend(TaskProcessQueue.prototype, EventEmitter.prototype); 90 | 91 | _.extend(TaskProcessQueue.prototype, { 92 | 93 | unshift: function (task) { 94 | "use strict"; 95 | var self; 96 | 97 | TaskProcessQueue.prototype.constructor.call(this); 98 | 99 | self = this.__taskProcessQueue; 100 | 101 | self.immediate_q.push(task); 102 | 103 | if (self.status === PROCESS_STATUS.IDLE) { 104 | self.status = PROCESS_STATUS.BUSY; 105 | sys.nextTick(self.process); 106 | } 107 | 108 | }, 109 | 110 | enqueue: function (task) { 111 | "use strict"; 112 | 113 | var self; 114 | 115 | TaskProcessQueue.prototype.constructor.call(this); 116 | 117 | self = this.__taskProcessQueue; 118 | 119 | if (self.status === PROCESS_STATUS.IDLE) { 120 | self.status = PROCESS_STATUS.BUSY; 121 | sys.nextTick(self.process); 122 | } 123 | 124 | self.stage_q.push(task); 125 | }, 126 | 127 | halt: function () { 128 | "use strict"; 129 | 130 | var self; 131 | 132 | TaskProcessQueue.prototype.constructor.call(this); 133 | 134 | self = this.__taskProcessQueue; 135 | 136 | if (self.status === PROCESS_STATUS.BUSY) { 137 | self.status = PROCESS_STATUS.HALT; 138 | } 139 | }, 140 | 141 | interlace: function () { 142 | "use strict"; 143 | var self; 144 | 145 | TaskProcessQueue.prototype.constructor.call(this); 146 | 147 | self = this.__taskProcessQueue; 148 | 149 | self.interlace = true; 150 | }, 151 | 152 | deinterlace: function () { 153 | "use strict"; 154 | var self; 155 | 156 | TaskProcessQueue.prototype.constructor.call(this); 157 | 158 | self = this.__taskProcessQueue; 159 | 160 | self.interlace = false; 161 | } 162 | 163 | 164 | }); 165 | 166 | module.exports = TaskProcessQueue; 167 | 168 | /*var _ = require('lodash'), 169 | SystemNextTick = require('./sys.js').SystemNextTick, 170 | STATUS = { 171 | IDLE: 0, 172 | RUN: 1 173 | }; 174 | function Queue(dispatcher) { 175 | "use strict"; 176 | 177 | this._halt = this._halt || false; 178 | this._status = this._status || STATUS.IDLE; 179 | this._queue = this._queue || []; 180 | this._work_queue = this._work_queue || []; 181 | this._nextTick = this._nextTick || new SystemNextTick(); 182 | this._process = this._process || _.bind(function () { 183 | var target; 184 | 185 | while (this._work_queue.length) { 186 | this._queue.push(this._work_queue.pop()); 187 | } 188 | while (this._queue.length) { 189 | while (this._queue.length) { 190 | this._work_queue.push(this._queue.pop()); 191 | } 192 | target = this._work_queue.pop(); 193 | dispatcher.emit('emit'); 194 | target.call(); 195 | if (this._halt) { 196 | this._halt = false; 197 | return this._nextTick(this._process); 198 | } 199 | while (this._work_queue.length) { 200 | this._queue.push(this._work_queue.pop()); 201 | } 202 | } 203 | this._status = STATUS.IDLE; 204 | dispatcher.emit('emit'); 205 | }, this); 206 | 207 | } 208 | 209 | _.extend(Queue.prototype, { 210 | 211 | halt: function () { 212 | "use strict"; 213 | this._halt = true; 214 | }, 215 | 216 | enqueue: function (task) { 217 | "use strict"; 218 | 219 | Queue.prototype.constructor.call(this); 220 | 221 | if (this._status === STATUS.IDLE) { 222 | this._status = STATUS.RUN; 223 | this._nextTick(this._process); 224 | } 225 | return this._queue.push(task); 226 | } 227 | 228 | }); 229 | 230 | module.exports = { 231 | Queue: Queue 232 | }; 233 | */ 234 | -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | /** 4 | * @class 5 | */ 6 | function EventEmitter() { 7 | "use strict"; 8 | 9 | this.__eventEmitter = this.__eventEmitter || { 10 | events: {}, 11 | maxListeners: 10 12 | }; 13 | } 14 | 15 | _.extend(EventEmitter.prototype, { 16 | /** 17 | * @function EventEmitter#addListener 18 | * @param {string} e, 19 | * @param {function} listener 20 | * @return {this} 21 | */ 22 | addListener: function (e, listener) { 23 | "use strict"; 24 | //var max = this._maxListeners !== undefined ? this._maxListeners : 10; 25 | var self, 26 | max; 27 | 28 | 29 | EventEmitter.prototype.constructor.call(this); 30 | self = this.__eventEmitter; 31 | max = self.maxListeners; 32 | 33 | if (listener && _.isFunction(listener)) { 34 | if (!self.events[e]) { 35 | self.events[e] = []; 36 | } else if (max > 0 && self.events[e].length > max && console) { 37 | console.warn('More than ' + max + ' listeners attached to event "' + 38 | e + '"!'); 39 | } 40 | if (self.events[e].indexOf(listener) < 0) { 41 | self.events[e].push(listener); 42 | this.emit('newListener', listener); 43 | } 44 | } 45 | 46 | return this; 47 | }, 48 | /** 49 | * @function EventEmitter#once 50 | * @param {string} e, 51 | * @param {function} listener 52 | * @return {this} 53 | */ 54 | 55 | once: function (e, listener) { 56 | "use strict"; 57 | var bound = _.bind(function () { 58 | listener.apply(null, arguments); 59 | this.removeListener(e, bound); 60 | }, this); 61 | 62 | return this.addListener(e, bound); 63 | }, 64 | 65 | /** 66 | * @function EventEmitter#removeListener 67 | * @param {string} e, 68 | * @param {function} listener 69 | * @return {this} 70 | */ 71 | removeListener: function (e, listener) { 72 | "use strict"; 73 | var self, 74 | index; 75 | 76 | EventEmitter.prototype.constructor.call(this); 77 | 78 | self = this.__eventEmitter; 79 | 80 | if (self.events && e) { 81 | if (_.isUndefined(listener) && self.events[e]) { 82 | return this.removeAllListeners(e); 83 | } 84 | if (_.isFunction(listener) && self.events[e]) { 85 | index = self.events[e].indexOf(listener); 86 | 87 | if (index > -1) { 88 | self.events[e].splice(index, 1); 89 | this.emit('removeListener', listener); 90 | } 91 | } 92 | } 93 | return this; 94 | }, 95 | 96 | /** 97 | * @function EventEmitter#removeAllListeners 98 | * @param {string} e, 99 | * @return {this} 100 | */ 101 | removeAllListeners: function (e) { 102 | "use strict"; 103 | 104 | var self; 105 | 106 | function remove(e) { 107 | _.each(e.splice(0, e.length), function (removedListener) { 108 | this.emit('removeListener', removedListener); 109 | }, this); 110 | } 111 | 112 | EventEmitter.prototype.constructor.call(this); 113 | 114 | self = this.__eventEmitter; 115 | if (self.events) { 116 | if (!e) { 117 | _.each(self.events, remove, this); 118 | } else if (self.events[e]) { 119 | remove.call(this, self.events[e]); 120 | } 121 | } 122 | 123 | return this; 124 | }, 125 | /** 126 | * @function EventEmitter#emit 127 | */ 128 | 129 | emit: function () { 130 | "use strict"; 131 | var args = Array.prototype.slice.call(arguments), 132 | e = args.shift(), 133 | queue, 134 | self; 135 | 136 | EventEmitter.prototype.constructor.call(this); 137 | 138 | self = this.__eventEmitter; 139 | 140 | if (self.events && self.events[e]) { 141 | 142 | queue = _.map(self.events[e], function (listener, index) { 143 | return listener; 144 | }); 145 | 146 | _.each(queue, function (listener) { 147 | if (!self.events[e] || 148 | self.events[e].indexOf(listener) < 0) { 149 | return; 150 | } 151 | listener.apply(undefined, args); 152 | }, this); 153 | 154 | return queue.length > 0; 155 | } 156 | 157 | return false; 158 | }, 159 | 160 | /** 161 | * @function EventEmitter#listeners 162 | * @param {string} e 163 | * @return {array} 164 | */ 165 | listeners: function (e) { 166 | "use strict"; 167 | var self, 168 | listeners = []; 169 | 170 | EventEmitter.prototype.constructor.call(this); 171 | 172 | self = this.__eventEmitter; 173 | 174 | if (self.events && self.events[e]) { 175 | listeners = listeners.concat(self.events[e]); 176 | } 177 | 178 | return listeners; 179 | }, 180 | 181 | /** 182 | * @function EventEmitter#setMaxListeners 183 | * @param {integer} n 184 | */ 185 | setMaxListeners: function (n) { 186 | "use strict"; 187 | var self; 188 | 189 | EventEmitter.prototype.constructor.call(this); 190 | 191 | if (n || n === 0) { 192 | self = this.__eventEmitter; 193 | self.maxListeners = parseInt(n, 10); 194 | } 195 | } 196 | 197 | }); 198 | 199 | /* 200 | * Aliasing... 201 | */ 202 | 203 | _.extend(EventEmitter.prototype, { 204 | on: EventEmitter.prototype.addListener, 205 | off: EventEmitter.prototype.removeListener, 206 | addEventListener: EventEmitter.prototype.addListener, 207 | removeEventListener: EventEmitter.prototype.removeListener 208 | }); 209 | 210 | /** 211 | * @function EventEmitter.listenerCount 212 | * @param {EventEmitter} emitter 213 | * @param {string} e 214 | * @return {integer} 215 | * @static 216 | */ 217 | 218 | EventEmitter.listenerCount = function (emitter, e) { 219 | "use strict"; 220 | if (emitter && e && emitter.__eventEmitter.events) { 221 | return emitter.__eventEmitter.events[e] ? 222 | emitter.__eventEmitter.events[e].length : 0; 223 | } 224 | }; 225 | 226 | module.exports = { 227 | EventEmitter: EventEmitter 228 | }; 229 | -------------------------------------------------------------------------------- /lib/dispatcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @module lib/dispatcher.js 4 | */ 5 | 6 | var _ = require('lodash'), 7 | EventEmitter = require('./events.js').EventEmitter, 8 | TaskProcessQueue = require('./task_queue.js'), 9 | Store = require('./store.js'), 10 | Q = require('q'); 11 | 12 | 13 | /** 14 | * Creates a new Dispatcher. 15 | * @class 16 | */ 17 | 18 | function Dispatcher() { 19 | "use strict"; 20 | 21 | this.__dispatcher = this.__dispatcher || (_.bind(function () { 22 | 23 | this.onStoreWaitFor = _.bind(this.onStoreWaitFor, this); 24 | 25 | this.drainEmitQueue = _.bind(this.drainEmitQueue, this); 26 | 27 | this.onStoreEmit = _.bind(this.onStoreEmit, this); 28 | 29 | this.addEventListener('processEnd', function () { 30 | console.log('processEnd'); 31 | }); 32 | 33 | this.addEventListener('processEnd', this.drainEmitQueue); 34 | 35 | this.addEventListener('taskEnd', function () { 36 | console.log('taskEnd'); 37 | }); 38 | 39 | this.addEventListener('processStart', function () { 40 | console.log('processStart'); 41 | }); 42 | this.addEventListener('taskStart', function () { 43 | console.log('taskStart'); 44 | }); 45 | this.addEventListener('taskStart', this.drainEmitQueue); 46 | 47 | this.addEventListener('taskClear', function () { 48 | console.log('taskClear'); 49 | }); 50 | 51 | this.addEventListener('taskClear', this.drainEmitQueue); 52 | 53 | this.addEventListener('processHalt', function () { 54 | console.log('processHalt'); 55 | }); 56 | this.addEventListener('processSleep', function () { 57 | console.log('processSleep'); 58 | }); 59 | 60 | return { 61 | snapshots: [], 62 | emitQueue: [], 63 | storesData: {} 64 | }; 65 | 66 | }, this)()); 67 | } 68 | 69 | _.extend( 70 | Dispatcher.prototype, 71 | TaskProcessQueue.prototype, 72 | EventEmitter.prototype 73 | ); 74 | 75 | _.extend(Dispatcher.prototype, { 76 | 77 | drainEmitQueue: function () { 78 | "use strict"; 79 | var self, 80 | stack = []; 81 | 82 | 83 | self = this.__dispatcher; 84 | 85 | this.snapshot(); 86 | while (self.emitQueue.length) { 87 | stack.push(self.emitQueue.pop()); 88 | } 89 | while (stack.length) { 90 | stack.pop().call(); 91 | while (self.emitQueue.length) { 92 | stack.push(self.emitQueue.pop()); 93 | } 94 | } 95 | }, 96 | 97 | snapshot: function () { 98 | "use strict"; 99 | var self; 100 | 101 | self = this.__dispatcher; 102 | 103 | if (false) { 104 | //Not really sure how generally useful this is... 105 | self.snapshots.push(JSON.stringify(_.map(self.storesData, 106 | function (data, key) { 107 | var obj = {}; 108 | obj[key] = data.store; 109 | return obj; 110 | }))); 111 | } 112 | }, 113 | register: function (store, listener) { 114 | "use strict"; 115 | var self; 116 | 117 | Dispatcher.prototype.constructor.call(this); 118 | Store.prototype.constructor.call(store); 119 | 120 | self = this.__dispatcher; 121 | 122 | 123 | if (self.storesData[store.guid()] !== undefined) { 124 | this.unregister(store); 125 | } 126 | 127 | self.storesData[store.guid()] = { 128 | store: store, 129 | deferred: null, 130 | currentlyWaiting: false, 131 | currentlyWaitingFor: [], 132 | callback: _.bind(function (payload) { 133 | try { 134 | listener.call(store, payload); 135 | if (!self.storesData[store.guid()].currentlyWaiting) { 136 | self.storesData[store.guid()].deferred.resolve(payload); 137 | } 138 | } catch (e) { 139 | console.log(e); 140 | self.storesData[store.guid()].deferred.reject(e); 141 | } 142 | }, this) 143 | }; 144 | 145 | store.on('store#waitFor', this.onStoreWaitFor); 146 | store.on('store#emit', this.onStoreEmit); 147 | this.on('#channel', self.storesData[store.guid()].callback); 148 | 149 | return store; 150 | }, 151 | 152 | unregister: function (store) { 153 | "use strict"; 154 | 155 | var self; 156 | 157 | Dispatcher.prototype.constructor.call(this); 158 | 159 | self = this.__dispatcher; 160 | 161 | if (!(store.guid && store.__store) || !self.storesData[store.guid()]) { 162 | return store; 163 | } 164 | store.off('store#waitFor', this.onStoreWaitFor); 165 | this.off('#channel', self.storesData[store.guid()].callback); 166 | delete self.storesData[store.guid()]; 167 | 168 | return store; 169 | }, 170 | 171 | onStoreWaitFor: function (wait) { 172 | "use strict"; 173 | 174 | var self, 175 | cyclicFailureError; 176 | 177 | Dispatcher.prototype.constructor.call(this); 178 | 179 | self = this.__dispatcher; 180 | 181 | if (!_.isArray(wait.targets)) { 182 | wait.targets = [wait.targets]; 183 | } 184 | 185 | _.each(wait.targets, function (target) { 186 | if (target === wait.store) { 187 | cyclicFailureError = 'A store can\'t wait on itself!'; 188 | } 189 | }); 190 | 191 | if (cyclicFailureError) { 192 | throw new Error(cyclicFailureError); 193 | } 194 | 195 | self.storesData[wait.store.guid()].currentlyWaiting = true; 196 | self.storesData[wait.store.guid()].currentlyWaitingFor = 197 | [].concat(wait.targets); 198 | 199 | this.halt(); //Halt the processing queue; 200 | 201 | Q.all(_.filter(_.map(self.storesData, function (data) { 202 | return wait.targets.indexOf(data.store) !== -1 ? 203 | data.deferred.promise : false; 204 | }), _.identity)) 205 | 206 | .then(function (x) { 207 | var payload = x.pop(); 208 | 209 | wait.onFulfilled.call(wait.store, payload); 210 | 211 | return payload; 212 | 213 | }, function (e) { 214 | wait.onRejected.call(wait.store, e); 215 | throw e; 216 | }) 217 | 218 | .then(function (payload) { 219 | self.storesData[wait.store.guid()].deferred.resolve(payload); 220 | }, function (e) { 221 | self.storesData[wait.store.guid()].deferred.reject(e); 222 | }) 223 | 224 | .finally(function () { 225 | self.storesData[wait.store.guid()].currentlyWaiting = false; 226 | self.storesData[wait.store.guid()].currentlyWaitingFor = []; 227 | }); 228 | }, 229 | 230 | onStoreEmit: function (fn) { 231 | "use strict"; 232 | var self; 233 | 234 | self = this.__dispatcher; 235 | 236 | self.emitQueue.push(fn); 237 | }, 238 | 239 | dispatch: function (payload) { 240 | "use strict"; 241 | 242 | var self; 243 | 244 | Dispatcher.prototype.constructor.call(this); 245 | 246 | self = this.__dispatcher; 247 | 248 | this.enqueue(_.bind(function () { 249 | self.deferreds = {}; 250 | 251 | _.each(self.storesData, function (storeData, key) { 252 | storeData.deferred = Q.defer(); 253 | }); 254 | 255 | this.emit('#channel', payload); 256 | }, this)); 257 | 258 | }, 259 | 260 | thread: function () { 261 | "use strict"; 262 | 263 | var self; 264 | 265 | Dispatcher.prototype.constructor.call(this); 266 | 267 | this.enqueue(_.bind(this.interlace, this)); 268 | }, 269 | 270 | dethread: function () { 271 | "use strict"; 272 | 273 | var self; 274 | 275 | Dispatcher.prototype.constructor.call(this); 276 | 277 | this.enqueue(_.bind(this.deinterlace, this)); 278 | }, 279 | 280 | setImmediate: function (fn) { 281 | "use strict"; 282 | 283 | var self; 284 | 285 | Dispatcher.prototype.constructor.call(this); 286 | //this.unshift(fn); 287 | this.unshift(_.bind(function() { 288 | //this.interlace(); 289 | this.thread(); 290 | this.enqueue(fn); 291 | this.dethread(); 292 | },this)); 293 | } 294 | 295 | }); 296 | 297 | module.exports = { 298 | Dispatcher: Dispatcher 299 | }; 300 | 301 | 302 | -------------------------------------------------------------------------------- /spec/tests/events_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * events_test.js */ 4 | 5 | /*jslint sloppy:true */ 6 | /*global describe, it, before, beforeEach*/ 7 | 8 | var EventEmitter = require('./../../lib/events.js').EventEmitter, 9 | _ = require('lodash'), 10 | mut, 11 | bar = function () {}, 12 | baz = function () {}; 13 | 14 | function MUT() {} 15 | 16 | _.extend(MUT.prototype, EventEmitter.prototype); 17 | 18 | describe('EventEmitter', function () { 19 | beforeEach(function () { 20 | mut = new MUT(); 21 | }); 22 | describe('#addListener', function () { 23 | it('should add a listener', function () { 24 | mut.addListener('foo', bar); 25 | mut.__eventEmitter.events['foo'][0].should.equal(bar); 26 | mut.__eventEmitter.events['foo'][0].should.be.a.Function; 27 | mut.__eventEmitter.events['foo'].length.should.equal(1); 28 | }); 29 | 30 | it('should not register the same listener twice for the same event', 31 | function () { 32 | mut.addListener('foo', bar); 33 | mut.addListener('foo', bar); 34 | mut.__eventEmitter.events['foo'][0].should.equal(bar); 35 | mut.__eventEmitter.events['foo'][0].should.be.a.Function; 36 | mut.__eventEmitter.events['foo'].length.should.equal(1); 37 | }); 38 | 39 | it('should register the same listener for multiple events', 40 | function () { 41 | mut.addListener('foo', bar); 42 | mut.addListener('bar', bar); 43 | mut.__eventEmitter.events['foo'][0].should.equal(bar); 44 | mut.__eventEmitter.events['foo'].length.should.equal(1); 45 | mut.__eventEmitter.events['bar'][0].should.equal(bar); 46 | mut.__eventEmitter.events['bar'].length.should.equal(1); 47 | }); 48 | 49 | it('should add multiple listeners to the same event', function () { 50 | mut.addListener('foo', bar); 51 | mut.addListener('foo', baz); 52 | mut.__eventEmitter.events['foo'][0].should.equal.bar; 53 | mut.__eventEmitter.events['foo'][0].should.be.a.Function; 54 | mut.__eventEmitter.events['foo'][1].should.equal.baz; 55 | mut.__eventEmitter.events['foo'][1].should.be.a.Function; 56 | mut.__eventEmitter.events['foo'].length.should.equal(2); 57 | }); 58 | 59 | it('should add multiple listeners to different events', function () { 60 | mut.addListener('foo1', bar); 61 | mut.addListener('foo2', baz); 62 | mut.__eventEmitter.events['foo1'][0].should.equal.bar; 63 | mut.__eventEmitter.events['foo1'][0].should.be.a.Function; 64 | mut.__eventEmitter.events['foo1'].length.should.equal(1); 65 | mut.__eventEmitter.events['foo2'][0].should.equal.baz; 66 | mut.__eventEmitter.events['foo2'][0].should.be.a.Function; 67 | mut.__eventEmitter.events['foo1'].length.should.equal(1); 68 | }); 69 | 70 | it('should add a listener during an event to fire on the next event', 71 | function (done) { 72 | var flags = [], 73 | thrice = 0; 74 | function a() { 75 | flags.push('a'); 76 | mut.addListener('foo', b); 77 | } 78 | function b() { 79 | flags.push('b'); 80 | } 81 | function c() { 82 | if (++thrice === 3) { 83 | flags.should.eql(['a', 'a', 'b', 'a']); 84 | done(); 85 | } 86 | } 87 | mut.addListener('foo', a); 88 | mut.addListener('foo', c); 89 | mut.emit('foo'); 90 | mut.emit('foo'); 91 | mut.emit('foo'); 92 | }); 93 | it('should have an alias `#on()`', function () { 94 | mut.on.should.equal(mut.addListener); 95 | }); 96 | 97 | it('should have an alias `#addEventListener()`', function () { 98 | mut.addEventListener.should.equal(mut.addListener); 99 | }); 100 | }); 101 | 102 | describe('#removeListener()', function () { 103 | it('should remove a specific listener', function () { 104 | mut.addListener('foo', bar); 105 | mut.removeListener('foo', bar); 106 | mut.__eventEmitter.events['foo'].length.should.equal(0); 107 | }); 108 | 109 | it('should remove all listeners for an event', function () { 110 | mut.addListener('foo', bar); 111 | mut.addListener('foo', baz); 112 | mut.removeListener('foo'); 113 | mut.__eventEmitter.events['foo'].length.should.equal(0); 114 | }); 115 | 116 | it('should remove a listener during an event, effectively cancelling that event for that listener', function (done) { 117 | var flags = []; 118 | function a() { 119 | flags.push('a'); 120 | mut.removeListener('foo', b); 121 | } 122 | function b() { 123 | flags.push('b'); 124 | } 125 | function c() { 126 | flags.should.eql(['a']); 127 | done(); 128 | } 129 | mut.addListener('foo', a); 130 | mut.addListener('foo', b); 131 | mut.addListener('foo', c); 132 | 133 | mut.emit('foo'); 134 | }); 135 | 136 | it('should have an alias `#off()`', function () { 137 | mut.off.should.equal(mut.removeListener); 138 | }); 139 | 140 | it('should have an alias `#removeEventListener()`', function () { 141 | mut.removeEventListener.should.equal(mut.removeListener); 142 | }); 143 | }); 144 | describe('#removeAllListeners()', function () { 145 | 146 | it('should remove all listeners', function () { 147 | mut.addListener('foo1', bar); 148 | mut.addListener('foo2', baz); 149 | mut.removeAllListeners(); 150 | mut.__eventEmitter.events['foo1'].length.should.equal(0); 151 | mut.__eventEmitter.events['foo2'].length.should.equal(0); 152 | }); 153 | }); 154 | 155 | describe('#listeners()', function () { 156 | it('should return an array with the listeners for an event', 157 | function () { 158 | var listeners = [bar, baz]; 159 | mut.addListener('foo', bar); 160 | mut.addListener('foo', baz); 161 | 162 | _.each(mut.listeners('foo'), function (_, i, array) { 163 | array[i].should.equal(listeners[i]); 164 | }); 165 | mut.__eventEmitter.events['foo'].length.should.equal(2); 166 | }); 167 | }); 168 | 169 | 170 | describe('#setMaxListeners()', function () { 171 | it('should set the maximum # of listeners to the object', function () { 172 | mut.setMaxListeners(20); 173 | mut.__eventEmitter.maxListeners.should.equal(20); 174 | }); 175 | 176 | it('should `console.warn` if there are too many listeners', 177 | function () { 178 | var _console = { 179 | warn: function () { 180 | true.should.be.ok; 181 | } 182 | }; 183 | //_console = GLOBAL.console; 184 | var con = window.console; 185 | window.console = _console; 186 | mut.setMaxListeners(1); 187 | mut.addEventListener('foo', bar); 188 | window.console = con; 189 | }); 190 | it('should not warn if `maxListeners` is set to `0`', function () { 191 | var flag = true; 192 | var _console = { 193 | warn: function () { 194 | flag = false; 195 | } 196 | }; 197 | 198 | var con = window.console; 199 | window.console = _console; 200 | mut.setMaxListeners(0); 201 | mut.addEventListener('foo', bar); 202 | mut.addEventListener('foo', baz); 203 | mut.addEventListener('bar', bar); 204 | mut.addEventListener('bar', baz); 205 | window.console = con; 206 | flag.should.be.ok; 207 | }); 208 | }); 209 | describe('#emit()', function () { 210 | it('should call registered listeners for an event in order with arguments', 211 | function (done) { 212 | var flags = []; 213 | mut.addEventListener('foo', function (x, y) { 214 | flags.push('a'); 215 | flags.push(x); 216 | flags.push(y); 217 | }); 218 | mut.addListener('foo', function (x, y) { 219 | flags.push('b'); 220 | flags.push(x); 221 | flags.push(y); 222 | }); 223 | mut.addListener('foo', function (x, y) { 224 | x.should.equal('arg1'); 225 | y.should.equal('arg2'); 226 | flags.should.eql(['a', x, y,'b', x, y]); 227 | done(); 228 | }); 229 | mut.emit('foo', 'arg1', 'arg2'); 230 | }); 231 | }); 232 | describe('#once()', function () { 233 | it('should register a function that is only called once.', 234 | function (done) { 235 | var flags = [], 236 | twice = 0; 237 | mut.once('foo', function (x, y) { 238 | flags.push('a'); 239 | flags.push(x); 240 | flags.push(y); 241 | }); 242 | mut.addListener('foo', function (x, y) { 243 | if(++twice === 2) { 244 | flags.should.eql(['a', 'arg1', 'arg2']); 245 | done(); 246 | } 247 | }); 248 | mut.emit('foo', 'arg1', 'arg2'); 249 | mut.emit('foo', 'bar', 'baz'); 250 | }); 251 | }); 252 | 253 | describe(' #listenerCount()', function () { 254 | it('should return the # of listeners an emitter has for an event', 255 | function () { 256 | mut.addListener('foo', bar); 257 | mut.addListener('foo', baz); 258 | 259 | EventEmitter.listenerCount(mut, 'foo').should.equal(2); 260 | }); 261 | }); 262 | }); 263 | 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![quantum-flux](http://sterpe.github.io/quantum-flux/img/quantum-flux.jpg "") 2 | 3 | ==== 4 | 5 | [![Build Status](http://travis-ci.org/sterpe/quantum-flux.svg?branch=master)](http://travis-ci.org/sterpe/quantum-flux) 6 | 7 | 8 | ###Installation 9 | ``` 10 | npm install quantum-flux 11 | ``` 12 | 13 | ###Table of Contents 14 | + [Introduction](http://github.com/sterpe/quantum-flux/blob/master/README.md#introduction) 15 | + [Why quantum-flux?](http://github.com/sterpe/quantum-flux/blob/master/README.md#why-quantum-flux) 16 | + [Basic Usage](http://github.com/sterpe/quantum-flux/blob/master/README.md#basic-usage) 17 | + [API Documentation](http://github.com/sterpe/quantum-flux/blob/master/README.md#api-documentation) 18 | + [Dispatcher] (http://github.com/sterpe/quantum-flux/blob/master/README.md#the-dispatcher) 19 | + [Quantum.constructor()] (http://github.com/sterpe/quantum-flux/blob/master/README.md#quantum) 20 | + [Quantum.dispatch(payload)] (http://github.com/sterpe/quantum-flux/blob/master/README.md#quantumdispatch) 21 | + [Quantum.register(store, listener)] (http://github.com/sterpe/quantum-flux/blob/master/README.md#quantumregister) 22 | + [Quantum.unregister(store)](http://github.com/sterpe/quantum-flux/blob/master/README.md#quantumunregister) 23 | + [Stores] (http://github.com/sterpe/quantum-flux/blob/master/README.md#the-stores) 24 | + [About Stores] (http://github.com/sterpe/quantum-flux/blob/master/README.md#about-stores) 25 | + [Store.waitFor([stores], onFulfilled, onRejected)] (http://github.com/sterpe/quantum-flux/blob/master/README.md#storewaitfor) 26 | + [Store.addChangeListener(func)] (http://github.com/sterpe/quantum-flux/blob/master/README.md#storeaddchangelistener) 27 | + [Store.removeChangeListener(func)] (http://github.com/sterpe/quantum-flux/blob/master/README.md#storeremovechangelistener) 28 | + [Store.changed([args])] (http://github.com/sterpe/quantum-flux/blob/master/README.md#storechanged) 29 | + [Advanced APIs] (http://github.com/sterpe/quantum-flux/blob/master/README.md#advanced-apis) 30 | + [Quantum.setImmediate(func)] (http://github.com/sterpe/quantum-flux/blob/master/README.md#quantumsetimmediate) 31 | + [Quantum.interlace()] (http://github.com/sterpe/quantum-flux/blob/master/README.md#quantuminterlace) 32 | + [Quantum.deInterlace()] (http://github.com/sterpe/quantum-flux/blob/master/README.md#quantumdeinterlace) 33 | + [Contributing to Quantum Flux] (http://github.com/sterpe/quantum-flux/blob/master/README.md#contributing-to-quantum-flux) 34 | 35 | ###Introduction 36 | __Flux__ is the data-flow architecture associated with the React UI Framework. In brief it proposes an application composed of three major parts: the dispatcher, it's stores, and their views (React Components). 37 | 38 | Data flows through such an application in a single, cyclic, direction: 39 | 40 | ```smalltalk 41 | Views ---> (actions) ---> Dispatcher ----> (callback) ----> Stores ----+ 42 | ^ | 43 | | V 44 | +--------( "change" event handlers) <------ ("change" events) ---------+ 45 | ``` 46 | 47 | Views generate actions, propogated to a dispatcher, the dispatcher informs 48 | data stores about the event and they update themselves appropriately, stores 49 | fire "change" events which views listen to vis-a-vis event handlers and so on...a single glorious cycle. 50 | 51 | A full introduction to Flux Architecture can be found @ http://facebook.github.io/react/docs/flux-overview.html 52 | 53 | ###Why quantum-flux? 54 | 55 | Because all actions in an application propogate to the application dispatcher, which in turn funnels them universally to all application stores, there is a natural single-thread processing bottleneck that will occur as an application grows. 56 | 57 | In a hypothetically large application with many stores and many more actions, it begins to make sense to think about the idea of _**non-blocking**_ dispatch resolution, enabling Javascript based-timers, CSS animations, and UI interactions to continue while the dispatch phase of a flux-cycle complete. 58 | 59 | An asynchronous dispatcher would allow the application to queue up additional call to the dispatcher (even recursive calls) processing them asynchronously, while ensuring that stores own change event handlers were themselves called synchronously and in the correct total ordering of events. 60 | 61 | In effect we enforce a total state transition between two phases: a dispatch or "action" phase and the render or "react" phase. It looks something like this: 62 | 63 | ```smalltalk 64 | 65 | An action occurs, 66 | moving application into a still Application state (all stores) 67 | resolving state that is neither is now fully transitioned from 68 | fully A nor B state A to B. 69 | | ---> | 70 | | -->> Asynchronous Resolution Phase --> | 71 | STATE A | --> | STATE B 72 | V (ACTION/FLUX PHASE) V (REACT PHASE) 73 | Views ---> (actions) ---> Dispatcher ----> (callback) ----> Stores ----+ 74 | ^ | 75 | | V 76 | +--------( "change" event handlers) <------ ("change" events) ---------+ 77 | ^ 78 | | <-- Synchronous Resolution Phase <-- 79 | | 80 | (END REACT PHASE) 81 | 1x BROWSER REPAINT, STATE B 82 | 83 | ``` 84 | This is _**quantum-flux**_. 85 | 86 | The synchronous resolution of the "react phase" ensures that responsive UI repaints remain atomic and correctly ordered, just as they would be in a pure synchronous dispatcher implementation, while supporting a non-blocking "action/flux phase." 87 | 88 | ###Basic Usage 89 | 90 | ```javascript 91 | //Create a flux dispatcher. ******************************************** 92 | 93 | var Quantum = require('quantum-flux'); 94 | 95 | var flux = new Quantum(); 96 | 97 | //Register a store. **************************************************** 98 | 99 | var store = { 100 | data: null 101 | }; 102 | 103 | flux.register(store, function (payload) { 104 | //Do something when there is an appropriate payload 105 | switch (payload.isRelevant) { 106 | 107 | case "yes": 108 | //update the store's data. 109 | this.data = payload.data; 110 | break; 111 | 112 | default: 113 | return; 114 | } 115 | //notify any listeners that there was a change. 116 | this.changed(); 117 | }); 118 | 119 | //Add a listener to this store. **************************************** 120 | 121 | store.onChange(function () { 122 | //do something with the store data when it's updated. 123 | console.log(store.data); 124 | }); 125 | 126 | //Dispatch an action! ************************************************** 127 | 128 | flux.dispatch({ 129 | isRelevant: "yes", 130 | data: 42 131 | }); 132 | 133 | ``` 134 | #API Documentation 135 | -- 136 | ##the dispatcher 137 | ###Quantum 138 | `Quantum()` 139 | 140 | -- 141 | ####Constructor 142 | #####Parameters 143 | ######none 144 | #####Example 145 | ```javascript 146 | var Quantum = require('quantum-flux'); 147 | 148 | var flux = new Quantum(); 149 | 150 | ``` 151 | 152 | -- 153 | ###Quantum.dispatch 154 | `Quantum.dispatch(payload)` 155 | 156 | -- 157 | #####Description: 158 | Dispatch the argument `payload` to all registered stores. 159 | #####Parameters 160 | Name | Type | Description 161 | --- | --- | --- | 162 | `payload` |`any` | The thing to be dispatched, can be falsey. 163 | 164 | #####Returns 165 | `undefined` 166 | 167 | #####Example 168 | ```javascript 169 | var flux = new Quantum(), 170 | 171 | flux.dispatch({ 172 | actionType: 'first quantum dispatch!', 173 | value: 'hello, quantum-flux!' 174 | }); 175 | 176 | ``` 177 | -- 178 | ###Quantum.register 179 | `Quantum.register(store, listener)` 180 | 181 | -- 182 | #####Description: 183 | Register a new store and it's listener with the dispatcher. 184 | If `store` is already registered with the dispatcher, changes stores current listening function to `listener`. 185 | #####Parameters 186 | Name | Type | Description 187 | --- | --- | --- | 188 | `store` |`{object}` | The dispatcher will automatically extend "plain" `Objects` with `Quantum.Store.prototype`. 189 | `listener` |`{function}` | The store's callback for dispatches; will be called as: `listener.apply(store, [args]);` 190 | 191 | #####Returns 192 | `store` 193 | 194 | #####Example 195 | 196 | ```javascript 197 | var flux = new Quantum(); 198 | 199 | var store = { 200 | foo: "bar", 201 | fn: function (payload) { 202 | if (payload.actionType === "FOO_UPDATE") { 203 | this.foo = payload.value; 204 | this.changed(); 205 | } 206 | } 207 | }; 208 | 209 | flux.register(store, store.fn); 210 | ``` 211 | -- 212 | ###Quantum.unregister 213 | `Quantum.unregister(store)` 214 | 215 | -- 216 | #####Description 217 | Unregister `store` with the dispatcher. The listening function associated with `store` will no longer be informed about dispatch events. 218 | #####Parameters 219 | Name | Type | Description 220 | --- | --- | --- | 221 | `store` | `{object}` | The store to be unregistered with the flux dispatcher. 222 | #####Returns 223 | `store` 224 | 225 | #####Example 226 | 227 | ```javascript 228 | //Assume `store` is a previously registered store. 229 | 230 | flux.unregister(store); 231 | ``` 232 | -- 233 | ##the stores 234 | ####About Stores 235 | Stores implement an EventEmitter prototype very similiar to the [Node.js EventEmitter](http://nodejs.org/api/events.html) prototype with one exception: the behavior of removeListener is consistent with the browser behavior of removeEventListener not Node's removeListener behavior. 236 | 237 | Please consult the Node.js documentation for complete API documentation for the EventEmitter prototype: 238 | 239 | http://nodejs.org/api/events.html 240 | ####Example 241 | 242 | ```javascript 243 | var flux = new Quantum; 244 | 245 | var store = {}; 246 | 247 | flux.register(store, function () {}); 248 | 249 | var f1 = function () { 250 | store.removeListener('change', f2); // or aliases - #removeEventListener() or #off() 251 | }; 252 | 253 | var f2 = function () { 254 | }; 255 | 256 | store.addListener('change', f1); //or aliases - #addEventListener() or #on() 257 | store.addListener('change', f2); 258 | 259 | store.emit('change'); 260 | 261 | //f2 was not called, as f1 removed it. This is consistent with the 262 | //in browser behavior of `removeEventListener`. 263 | 264 | //In node.js f2 would have been called, but would not be called the 265 | //next time the `change` event was fired. 266 | ``` 267 | 268 | ###Store.waitFor 269 | `Store.waitFor([stores], onFulfilled, onRejected)` 270 | 271 | -- 272 | #####Description 273 | When called inside a stores registered dispatch handler, instructs the 274 | dispatcher to defer execution of the onFulfilled or onRejected callbacks until the store or array of stores specified by `[stores]` have first completedthe current dispatch cycle. 275 | 276 | -- 277 | ###Store.addChangeListener 278 | `Store.addChangeListener(func)` 279 | 280 | -- 281 | #####Description 282 | A convenience method for: 283 | 284 | ```javascript 285 | Store.on('change', func); 286 | ``` 287 | #####Parameters 288 | Name | Type | Description 289 | --- | --- | --- | 290 | `func` | `{function}`| The function handler for store change events. 291 | #####returns 292 | `undefined` 293 | #####Aliases 294 | ######onChange() 295 | #####Example 296 | 297 | ```javascript 298 | var flux = new Quantum(); 299 | 300 | var store = {}; 301 | 302 | flux.register(store, function () {}); 303 | 304 | var f = function () { /*...*/ }; 305 | 306 | store.addChangeListener(f); 307 | 308 | ``` 309 | -- 310 | ###Store.removeChangeListener 311 | `Store.removeChangeListener(func)` 312 | 313 | -- 314 | #####Description 315 | A convenience method for: 316 | 317 | ```javascript 318 | Store.off('change', func); 319 | ``` 320 | #####Parameters 321 | Name | Type | Description 322 | ---| --- | --- | 323 | `func` | `{function}` | The handler to desubscribe from change events. 324 | #####returns 325 | `undefined` 326 | #####Aliases 327 | ######offChange() 328 | #####Example 329 | 330 | ```javascript 331 | var flux = new Quantum(); 332 | 333 | var store = {}; 334 | 335 | flux.register(store, function () {}); 336 | 337 | var f = function () { /*...*/ }; 338 | 339 | store.addChangeListener(f); 340 | 341 | //stop listening to this store's change event with `f` 342 | store.removeChangeListener(f); 343 | ``` 344 | -- 345 | ###Store.changed 346 | `Store.changed([args])` 347 | 348 | -- 349 | #####Description 350 | A convenience method for: 351 | 352 | ```javascript 353 | Store.emit('change', /* [args] */); 354 | ``` 355 | #####Parameters 356 | Name | Type | Description 357 | --- | --- | --- | 358 | `args...` | `any` | Optional arguments to pass to event listeners for the change event. 359 | #####Returns 360 | `undefined` 361 | 362 | #####Example 363 | 364 | ```javascript 365 | var flux = new Quantum(); 366 | var store = {}; 367 | 368 | flux.register(store, function (payload) { 369 | if (payload.isRelevantToThisStore) { 370 | 371 | //it updates the store appropriately. 372 | 373 | this.changed(); 374 | } 375 | }) 376 | 377 | store.onChange(/*...etc...*/); 378 | ``` 379 | 380 | ##Advanced APIs 381 | 382 | ###Quantum.setImmediate 383 | `Quantum.setImmediate(func)` 384 | 385 | -- 386 | #####Description 387 | Preempt the next dispatch in the dispatch queue with the async evaluation of `func`. A browser repaint will be allowed both before and after `func` is evaluated, then dispatching will continue in it's "natural"* order. If `func` dispatches an action with Quantum.dispatch(), that dispatch will also precede the natural queue order...and so on. 388 | 389 | It can be hard to notice the effects of __setImmediate__ unless `func` issues a call to `Quantum#dispatch()` in all cases, however, __setImmediate__ has the effect of "padding" the execution of `func` with browser repaints, i.e., it is asynchronous both before _and_ after. 390 | 391 | #####Parameters 392 | Name | Type | Description 393 | --- | --- | --- | 394 | `func`| `{function}` | The function to execute prior to the next dispatch in the "natural"* order. 395 | 396 | \* We can define "natural" order as the order of dispatch events queued such that registered stores fire their own changed listeners and other events in attached order __and__ stores do this in in the order of dispatch resolution...i.e., if storeA waitedFor storeB, storeB fires it's own changes prior to storeA. 397 | 398 | It's important to remember that the `quantum-flux` dispatcher supports recursive calls to dispatch as part of the natural order. 399 | 400 | #####Example 401 | ```javascript 402 | var flux = new Quantum(); 403 | 404 | var store1 = flux.register({}, function (p) { 405 | this.changed(); 406 | }); 407 | 408 | store1.addChangeListener(function () { 409 | console.log('logged synchronously'); 410 | }); 411 | 412 | var store2 = flux.register({}, function (p) { 413 | this.changed(); 414 | }); 415 | 416 | store2.addChangeListener(function (p) { 417 | flux.setImmediate(function () { 418 | //The thread 'sleeps' directly before this...browser can repaint. 419 | 420 | console.log('executed asynchronously, but still in order'); 421 | 422 | //And 'sleeps' directly after this again...browser can repaint. 423 | }); 424 | }); 425 | 426 | flux.dispatch(); 427 | 428 | //logged synchronously 429 | //executed asynchronously, but still in order 430 | ``` 431 | ###Quantum.interlace 432 | `Quantum.interlace()` 433 | 434 | -- 435 | #####Description 436 | Use maximum thread-sharing when processing flux-store dispatch digestion. This will increase the number of browser repaints available and/or allow other javascript processes (such as timers) to run while stores digest the current dispatch. 437 | 438 | If interlacing is already enabled, the call has no effect. 439 | 440 | By default, the __quantum-flux__ dispatcher only thread-shares when processing a __waitFor()__ or __setImmediate()__ request. 441 | 442 | #####Default 443 | ######thead-interlacing is disabled. 444 | #####Parameters 445 | ######none 446 | #####Returns 447 | `undefined` 448 | #####Example 449 | ```javascript 450 | var flux = new Quantum(); 451 | 452 | flux.interlace(); 453 | 454 | ``` 455 | 456 | ###Quantum.deInterlace 457 | `Quantum.deInterlace()` 458 | 459 | -- 460 | #####Description 461 | Turn off thread-interlacing if it has been enabled, otherwise has no effect. Interlacing is __disabled__ by default. 462 | #####Default 463 | ######thread-interlacing is disabled. 464 | #####Parameters 465 | ######none 466 | #####Returns 467 | `undefined` 468 | #####Example 469 | ```javascript 470 | var flux = new Quantum(); 471 | 472 | flux.interlace(); 473 | flux.deInterlace(); 474 | 475 | ``` 476 | 477 | ##Contributing to Quantum Flux 478 | 479 | * 2 space indentation 480 | * 80 columns 481 | * Include unit tests in your pull requests 482 | --------------------------------------------------------------------------------