├── .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 |
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 |
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 | 
2 |
3 | ====
4 |
5 | [](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 |
--------------------------------------------------------------------------------