├── .gitignore ├── .jscsrc ├── .travis.yml ├── FUTURE.md ├── Gruntfile.js ├── README.md ├── TODO.md ├── bower.json ├── build └── tasks │ ├── browserify.js │ ├── concat.js │ ├── connect.js │ ├── docco.js │ ├── jscs.js │ ├── jshint.js │ ├── karma.js │ ├── release.js │ ├── uglify.js │ └── watch.js ├── dist ├── .tmp │ └── delorean-requires.js ├── delorean.js ├── delorean.min.js └── delorean.min.js.map ├── docs ├── README.md ├── actions.md ├── api │ ├── delorean.html │ ├── docco.css │ ├── public │ │ ├── fonts │ │ │ ├── aller-bold.eot │ │ │ ├── aller-bold.ttf │ │ │ ├── aller-bold.woff │ │ │ ├── aller-light.eot │ │ │ ├── aller-light.ttf │ │ │ ├── aller-light.woff │ │ │ ├── fleurons.eot │ │ │ ├── fleurons.ttf │ │ │ ├── fleurons.woff │ │ │ ├── novecento-bold.eot │ │ │ ├── novecento-bold.ttf │ │ │ └── novecento-bold.woff │ │ ├── images │ │ │ └── gray.png │ │ └── stylesheets │ │ │ └── normalize.css │ └── requirements.html ├── asset │ └── flux-diagram.png ├── dispatchers.md ├── routing.md ├── stores.md ├── tutorial.md └── views.md ├── examples ├── firebase-chat │ ├── index.html │ └── js │ │ ├── app.js │ │ └── app.jsx ├── todomvc-flightjs │ ├── index.html │ └── js │ │ └── main.js ├── todomvc │ ├── Gruntfile.coffee │ ├── README.md │ ├── asset │ │ ├── css │ │ │ └── app.css │ │ ├── js │ │ │ ├── app.js │ │ │ └── index.js │ │ └── jsx │ │ │ └── index.jsx │ ├── index.html │ └── package.json └── tutorial │ ├── index.html │ └── js │ └── app.js ├── package.json ├── src ├── delorean.js └── requirements.js ├── test ├── karma.conf.js ├── spec │ ├── common.js │ └── core │ │ ├── fluxSpec.js │ │ └── reactSpec.js └── vendor │ └── react-0.11.1.js └── upgrade.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | dist/.tmp 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "crockford" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | -------------------------------------------------------------------------------- /FUTURE.md: -------------------------------------------------------------------------------- 1 | # Future of DeLorean.js 2 | 3 | ## ES6 Syntax 4 | 5 | ### Importing 6 | ```js 7 | import {Store, Dispatcher} from "delorean"; 8 | ``` 9 | 10 | ### Stores 11 | ```js 12 | /* 13 | * Stores are simple data buckets which manages data. 14 | */ 15 | @Store({ 16 | actions: { 17 | 'incoming-data': 'setData' 18 | } 19 | }) 20 | class TodoStore { 21 | 22 | constructor() { 23 | this.data = null; 24 | } 25 | 26 | setData(data) { 27 | this.data = data; 28 | this.emit('change'); 29 | } 30 | } 31 | ``` 32 | 33 | ### Dispatcher 34 | ```js 35 | /* 36 | * Dispatchers are simple action dispatchers for stores. 37 | * Stores handle the related action. 38 | */ 39 | @Dispatcher({ 40 | stores: { 41 | 'increment': store 42 | } 43 | }) 44 | class TodoDispatcher { 45 | setData(data) { 46 | this.dispatch('incoming-data', data); 47 | } 48 | } 49 | ``` 50 | 51 | ### Action Creators 52 | ```js 53 | /* 54 | * Action Creators are simple controllers. They are simple functions. 55 | * They talk to dispatchers. They are not required. 56 | */ 57 | var Actions = { 58 | setData(data) { 59 | Dispatcher.setData(data); 60 | } 61 | 62 | getDataFromServer() { 63 | fetch('/somewhere', (data)=> { 64 | Dispatcher.setData(data); 65 | }); 66 | } 67 | } 68 | ``` 69 | 70 | ### Misc ... 71 | ```js 72 | // The data cycle. 73 | store.onChange(() => { 74 | // End of data cycle. 75 | document.getElementById('result').innerText = store.store.data; 76 | }); 77 | 78 | document.getElementById('dataChanger').onclick = () => { 79 | // Start data cycle: 80 | Actions.setData(Math.random()); 81 | }; 82 | ``` 83 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json') 6 | }); 7 | 8 | grunt.loadTasks('build/tasks'); 9 | 10 | grunt.registerTask('default', ['jscs', 'jshint', 'browserify', 'concat', 'uglify']); 11 | grunt.registerTask('test', ['karma', 'docco']); 12 | grunt.registerTask('dev', ['connect', 'watch']); 13 | }; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeLorean.js 2 | 3 | [![Build Status](https://travis-ci.org/deloreanjs/delorean.svg?branch=master)](https://travis-ci.org/deloreanjs/delorean) 4 | [![NPM version](https://badge.fury.io/js/delorean.svg)](http://badge.fury.io/js/delorean) 5 | ![Coverage](http://progressed.io/bar/84?title=coverage) 6 | 7 | DeLorean is a tiny Flux pattern implementation. 8 | 9 | - **Unidirectional data flow**, it makes your app logic **simpler than MVC**, 10 | - Automatically listens to data changes and keeps your data updated, 11 | - Makes data more **consistent** across your whole application, 12 | - It's framework agnostic, completely. There's **no view framework dependency**. 13 | - Very small, just **5K** gzipped. 14 | - Built-in **React.js** integration, easy to use with **Flight.js** and **Ractive.js** and probably all others. 15 | - Improve your UI/data consistency using **rollbacks**. 16 | 17 | ### Tutorial 18 | 19 | You can learn Flux and DeLorean.js in minutes. [Read the tutorial](./docs/tutorial.md) 20 | 21 | ## Using with Frameworks 22 | 23 | - [Try **React.js** example on JSFiddle](http://jsfiddle.net/smadad/m2r0xo70/3/) 24 | - [Try **Flight.js** example on JSFiddle](http://jsfiddle.net/smadad/hz9nahga/1/) 25 | - [Try **Ractive.js** example on JSFiddle](http://jsfiddle.net/2r1k2k90/33/) 26 | 27 | --- 28 | 29 | ## Install 30 | 31 | You can install **DeLorean** with Bower: 32 | 33 | ```bash 34 | bower install delorean 35 | ``` 36 | 37 | You can also install by NPM to use with **Browserify** *(recommended)* 38 | 39 | ```bash 40 | npm install delorean 41 | ``` 42 | 43 | ## Usage 44 | 45 | Hipster way: 46 | 47 | ```js 48 | var Flux = require('delorean').Flux; 49 | // ... 50 | ``` 51 | 52 | Old-skool way: 53 | 54 | ```html 55 | 56 | 60 | ``` 61 | 62 | ## Overview 63 | 64 | ```javascript 65 | var Flux = DeLorean.Flux; 66 | /* 67 | * Stores are simple data buckets which manages data. 68 | */ 69 | var Store = Flux.createStore({ 70 | data: null, 71 | setData: function (data) { 72 | this.data = data; 73 | this.emit('change'); 74 | }, 75 | actions: { 76 | 'incoming-data': 'setData' 77 | } 78 | }); 79 | var store = Store; 80 | 81 | /* 82 | * Dispatcher are simple action dispatchers for stores. 83 | * Stores handle the related action. 84 | */ 85 | var Dispatcher = Flux.createDispatcher({ 86 | setData: function (data) { 87 | this.dispatch('incoming-data', data); 88 | }, 89 | getStores: function () { 90 | return {increment: store}; 91 | } 92 | }); 93 | 94 | /* 95 | * Action Creators are simple controllers. They are simple functions. 96 | * They talk to dispatchers. They are not required. 97 | */ 98 | var Actions = { 99 | setData: function (data) { 100 | Dispatcher.setData(data); 101 | } 102 | }; 103 | 104 | // The data cycle. 105 | store.onChange(function () { 106 | // End of data cycle. 107 | document.getElementById('result').innerText = store.data; 108 | }); 109 | 110 | document.getElementById('dataChanger').onclick = function () { 111 | // Start data cycle: 112 | Actions.setData(Math.random()); 113 | }; 114 | ``` 115 | [Run this example on JSFiddle](http://jsfiddle.net/smadad/tL4mctjd/1/) 116 | 117 | ## Docs 118 | 119 | You can read the [tutorial](./docs/tutorial.md) to get started 120 | **DeLorean.js** with your favorite framework. 121 | 122 | ### Basic Concepts 123 | 124 | - [**Store**: A postbox](./docs/stores.md) 125 | - [**Dispatcher**: The postman, drops mail in the postboxes](./docs/dispatchers.md) 126 | - [**View (or Component)**: Box owner, checks the box for mail](./docs/views.md) 127 | - [**Action Creator**: The post office, manages postmen](./docs/actions.md) 128 | 129 | Or you can visit [documents](./docs) page. 130 | 131 | ## Running the TodoMVC example 132 | 133 | There is a simple TodoMVC example working with DeLorean.js 134 | 135 | ```bash 136 | cd examples/todomvc 137 | grunt 138 | open index.html 139 | ``` 140 | 141 | ## Authors 142 | 143 | - Fatih Kadir Akin [@f](https://github.com/f) 144 | - Burak Can [@burakcan](https://github.com/burakcan) 145 | - Darcy Adams [@darcyadams](https://github.com/darcyadams) 146 | 147 | ## Contributors 148 | 149 | - Tom Moor [@tommoor](https://github.com/tommoor) 150 | - Tim Branyen [@tbranyen](https://github.com/tbranyen) 151 | - Quang Van [@quangv](https://github.com/quangv) 152 | - James H. Edwards [@incrediblesound](https://github.com/incrediblesound) 153 | - Fehmi Can Sağlam [@fehmicansaglam](https://github.com/fehmicansaglam) 154 | - Serge van den Oever [@svdoever](https://github.com/svdoever) 155 | - Markus Ast [@rkusa](https://github.com/rkusa) 156 | - Peter Rumenov Denev [@peterdenev](https://github.com/peterdenev) 157 | 158 | ## Contribution 159 | 160 | ```bash 161 | git clone git@github.com:deloreanjs/delorean.git 162 | cd delorean 163 | git checkout -b your-feature-branch 164 | ``` 165 | 166 | After you make some changes and add your test cases to the `test/spec/*Spec.js` 167 | files. please run: 168 | 169 | ```bash 170 | grunt 171 | grunt test 172 | ``` 173 | 174 | When it's all OK, [open a pull request](https://github.com/deloreanjs/delorean/compare/). 175 | 176 | ## License 177 | 178 | [MIT License](http://f.mit-license.org) 179 | 180 | ## Name 181 | 182 | The **flux capacitor** was the core component of Doctor Emmett Brown's **DeLorean time machine** 183 | 184 | ## Links about DeLorean.js 185 | 186 | - [http://dailyjs.com/2014/08/19/delorean-cash/](http://dailyjs.com/2014/08/19/delorean-cash/) 187 | - [https://reactjsnews.com/the-state-of-flux/](https://reactjsnews.com/the-state-of-flux/) 188 | - [http://facebook.github.io/react/blog/2014/10/17/community-roundup-23.html](http://facebook.github.io/react/blog/2014/10/17/community-roundup-23.html) 189 | - [https://scotch.io/tutorials/getting-to-know-flux-the-react-js-architecture](https://scotch.io/tutorials/getting-to-know-flux-the-react-js-architecture) 190 | - [http://thewebplatform.libsyn.com/flux-application-architecture-react](http://thewebplatform.libsyn.com/flux-application-architecture-react) 191 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - Split README file into chapters. 2 | - Fix English grammar mistakes in README. 3 | - Draw the flow with SVG or something else. 4 | - Build a webpage. 5 | - Build Backbone.js plugin. 6 | - Build Flight.js mixin. 7 | - Seperate React and Flight mixins from source. 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delorean.js", 3 | "version": "1.0.0", 4 | "homepage": "http://deloreanjs.com", 5 | "authors": [ 6 | "Fatih Kadir Akin " 7 | ], 8 | "description": "Flux Architecture Framework", 9 | "main": "dist/delorean.min.js", 10 | "moduleType": [ 11 | "node" 12 | ], 13 | "keywords": [ 14 | "flux", 15 | "delorean", 16 | "react" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests", 25 | "src", 26 | "example" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /build/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('browserify', { 5 | dist: { 6 | files: { 7 | 'dist/.tmp/delorean-requirements.js': 'src/requirements.js' 8 | } 9 | } 10 | }); 11 | 12 | grunt.loadNpmTasks('grunt-browserify'); 13 | }; 14 | -------------------------------------------------------------------------------- /build/tasks/concat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('concat', { 5 | options: { 6 | separator: ';', 7 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 8 | '<%= grunt.template.today("yyyy-mm-dd") %> */\n' 9 | }, 10 | build: { 11 | src: ['src/delorean.js', 'dist/.tmp/delorean-requirements.js'], 12 | dest: 'dist/delorean.js' 13 | } 14 | }); 15 | 16 | grunt.loadNpmTasks('grunt-contrib-concat'); 17 | }; 18 | -------------------------------------------------------------------------------- /build/tasks/connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('connect', { 5 | server: { 6 | options: { 7 | livereload: true, 8 | port: 9001, 9 | base: 'coverage/' 10 | } 11 | } 12 | }); 13 | 14 | grunt.loadNpmTasks('grunt-contrib-connect'); 15 | }; 16 | -------------------------------------------------------------------------------- /build/tasks/docco.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('docco', { 5 | dist: { 6 | src: ['src/*.js'], 7 | options: { 8 | output: 'docs/api/' 9 | } 10 | } 11 | }); 12 | 13 | grunt.loadNpmTasks('grunt-docco'); 14 | }; 15 | -------------------------------------------------------------------------------- /build/tasks/jscs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('jscs', { 5 | src: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js', '!test/vendor/**/*.js'], 6 | options: { 7 | config: '.jscsrc', 8 | validateIndentation: 2, 9 | disallowDanglingUnderscores: null, 10 | disallowMultipleVarDecl: null, 11 | requireMultipleVarDecl: null 12 | } 13 | }); 14 | 15 | grunt.loadNpmTasks('grunt-jscs-checker'); 16 | }; 17 | -------------------------------------------------------------------------------- /build/tasks/jshint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('jshint', { 5 | options: { 6 | laxbreak: true, 7 | node: true, 8 | eqnull: true 9 | }, 10 | all: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js', '!test/vendor/**/*.js'] 11 | }); 12 | 13 | grunt.loadNpmTasks('grunt-contrib-jshint'); 14 | }; 15 | -------------------------------------------------------------------------------- /build/tasks/karma.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('karma', { 5 | unit: { 6 | configFile: 'test/karma.conf.js' 7 | } 8 | }); 9 | 10 | grunt.loadNpmTasks('grunt-karma'); 11 | }; 12 | -------------------------------------------------------------------------------- /build/tasks/release.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('release', { 5 | options: { 6 | files: ['package.json', 'bower.json'] 7 | } 8 | }); 9 | 10 | grunt.loadNpmTasks('grunt-release'); 11 | }; 12 | -------------------------------------------------------------------------------- /build/tasks/uglify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('uglify', { 5 | options: { 6 | sourceMap: true, 7 | mangle: { 8 | except: ['DeLorean', 'Store', 'Dispatcher', 'Flux'] 9 | } 10 | }, 11 | build: { 12 | files: { 13 | 'dist/delorean.min.js': ['dist/delorean.js'] 14 | } 15 | } 16 | }); 17 | 18 | grunt.loadNpmTasks('grunt-contrib-uglify'); 19 | }; 20 | -------------------------------------------------------------------------------- /build/tasks/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.config('watch', { 5 | development: { 6 | files: ['!src/index.js', 'src/**/*.js', 'test/**/*.js'], 7 | tasks: ['default'], 8 | options: { 9 | livereload: true 10 | } 11 | } 12 | }); 13 | 14 | grunt.loadNpmTasks('grunt-contrib-watch'); 15 | }; 16 | -------------------------------------------------------------------------------- /dist/.tmp/delorean-requires.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._events[type].length > m) { 754 | this._events[type].warned = true; 755 | console.error('(node) warning: possible EventEmitter memory ' + 756 | 'leak detected. %d listeners added. ' + 757 | 'Use emitter.setMaxListeners() to increase limit.', 758 | this._events[type].length); 759 | if (typeof console.trace === 'function') { 760 | // not supported in IE 10 761 | console.trace(); 762 | } 763 | } 764 | } 765 | 766 | return this; 767 | }; 768 | 769 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 770 | 771 | EventEmitter.prototype.once = function(type, listener) { 772 | if (!isFunction(listener)) 773 | throw TypeError('listener must be a function'); 774 | 775 | var fired = false; 776 | 777 | function g() { 778 | this.removeListener(type, g); 779 | 780 | if (!fired) { 781 | fired = true; 782 | listener.apply(this, arguments); 783 | } 784 | } 785 | 786 | g.listener = listener; 787 | this.on(type, g); 788 | 789 | return this; 790 | }; 791 | 792 | // emits a 'removeListener' event iff the listener was removed 793 | EventEmitter.prototype.removeListener = function(type, listener) { 794 | var list, position, length, i; 795 | 796 | if (!isFunction(listener)) 797 | throw TypeError('listener must be a function'); 798 | 799 | if (!this._events || !this._events[type]) 800 | return this; 801 | 802 | list = this._events[type]; 803 | length = list.length; 804 | position = -1; 805 | 806 | if (list === listener || 807 | (isFunction(list.listener) && list.listener === listener)) { 808 | delete this._events[type]; 809 | if (this._events.removeListener) 810 | this.emit('removeListener', type, listener); 811 | 812 | } else if (isObject(list)) { 813 | for (i = length; i-- > 0;) { 814 | if (list[i] === listener || 815 | (list[i].listener && list[i].listener === listener)) { 816 | position = i; 817 | break; 818 | } 819 | } 820 | 821 | if (position < 0) 822 | return this; 823 | 824 | if (list.length === 1) { 825 | list.length = 0; 826 | delete this._events[type]; 827 | } else { 828 | list.splice(position, 1); 829 | } 830 | 831 | if (this._events.removeListener) 832 | this.emit('removeListener', type, listener); 833 | } 834 | 835 | return this; 836 | }; 837 | 838 | EventEmitter.prototype.removeAllListeners = function(type) { 839 | var key, listeners; 840 | 841 | if (!this._events) 842 | return this; 843 | 844 | // not listening for removeListener, no need to emit 845 | if (!this._events.removeListener) { 846 | if (arguments.length === 0) 847 | this._events = {}; 848 | else if (this._events[type]) 849 | delete this._events[type]; 850 | return this; 851 | } 852 | 853 | // emit removeListener for all listeners on all events 854 | if (arguments.length === 0) { 855 | for (key in this._events) { 856 | if (key === 'removeListener') continue; 857 | this.removeAllListeners(key); 858 | } 859 | this.removeAllListeners('removeListener'); 860 | this._events = {}; 861 | return this; 862 | } 863 | 864 | listeners = this._events[type]; 865 | 866 | if (isFunction(listeners)) { 867 | this.removeListener(type, listeners); 868 | } else { 869 | // LIFO order 870 | while (listeners.length) 871 | this.removeListener(type, listeners[listeners.length - 1]); 872 | } 873 | delete this._events[type]; 874 | 875 | return this; 876 | }; 877 | 878 | EventEmitter.prototype.listeners = function(type) { 879 | var ret; 880 | if (!this._events || !this._events[type]) 881 | ret = []; 882 | else if (isFunction(this._events[type])) 883 | ret = [this._events[type]]; 884 | else 885 | ret = this._events[type].slice(); 886 | return ret; 887 | }; 888 | 889 | EventEmitter.listenerCount = function(emitter, type) { 890 | var ret; 891 | if (!emitter._events || !emitter._events[type]) 892 | ret = 0; 893 | else if (isFunction(emitter._events[type])) 894 | ret = 1; 895 | else 896 | ret = emitter._events[type].length; 897 | return ret; 898 | }; 899 | 900 | function isFunction(arg) { 901 | return typeof arg === 'function'; 902 | } 903 | 904 | function isNumber(arg) { 905 | return typeof arg === 'number'; 906 | } 907 | 908 | function isObject(arg) { 909 | return typeof arg === 'object' && arg !== null; 910 | } 911 | 912 | function isUndefined(arg) { 913 | return arg === void 0; 914 | } 915 | 916 | },{}],12:[function(require,module,exports){ 917 | // shim for using process in browser 918 | 919 | var process = module.exports = {}; 920 | 921 | process.nextTick = (function () { 922 | var canSetImmediate = typeof window !== 'undefined' 923 | && window.setImmediate; 924 | var canPost = typeof window !== 'undefined' 925 | && window.postMessage && window.addEventListener 926 | ; 927 | 928 | if (canSetImmediate) { 929 | return function (f) { return window.setImmediate(f) }; 930 | } 931 | 932 | if (canPost) { 933 | var queue = []; 934 | window.addEventListener('message', function (ev) { 935 | var source = ev.source; 936 | if ((source === window || source === null) && ev.data === 'process-tick') { 937 | ev.stopPropagation(); 938 | if (queue.length > 0) { 939 | var fn = queue.shift(); 940 | fn(); 941 | } 942 | } 943 | }, true); 944 | 945 | return function nextTick(fn) { 946 | queue.push(fn); 947 | window.postMessage('process-tick', '*'); 948 | }; 949 | } 950 | 951 | return function nextTick(fn) { 952 | setTimeout(fn, 0); 953 | }; 954 | })(); 955 | 956 | process.title = 'browser'; 957 | process.browser = true; 958 | process.env = {}; 959 | process.argv = []; 960 | 961 | function noop() {} 962 | 963 | process.on = noop; 964 | process.addListener = noop; 965 | process.once = noop; 966 | process.off = noop; 967 | process.removeListener = noop; 968 | process.removeAllListeners = noop; 969 | process.emit = noop; 970 | 971 | process.binding = function (name) { 972 | throw new Error('process.binding is not supported'); 973 | } 974 | 975 | // TODO(shtylman) 976 | process.cwd = function () { return '/' }; 977 | process.chdir = function (dir) { 978 | throw new Error('process.chdir is not supported'); 979 | }; 980 | 981 | },{}],13:[function(require,module,exports){ 982 | // You can change dependencies using `DeLorean.Flux.define`. There are 983 | // two dependencies now: `EventEmitter` and `Promise` 984 | var requirements; 985 | 986 | module.exports = requirements = { 987 | // DeLorean uses **Node.js native EventEmitter** for event emittion 988 | EventEmitter: require('events').EventEmitter, 989 | // and **es6-promise** for Deferred object management. 990 | Promise: require('es6-promise').Promise, 991 | 992 | hello: 'world' 993 | }; 994 | // It's better you don't change them if you really need to. 995 | 996 | // This library needs to work for Browserify and also standalone. 997 | // If DeLorean is defined, it means it's called from the browser, not 998 | // the browserify. 999 | if (typeof DeLorean !== 'undefined') { 1000 | for (var requirement in requirements) { 1001 | DeLorean.Flux.define(requirement, requirements[requirement]); 1002 | } 1003 | } 1004 | 1005 | },{"es6-promise":1,"events":11}]},{},[13]); 1006 | -------------------------------------------------------------------------------- /dist/delorean.min.js: -------------------------------------------------------------------------------- 1 | !function(DeLorean){"use strict";function a(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function b(a){return"action:"+a}function c(a){return"original:"+a}function d(a){if(null==DeLorean.dispatcher)throw'No dispatcher found. The DeLoreanJS mixin requires a "dispatcher" has been created using Flux.createDispatcher.';return DeLorean.dispatcher}function e(b){if(null===b||"object"!=typeof b)return b;var c=b.constructor();for(var d in b)a(b,d)&&(c[d]=e(b[d]));return c}function f(a,b){b=e(b);for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}var Dispatcher,Store;if(Dispatcher=function(){function a(a){function b(){for(var b in a)a[b].listener.emit("__rollback")}for(var c in a)a[c].listener.on("rollback",b)}function Dispatcher(b){DeLorean.EventEmitter.defaultMaxListeners=50,this.listener=new DeLorean.EventEmitter,this.stores=b,a(Object.keys(b).map(function(a){return b[a]}))}return Dispatcher.prototype.dispatch=function(){var a,b,c,d=this;c=Array.prototype.slice.call(arguments),this.listener.emit.apply(this.listener,["dispatch"].concat(c)),a=function(){var a,b=[];for(var c in d.stores){if(a=d.stores[c],!a instanceof Store)throw"Given store is not a store instance";b.push(a)}return b}(),b=this.waitFor(a,c[0]);for(var e in d.stores)d.stores[e].dispatchAction.apply(d.stores[e],c);return b},Dispatcher.prototype.waitFor=function(a,b){var c,d=this;return c=function(){function c(a){return new DeLorean.Promise(function(c,d){a.listener.once("change",c),a.listener.once("rollback",d),a.listener.once("cleanup_"+b,function(){a.listener.removeListener("change",c),a.listener.removeListener("rollback",d)})})}var d,e=[];for(var f in a)a[f].actions&&null!=a[f].actions[b]&&(d=c(a[f]),e.push(d));return e}(),DeLorean.Promise.all(c).then(function(){d.listener.emit("change:all")})},Dispatcher.prototype.registerAction=function(a,b){if("function"!=typeof b)throw"Action callback should be a function.";this[a]=b.bind(this.stores)},Dispatcher.prototype.register=function(a){if("function"!=typeof a)throw"Global callback should be a function.";this.listener.on("dispatch",a)},Dispatcher.prototype.getStore=function(a){if(!this.stores[a])throw"Store "+a+" does not exist.";return this.stores[a].getState()},Dispatcher.prototype.on=function(){return this.listener.on.apply(this.listener,arguments)},Dispatcher.prototype.off=function(){return this.listener.removeListener.apply(this.listener,arguments)},Dispatcher.prototype.emit=function(){return this.listener.emit.apply(this.listener,arguments)},Dispatcher}(),Store=function(){function Store(a){this.state||(this.state={}),this.listener=new DeLorean.EventEmitter,this.bindActions(),this.buildScheme(),this.initialize.apply(this,arguments)}return Store.prototype.initialize=function(){},Store.prototype.get=function(a){return this.state[a]},Store.prototype.set=function(a,b){var c=[];if("object"==typeof a)for(var d in a)c.push(d),this.setValue(d,a[d]);else c.push(a),this.setValue(a,b);return this.recalculate(c),this.state[a]},Store.prototype.setValue=function(a,b){var d,e=this.scheme;return e&&this.scheme[a]?(d=e[a],this.state[a]="undefined"!=typeof b?b:d["default"],"function"==typeof d.calculate&&(this.state[c(a)]=b,this.state[a]=d.calculate.call(this,b))):null!=console&&console.warn("Scheme must include the key, "+a+", you are trying to set. "+a+" will NOT be set on the store."),this.state[a]},Store.prototype.formatScheme=function(a){var b,c,d,e={};for(var f in a)b=a[f],c=null,d=null,e[f]={"default":null},c=b&&"object"==typeof b?b["default"]:b,e[f]["default"]=c,b&&"function"==typeof b.calculate?(d=b.calculate,b.deps?e[f].deps=b.deps:e[f].deps=[]):"function"==typeof b&&(d=b),d&&(e[f].calculate=d);return e},Store.prototype.buildScheme=function(){var a,b,d,f,g,h,i=[];if("object"==typeof this.scheme){a=this.scheme=this.formatScheme(this.scheme),f=this.__dependencyMap={};for(b in a)d=a[b],this.state[b]=e(d["default"]);for(b in a)if(d=a[b],d.calculate){g=d.deps||[];for(var j=0;j0&&this.recalculate(h),this.listener.emit("change")},Store.prototype.getState=function(){return this.state},Store.prototype.clearState=function(){return this.state={},this},Store.prototype.resetState=function(){return this.buildScheme(),this.listener.emit("change"),this},Store.prototype.bindActions=function(){var c;this.emitChange=this.listener.emit.bind(this.listener,"change"),this.emitRollback=this.listener.emit.bind(this.listener,"rollback"),this.rollback=this.listener.on.bind(this.listener,"__rollback"),this.emit=this.listener.emit.bind(this.listener);for(var d in this.actions)if(a(this.actions,d)){if(c=this.actions[d],"function"!=typeof this[c])throw"Callback '"+c+"' defined for action '"+d+"' should be a method defined on the store!";this.listener.on(b(d),this[c].bind(this))}},Store.prototype.dispatchAction=function(a,c){this.listener.emit(b(a),c),this.listener.emit("cleanup_"+a)},Store.prototype.listenChanges=function(a){var b,c=this;return Object.observe?(b=Array.isArray(a)?Array.observe:Object.observe,void b(a,function(a){c.listener.emit("change",a)})):void console.error("Store#listenChanges method uses Object.observe, you should fire changes manually.")},Store.prototype.onChange=function(a){this.listener.on("change",a)},Store}(),DeLorean.Flux={createStore:function(a){if("object"!=typeof a)throw"Stores should be defined by passing the definition to the constructor";var b=function(){return Store.apply(this,arguments)},c=function(){this.constructor=b};return c.prototype=Store.prototype,b.prototype=new c,f(b.prototype,a),new b},createDispatcher:function(b){var c,d,e,f,g;"function"==typeof b.getStores&&(c=b.getStores()),d=new Dispatcher(c||{});for(var h in b)a(b,h)&&"getStores"!==h&&"viewTriggers"!==h&&"function"==typeof b[h]&&(e=b[h],d.registerAction(h,e.bind(d)));f=b.viewTriggers;for(var i in f)g=f[i],"function"==typeof d[g]?d.on(i,d[g]):null!=console&&console.warn(g+" should be a method defined on your dispatcher. The "+i+" trigger will not be bound to any method.");return null!=DeLorean.dispatcher&&null!=console&&console.warn("You are attempting to create more than one dispatcher. DeLorean is intended to be used with a single dispatcher. This latest dispatcher created will overwrite any previous versions."),DeLorean.dispatcher=d,d},define:function(a,b){DeLorean[a]=b}},DeLorean.Dispatcher=Dispatcher,DeLorean.Store=Store,DeLorean.Flux.mixins={trigger:{componentWillMount:function(){this.__dispatcher=d(this)},trigger:function(){this.__dispatcher.emit.apply(this.__dispatcher,arguments)}},storeListener:{trigger:function(){this.__dispatcher.emit.apply(this.__dispatcher,arguments)},componentDidMount:function(){function b(a,b){return function(){var a;e.setState(e.getStoreStates()),e.storeDidChange&&(a=[b].concat(Array.prototype.slice.call(arguments,0)),e.storeDidChange.apply(e,a))}}var c,d,e=this;this.__changeHandlers={};for(d in this.__watchStores)a(this.stores,d)&&(c=this.stores[d],this.__changeHandlers[d]=b(c,d),c.onChange(this.__changeHandlers[d]))},componentWillUnmount:function(){for(var b in this.__changeHandlers)if(a(this.stores,b)){var c=this.stores[b];c.listener.removeListener("change",this.__changeHandlers[b])}},getInitialState:function(){var a,b,c=this;if(this.__dispatcher=d(this),this.storesDidChange&&this.__dispatcher.on("change:all",function(){c.storesDidChange()}),this.stores=this.__dispatcher.stores,this.__watchStores={},b=this.watchStores||this.props.watchStores,null!=b)for(var e=0;e4&&console.warn('Your component is watching changes on all stores, you may want to define a "watchStores" property in order to only watch stores relevant to this component.');return this.getStoreStates()},getStoreStates:function(){var b={stores:{}};for(var c in this.__watchStores)a(this.stores,c)&&(b.stores[c]=this.__watchStores[c].getState());return b},getStore:function(a){if(null!=console&&"undefined"==typeof this.__watchStores[a]){var b;b="Attempt to getStore "+a+" failed. ",b+="undefined"==typeof this.stores[a]?"It is not defined on the dispatcher. ":"It is not being watched by the component. ",b+=null!=this.constructor&&null!=this.constructor.displayName?"Check the "+this.constructor.displayName+" component.":"",console.warn(b)}return this.state.stores[a]}}},"undefined"!=typeof module&&"undefined"!=typeof module.exports){var g=require("./requirements");for(var h in g)DeLorean.Flux.define(h,g[h]);module.exports=DeLorean}else"function"==typeof define&&define.amd?define(["./requirements.js"],function(a){for(var b in a)DeLorean.Flux.define(b,a[b]);return DeLorean}):window.DeLorean=DeLorean}({}),function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;ga||isNaN(a))throw TypeError("n must be a positive number");return this._maxListeners=a,this},d.prototype.emit=function(a){var b,c,d,f,i,j;if(this._events||(this._events={}),"error"===a&&(!this._events.error||g(this._events.error)&&!this._events.error.length)){if(b=arguments[1],b instanceof Error)throw b;throw TypeError('Uncaught, unspecified "error" event.')}if(c=this._events[a],h(c))return!1;if(e(c))switch(arguments.length){case 1:c.call(this);break;case 2:c.call(this,arguments[1]);break;case 3:c.call(this,arguments[1],arguments[2]);break;default:for(d=arguments.length,f=new Array(d-1),i=1;d>i;i++)f[i-1]=arguments[i];c.apply(this,f)}else if(g(c)){for(d=arguments.length,f=new Array(d-1),i=1;d>i;i++)f[i-1]=arguments[i];for(j=c.slice(),d=j.length,i=0;d>i;i++)j[i].apply(this,f)}return!0},d.prototype.addListener=function(a,b){var c;if(!e(b))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",a,e(b.listener)?b.listener:b),this._events[a]?g(this._events[a])?this._events[a].push(b):this._events[a]=[this._events[a],b]:this._events[a]=b,g(this._events[a])&&!this._events[a].warned){var c;c=h(this._maxListeners)?d.defaultMaxListeners:this._maxListeners,c&&c>0&&this._events[a].length>c&&(this._events[a].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[a].length),"function"==typeof console.trace&&console.trace())}return this},d.prototype.on=d.prototype.addListener,d.prototype.once=function(a,b){function c(){this.removeListener(a,c),d||(d=!0,b.apply(this,arguments))}if(!e(b))throw TypeError("listener must be a function");var d=!1;return c.listener=b,this.on(a,c),this},d.prototype.removeListener=function(a,b){var c,d,f,h;if(!e(b))throw TypeError("listener must be a function");if(!this._events||!this._events[a])return this;if(c=this._events[a],f=c.length,d=-1,c===b||e(c.listener)&&c.listener===b)delete this._events[a],this._events.removeListener&&this.emit("removeListener",a,b);else if(g(c)){for(h=f;h-->0;)if(c[h]===b||c[h].listener&&c[h].listener===b){d=h;break}if(0>d)return this;1===c.length?(c.length=0,delete this._events[a]):c.splice(d,1),this._events.removeListener&&this.emit("removeListener",a,b)}return this},d.prototype.removeAllListeners=function(a){var b,c;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[a]&&delete this._events[a],this;if(0===arguments.length){for(b in this._events)"removeListener"!==b&&this.removeAllListeners(b);return this.removeAllListeners("removeListener"),this._events={},this}if(c=this._events[a],e(c))this.removeListener(a,c);else for(;c.length;)this.removeListener(a,c[c.length-1]);return delete this._events[a],this},d.prototype.listeners=function(a){var b;return b=this._events&&this._events[a]?e(this._events[a])?[this._events[a]]:this._events[a].slice():[]},d.listenerCount=function(a,b){var c;return c=a._events&&a._events[b]?e(a._events[b])?1:a._events[b].length:0}},{}],12:[function(a,b,c){function d(){}var e=b.exports={};e.nextTick=function(){var a="undefined"!=typeof window&&window.setImmediate,b="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(a)return function(a){return window.setImmediate(a)};if(b){var c=[];return window.addEventListener("message",function(a){var b=a.source;if((b===window||null===b)&&"process-tick"===a.data&&(a.stopPropagation(),c.length>0)){var d=c.shift();d()}},!0),function(a){c.push(a),window.postMessage("process-tick","*")}}return function(a){setTimeout(a,0)}}(),e.title="browser",e.browser=!0,e.env={},e.argv=[],e.on=d,e.addListener=d,e.once=d,e.off=d,e.removeListener=d,e.removeAllListeners=d,e.emit=d,e.binding=function(a){throw new Error("process.binding is not supported")},e.cwd=function(){return"/"},e.chdir=function(a){throw new Error("process.chdir is not supported")}},{}],13:[function(a,b,c){var d;if("undefined"!=typeof b&&"undefined"!=typeof b.exports?b.exports=d={EventEmitter:a("events").EventEmitter,Promise:a("es6-promise").Promise}:"function"==typeof define&&define.amd?define(function(a,b,c){a("events"),a("es6-promise");return{EventEmitter:a("events").EventEmitter,Promise:a("es6-promise").Promise}}):window.DeLorean=DeLorean,"undefined"!=typeof DeLorean)for(var e in d)DeLorean.Flux.define(e,d[e])},{"es6-promise":1,events:11}]},{},[13]); 2 | //# sourceMappingURL=delorean.min.js.map -------------------------------------------------------------------------------- /dist/delorean.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"delorean.min.js","sources":["delorean.js"],"names":["DeLorean","__hasOwn","object","prop","Object","prototype","hasOwnProperty","call","__generateActionName","name","__generateOriginalName","__findDispatcher","view","dispatcher","__clone","obj","copy","constructor","attr","__extend","props","Dispatcher","Store","__rollbackListener","stores","__listener","i","listener","emit","j","on","EventEmitter","defaultMaxListeners","this","keys","map","key","dispatch","deferred","args","self","Array","slice","arguments","apply","concat","store","storeName","push","waitFor","dispatchAction","actionName","promises","__promiseGenerator","Promise","resolve","reject","once","removeListener","promise","__promises","actions","all","then","registerAction","action","callback","bind","register","getStore","getState","off","state","bindActions","buildScheme","initialize","get","arg","set","arg1","value","changedProps","keyName","setValue","recalculate","definition","scheme","calculate","console","warn","formatScheme","defaultValue","calculatedValue","formattedScheme","default","deps","dependencyMap","dependents","dep","__dependencyMap","length","didRun","d","indexOf","clearState","resetState","emitChange","emitRollback","rollback","data","listenChanges","observer","observe","isArray","changes","error","onChange","Flux","createStore","Child","Surrogate","createDispatcher","actionsToDispatch","actionsOfStores","triggers","triggerMethod","getStores","viewTriggers","triggerName","define","mixins","trigger","componentWillMount","__dispatcher","storeListener","componentDidMount","__changeHandler","setState","getStoreStates","storeDidChange","__changeHandlers","__watchStores","componentWillUnmount","getInitialState","watchStores","storesDidChange","message","displayName","module","exports","requirements","require","requirement","amd","window","e","t","n","r","s","o","u","a","Error","f",1,"polyfill","./promise/polyfill","./promise/promise",2,"TypeError","resolver","index","resolveAll","results","remaining","isFunction","./utils",3,"process","global","useNextTick","nextTick","flush","useMutationObserver","iterations","BrowserMutationObserver","node","document","createTextNode","characterData","useSetTimeout","local","setTimeout","queue","tuple","asap","scheduleFlush","browserGlobal","MutationObserver","WebKitMutationObserver","undefined","toString","JkpR2F",4,"configure","config","instrument",5,"es6PromiseSupport","RSVPPromise","./promise",6,"_subscribers","invokeResolver","resolvePromise","rejectPromise","reason","invokeCallback","settled","detail","succeeded","failed","hasCallback","handleThenable","FULFILLED","REJECTED","subscribe","parent","child","onFulfillment","onRejection","subscribers","publish","_detail","resolved","objectOrFunction","val","fulfill","_state","PENDING","SEALED","async","publishFulfillment","publishRejection","now","race","staticResolve","staticReject","thenPromise","callbacks","catch","./all","./asap","./config","./race","./reject","./resolve",7,8,9,10,"x","Date","getTime",11,"_events","_maxListeners","isNumber","isObject","isUndefined","setMaxListeners","isNaN","type","er","handler","len","listeners","addListener","m","newListener","warned","trace","g","fired","list","position","splice","removeAllListeners","ret","listenerCount","emitter",12,"noop","canSetImmediate","setImmediate","canPost","postMessage","addEventListener","ev","source","stopPropagation","fn","shift","title","browser","env","argv","binding","cwd","chdir","dir",13,"es6-promise","events"],"mappings":"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"} -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## What is Flux? 2 | 3 | Data in a Flux application flows in a single direction, in a cycle. 4 | 5 | - [**Store**: A postbox](./stores.md) 6 | - [**Dispatcher**: The postman, drops mail in the postboxes](./dispatchers.md) 7 | - [**View (or Component)**: Box owner, checks the box for mail](./views.md) 8 | - [**Action Creator**: The post office, manages postmen](./actions.md) 9 | 10 | [Read the tutorial now.](./tutorial.md) 11 | 12 | ## Flux Architecture Diagram (from Facebook) 13 | 14 | ![Flux Diagram](https://raw.githubusercontent.com/f/delorean/master/docs/asset/flux-diagram.png) 15 | -------------------------------------------------------------------------------- /docs/actions.md: -------------------------------------------------------------------------------- 1 | # Action Creators 2 | 3 | Action creators are the main controllers of the app. **They are simply objects** that 4 | manage everything. They allow you to compose data and logic. 5 | 6 | ```javascript 7 | var TodoActionCreator = { 8 | 9 | getAllTodos: function () { 10 | // It's an example for async requests. 11 | // You can do a server request. 12 | $.getJSON('/todos', function (data) { 13 | TodoListDispatcher.reset(data.todos); 14 | }); 15 | }, 16 | 17 | addTodo: function (todo) { 18 | // It statically calls dispatchers. 19 | TodoListDispatcher.addTodo(todo); 20 | }, 21 | 22 | removeTodo: function (todo) { 23 | TodoListDispatcher.removeTodo(todo); 24 | } 25 | 26 | }; 27 | ``` 28 | 29 | Then you just run `TodoActionCreator.getAllTodos()` method **to start the Flux cycle**. 30 | -------------------------------------------------------------------------------- /docs/api/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "novecento-bold"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | 79 | hr { 80 | border: 0; 81 | background: 1px #ddd; 82 | height: 1px; 83 | margin: 20px 0; 84 | } 85 | 86 | pre, tt, code { 87 | font-size: 12px; line-height: 16px; 88 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 89 | margin: 0; padding: 0; 90 | } 91 | .annotation pre { 92 | display: block; 93 | margin: 0; 94 | padding: 7px 10px; 95 | background: #fcfcfc; 96 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 97 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 98 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 99 | overflow-x: auto; 100 | } 101 | .annotation pre code { 102 | border: 0; 103 | padding: 0; 104 | background: transparent; 105 | } 106 | 107 | 108 | blockquote { 109 | border-left: 5px solid #ccc; 110 | margin: 0; 111 | padding: 1px 0 1px 1em; 112 | } 113 | .sections blockquote p { 114 | font-family: Menlo, Consolas, Monaco, monospace; 115 | font-size: 12px; line-height: 16px; 116 | color: #999; 117 | margin: 10px 0 0; 118 | white-space: pre-wrap; 119 | } 120 | 121 | ul.sections { 122 | list-style: none; 123 | padding:0 0 5px 0;; 124 | margin:0; 125 | } 126 | 127 | /* 128 | Force border-box so that % widths fit the parent 129 | container without overlap because of margin/padding. 130 | 131 | More Info : http://www.quirksmode.org/css/box.html 132 | */ 133 | ul.sections > li > div { 134 | -moz-box-sizing: border-box; /* firefox */ 135 | -ms-box-sizing: border-box; /* ie */ 136 | -webkit-box-sizing: border-box; /* webkit */ 137 | -khtml-box-sizing: border-box; /* konqueror */ 138 | box-sizing: border-box; /* css3 */ 139 | } 140 | 141 | 142 | /*---------------------- Jump Page -----------------------------*/ 143 | #jump_to, #jump_page { 144 | margin: 0; 145 | background: white; 146 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 147 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 148 | font: 16px Arial; 149 | cursor: pointer; 150 | text-align: right; 151 | list-style: none; 152 | } 153 | 154 | #jump_to a { 155 | text-decoration: none; 156 | } 157 | 158 | #jump_to a.large { 159 | display: none; 160 | } 161 | #jump_to a.small { 162 | font-size: 22px; 163 | font-weight: bold; 164 | color: #676767; 165 | } 166 | 167 | #jump_to, #jump_wrapper { 168 | position: fixed; 169 | right: 0; top: 0; 170 | padding: 10px 15px; 171 | margin:0; 172 | } 173 | 174 | #jump_wrapper { 175 | display: none; 176 | padding:0; 177 | } 178 | 179 | #jump_to:hover #jump_wrapper { 180 | display: block; 181 | } 182 | 183 | #jump_page { 184 | padding: 5px 0 3px; 185 | margin: 0 0 25px 25px; 186 | } 187 | 188 | #jump_page .source { 189 | display: block; 190 | padding: 15px; 191 | text-decoration: none; 192 | border-top: 1px solid #eee; 193 | } 194 | 195 | #jump_page .source:hover { 196 | background: #f5f5ff; 197 | } 198 | 199 | #jump_page .source:first-child { 200 | } 201 | 202 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 203 | @media only screen and (min-width: 320px) { 204 | .pilwrap { display: none; } 205 | 206 | ul.sections > li > div { 207 | display: block; 208 | padding:5px 10px 0 10px; 209 | } 210 | 211 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 212 | padding-left: 30px; 213 | } 214 | 215 | ul.sections > li > div.content { 216 | overflow-x:auto; 217 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 218 | box-shadow: inset 0 0 5px #e5e5ee; 219 | border: 1px solid #dedede; 220 | margin:5px 10px 5px 10px; 221 | padding-bottom: 5px; 222 | } 223 | 224 | ul.sections > li > div.annotation pre { 225 | margin: 7px 0 7px; 226 | padding-left: 15px; 227 | } 228 | 229 | ul.sections > li > div.annotation p tt, .annotation code { 230 | background: #f8f8ff; 231 | border: 1px solid #dedede; 232 | font-size: 12px; 233 | padding: 0 0.2em; 234 | } 235 | } 236 | 237 | /*---------------------- (> 481px) ---------------------*/ 238 | @media only screen and (min-width: 481px) { 239 | #container { 240 | position: relative; 241 | } 242 | body { 243 | background-color: #F5F5FF; 244 | font-size: 15px; 245 | line-height: 21px; 246 | } 247 | pre, tt, code { 248 | line-height: 18px; 249 | } 250 | p, ul, ol { 251 | margin: 0 0 15px; 252 | } 253 | 254 | 255 | #jump_to { 256 | padding: 5px 10px; 257 | } 258 | #jump_wrapper { 259 | padding: 0; 260 | } 261 | #jump_to, #jump_page { 262 | font: 10px Arial; 263 | text-transform: uppercase; 264 | } 265 | #jump_page .source { 266 | padding: 5px 10px; 267 | } 268 | #jump_to a.large { 269 | display: inline-block; 270 | } 271 | #jump_to a.small { 272 | display: none; 273 | } 274 | 275 | 276 | 277 | #background { 278 | position: absolute; 279 | top: 0; bottom: 0; 280 | width: 350px; 281 | background: #fff; 282 | border-right: 1px solid #e5e5ee; 283 | z-index: -1; 284 | } 285 | 286 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 287 | padding-left: 40px; 288 | } 289 | 290 | ul.sections > li { 291 | white-space: nowrap; 292 | } 293 | 294 | ul.sections > li > div { 295 | display: inline-block; 296 | } 297 | 298 | ul.sections > li > div.annotation { 299 | max-width: 350px; 300 | min-width: 350px; 301 | min-height: 5px; 302 | padding: 13px; 303 | overflow-x: hidden; 304 | white-space: normal; 305 | vertical-align: top; 306 | text-align: left; 307 | } 308 | ul.sections > li > div.annotation pre { 309 | margin: 15px 0 15px; 310 | padding-left: 15px; 311 | } 312 | 313 | ul.sections > li > div.content { 314 | padding: 13px; 315 | vertical-align: top; 316 | border: none; 317 | -webkit-box-shadow: none; 318 | box-shadow: none; 319 | } 320 | 321 | .pilwrap { 322 | position: relative; 323 | display: inline; 324 | } 325 | 326 | .pilcrow { 327 | font: 12px Arial; 328 | text-decoration: none; 329 | color: #454545; 330 | position: absolute; 331 | top: 3px; left: -20px; 332 | padding: 1px 2px; 333 | opacity: 0; 334 | -webkit-transition: opacity 0.2s linear; 335 | } 336 | .for-h1 .pilcrow { 337 | top: 47px; 338 | } 339 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 340 | top: 35px; 341 | } 342 | 343 | ul.sections > li > div.annotation:hover .pilcrow { 344 | opacity: 1; 345 | } 346 | } 347 | 348 | /*---------------------- (> 1025px) ---------------------*/ 349 | @media only screen and (min-width: 1025px) { 350 | 351 | body { 352 | font-size: 16px; 353 | line-height: 24px; 354 | } 355 | 356 | #background { 357 | width: 525px; 358 | } 359 | ul.sections > li > div.annotation { 360 | max-width: 525px; 361 | min-width: 525px; 362 | padding: 10px 25px 1px 50px; 363 | } 364 | ul.sections > li > div.content { 365 | padding: 9px 15px 16px 25px; 366 | } 367 | } 368 | 369 | /*---------------------- Syntax Highlighting -----------------------------*/ 370 | 371 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 372 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 373 | /* 374 | 375 | github.com style (c) Vasily Polovnyov 376 | 377 | */ 378 | 379 | pre code { 380 | display: block; padding: 0.5em; 381 | color: #000; 382 | background: #f8f8ff 383 | } 384 | 385 | pre .hljs-comment, 386 | pre .hljs-template_comment, 387 | pre .hljs-diff .hljs-header, 388 | pre .hljs-javadoc { 389 | color: #408080; 390 | font-style: italic 391 | } 392 | 393 | pre .hljs-keyword, 394 | pre .hljs-assignment, 395 | pre .hljs-literal, 396 | pre .hljs-css .hljs-rule .hljs-keyword, 397 | pre .hljs-winutils, 398 | pre .hljs-javascript .hljs-title, 399 | pre .hljs-lisp .hljs-title, 400 | pre .hljs-subst { 401 | color: #954121; 402 | /*font-weight: bold*/ 403 | } 404 | 405 | pre .hljs-number, 406 | pre .hljs-hexcolor { 407 | color: #40a070 408 | } 409 | 410 | pre .hljs-string, 411 | pre .hljs-tag .hljs-value, 412 | pre .hljs-phpdoc, 413 | pre .hljs-tex .hljs-formula { 414 | color: #219161; 415 | } 416 | 417 | pre .hljs-title, 418 | pre .hljs-id { 419 | color: #19469D; 420 | } 421 | pre .hljs-params { 422 | color: #00F; 423 | } 424 | 425 | pre .hljs-javascript .hljs-title, 426 | pre .hljs-lisp .hljs-title, 427 | pre .hljs-subst { 428 | font-weight: normal 429 | } 430 | 431 | pre .hljs-class .hljs-title, 432 | pre .hljs-haskell .hljs-label, 433 | pre .hljs-tex .hljs-command { 434 | color: #458; 435 | font-weight: bold 436 | } 437 | 438 | pre .hljs-tag, 439 | pre .hljs-tag .hljs-title, 440 | pre .hljs-rules .hljs-property, 441 | pre .hljs-django .hljs-tag .hljs-keyword { 442 | color: #000080; 443 | font-weight: normal 444 | } 445 | 446 | pre .hljs-attribute, 447 | pre .hljs-variable, 448 | pre .hljs-instancevar, 449 | pre .hljs-lisp .hljs-body { 450 | color: #008080 451 | } 452 | 453 | pre .hljs-regexp { 454 | color: #B68 455 | } 456 | 457 | pre .hljs-class { 458 | color: #458; 459 | font-weight: bold 460 | } 461 | 462 | pre .hljs-symbol, 463 | pre .hljs-ruby .hljs-symbol .hljs-string, 464 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 465 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 466 | pre .hljs-lisp .hljs-keyword, 467 | pre .hljs-tex .hljs-special, 468 | pre .hljs-input_number { 469 | color: #990073 470 | } 471 | 472 | pre .hljs-builtin, 473 | pre .hljs-constructor, 474 | pre .hljs-built_in, 475 | pre .hljs-lisp .hljs-title { 476 | color: #0086b3 477 | } 478 | 479 | pre .hljs-preprocessor, 480 | pre .hljs-pi, 481 | pre .hljs-doctype, 482 | pre .hljs-shebang, 483 | pre .hljs-cdata { 484 | color: #999; 485 | font-weight: bold 486 | } 487 | 488 | pre .hljs-deletion { 489 | background: #fdd 490 | } 491 | 492 | pre .hljs-addition { 493 | background: #dfd 494 | } 495 | 496 | pre .hljs-diff .hljs-change { 497 | background: #0086b3 498 | } 499 | 500 | pre .hljs-chunk { 501 | color: #aaa 502 | } 503 | 504 | pre .hljs-tex .hljs-formula { 505 | opacity: 0.5; 506 | } 507 | -------------------------------------------------------------------------------- /docs/api/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/api/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/api/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/api/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/api/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/api/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/api/public/fonts/fleurons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/fleurons.eot -------------------------------------------------------------------------------- /docs/api/public/fonts/fleurons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/fleurons.ttf -------------------------------------------------------------------------------- /docs/api/public/fonts/fleurons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/fleurons.woff -------------------------------------------------------------------------------- /docs/api/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/api/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/api/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /docs/api/public/images/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/api/public/images/gray.png -------------------------------------------------------------------------------- /docs/api/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /docs/api/requirements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | requirements.js 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 34 | 35 |
    36 | 37 |
  • 38 |
    39 |

    requirements.js

    40 |
    41 |
  • 42 | 43 | 44 | 45 |
  • 46 |
    47 | 48 |
    49 | 50 |
    51 |

    Dependency injection file.

    52 | 53 |
    54 | 55 |
  • 56 | 57 | 58 |
  • 59 |
    60 | 61 |
    62 | 63 |
    64 |

    You can change dependencies using DeLorean.Flux.define. There are 65 | two dependencies now: EventEmitter and Promise

    66 | 67 |
    68 | 69 |
    var requirements;
     70 | 
     71 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
     72 |   module.exports = requirements = {
    73 | 74 |
  • 75 | 76 | 77 |
  • 78 |
    79 | 80 |
    81 | 82 |
    83 |

    DeLorean uses Node.js native EventEmitter for event emittion

    84 | 85 |
    86 | 87 |
        EventEmitter: require('events').EventEmitter,
    88 | 89 |
  • 90 | 91 | 92 |
  • 93 |
    94 | 95 |
    96 | 97 |
    98 |

    and es6-promise for Deferred object management.

    99 | 100 |
    101 | 102 |
        Promise: require('es6-promise').Promise
    103 |   };
    104 | } else if (typeof define === 'function' && define.amd) {
    105 |   define(function (require, exports, module) {
    106 |     var events = require('events'),
    107 |         promise = require('es6-promise');
    108 | 109 |
  • 110 | 111 | 112 |
  • 113 |
    114 | 115 |
    116 | 117 |
    118 |

    Return the module value - http://requirejs.org/docs/api.html#cjsmodule 119 | Using simplified wrapper

    120 | 121 |
    122 | 123 |
        return {
    124 | 125 |
  • 126 | 127 | 128 |
  • 129 |
    130 | 131 |
    132 | 133 |
    134 |

    DeLorean uses Node.js native EventEmitter for event emittion

    135 | 136 |
    137 | 138 |
          EventEmitter: require('events').EventEmitter,
    139 | 140 |
  • 141 | 142 | 143 |
  • 144 |
    145 | 146 |
    147 | 148 |
    149 |

    and es6-promise for Deferred object management.

    150 | 151 |
    152 | 153 |
          Promise: require('es6-promise').Promise
    154 |     };
    155 |   });
    156 | } else {
    157 |   window.DeLorean = DeLorean;
    158 | }
    159 | 160 |
  • 161 | 162 | 163 |
  • 164 |
    165 | 166 |
    167 | 168 |
    169 |

    It’s better you don’t change them if you really need to.

    170 | 171 |
    172 | 173 |
  • 174 | 175 | 176 |
  • 177 |
    178 | 179 |
    180 | 181 |
    182 |

    This library needs to work for Browserify and also standalone. 183 | If DeLorean is defined, it means it’s called from the browser, not 184 | the browserify.

    185 | 186 |
    187 | 188 |
    189 | if (typeof DeLorean !== 'undefined') {
    190 |   for (var requirement in requirements) {
    191 |     DeLorean.Flux.define(requirement, requirements[requirement]);
    192 |   }
    193 | }
    194 | 195 |
  • 196 | 197 |
198 |
199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/asset/flux-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/docs/asset/flux-diagram.png -------------------------------------------------------------------------------- /docs/dispatchers.md: -------------------------------------------------------------------------------- 1 | # Dispatchers 2 | 3 | The dispatcher is the central hub that manages all data flow in a Flux application. 4 | It is essentially a registry of callbacks into the stores. Each store registers 5 | itself and provides a callback. When the dispatcher responds to an action, 6 | all stores in the application are sent the data payload provided by the 7 | action via the callbacks in the registry. 8 | 9 | When using the React mixin, any component with the Flux React mixin applied, will get the `state` of the dispatcher's stores. In flux, the dispatcher is a singleton, and should be created when the page loads 10 | with `Flux.createDispatcher` before calling `React.render`. Note: you can create multiple dispatchers 11 | with delorean, but as far as the mixin is concerned, the most recently created dispatcher is the one 12 | and only. 13 | 14 | ## `Flux.createDispatcher` 15 | 16 | ```js 17 | var TodoListApp = Flux.createDispatcher({ 18 | 19 | removeTodo: function (todo) { 20 | if (confirm('Do you really want to delete this todo?')) { 21 | this.dispatch('todo:remove', todo); 22 | } 23 | }, 24 | 25 | getStores: function () { 26 | return { 27 | todoStore: myTodos 28 | } 29 | } 30 | 31 | }); 32 | ``` 33 | 34 | ### Callback Registration and Dispatching 35 | 36 | #### Action `register` 37 | 38 | You can register global callbacks: 39 | 40 | ```js 41 | TodoDispatcher.register(function (actionName, payload) { 42 | switch (actionName) { 43 | case 'todo:add': 44 | console.log('New todo add dispatched with payload:', payload); 45 | break; 46 | } 47 | }); 48 | ``` 49 | 50 | And you can say: 51 | 52 | ```js 53 | TodoDispatcher.dispatch('todo:add', {text: 'Do your homework!'}); 54 | ``` 55 | 56 | #### Action `dispatch` 57 | 58 | When an action is dispatched, all the stores know about the status and 59 | process the data asynchronously. When all of them are finished, the dispatcher 60 | emits `change:all` event, also the `dispatch` method returns a promise. 61 | 62 | ```js 63 | var TodoListApp = Flux.createDispatcher({ 64 | 65 | removeTodo: function (todo) { 66 | if (confirm('Do you really want to delete this todo?')) { 67 | this.dispatch('todo:remove', todo) 68 | .then(function () { 69 | // All of the stores finished the process 70 | // about 'todo:remove' action 71 | alert('Item removed successfully'); 72 | }); 73 | } 74 | }, 75 | 76 | getStores: function () { 77 | return { 78 | todoStore: myTodos 79 | } 80 | } 81 | 82 | }); 83 | ``` 84 | 85 | #### `getStores()` 86 | 87 | The `getStores()` method is what hooks up your stores and React components to a dispatcher. This method should return an 88 | object with a key for each store you want to respond to `dispatch` calls. This method is also used by 89 | the React mixin, which uses it to apply state from these stores in your React components (states are 90 | generated from each store's `getState` method). 91 | 92 | 93 | #### `getStore(storeName)` 94 | 95 | `getStore` method is a shortcut to get the related store. 96 | 97 | ```js 98 | TodoListApp.getStore('todoStore'); // This will return `myTodos.store` 99 | ``` 100 | 101 | #### `waitFor([stores], event)` 102 | 103 | `waitFor` function takes arguments and the event name, and returns a promise. 104 | 105 | ```js 106 | TodoDispatcher.waitFor([todoStore, anotherStore], 'todo:add').then(function () { 107 | console.log('todoStore and anotherStore are changed now.'); 108 | }); 109 | ``` 110 | 111 | #### `viewTriggers` 112 | 113 | When working in React, view triggers offer a clean and simple API for exposing actions that are intended to be triggered 114 | from a view. This is a React specific feature, as it relies on **`Flux.mixins.storeListener`**. `viewTriggers` is a hash you define on your dispatcher, the keys are trigger names, and the values are handler method names (as strings), which you have defined on the dispatcher. The React **`Flux.mixins.storeListener`** will then expose a `trigger` method on components. 115 | `trigger` takes the trigger name as the first parameter, and `0` - `n` additoinal parameters you want to pass to you handler 116 | method. 117 | 118 | 119 | ```js 120 | var TodoListApp = Flux.createDispatcher({ 121 | 122 | viewTriggers: { 123 | 'getTodos': 'getTodos' 124 | }, 125 | 126 | getStores: function () { 127 | return { 128 | todoStore: myTodos 129 | } 130 | }, 131 | 132 | getTodos: function () { 133 | // code to GET todos 134 | }, 135 | 136 | }); 137 | 138 | 139 | var TodoListView = React.createClass({ 140 | 141 | mixins: [Flux.mixins.storeListener], 142 | 143 | render: function () { 144 | var self = this; 145 | return
    146 | {this.getStore('todoStore').todos.map(function (todo) { 147 | return 148 | })} 149 |
150 | }, 151 | 152 | componentDidMount: function() { 153 | // `trigger` method is added by the storeListener mixin 154 | // The first parameter is the viewTrigger name, additonal optional parameters can be passed after that 155 | this.trigger('getTodos'); 156 | } 157 | 158 | }); 159 | 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/routing.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | You can use any Router tool with DeLorean. In the example I use `director` as the router. 4 | 5 | ```js 6 | var Router = require('director').Router; 7 | ``` 8 | 9 | You may trig the action from View. So you can just do something like that: 10 | 11 | ```js 12 | var mainView = React.renderComponent(, 13 | document.getElementById('main')) 14 | 15 | var appRouter = new Router({ 16 | '/random': function () { 17 | TodoActionCreator.addTodo({text: Math.random()}); 18 | location.hash = '/'; 19 | } 20 | }); 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/stores.md: -------------------------------------------------------------------------------- 1 | # Stores 2 | 3 | Stores contain the application state and logic. Their role is somewhat similar 4 | to a model in a traditional MVC, but they manage the state of many objects. 5 | Unlike MVC models, they are not instances of one object, nor are they the same as Backbone's 6 | collections. More than simply managing a collection of ORM-style objects, 7 | stores manage the application state for a particular domain within the application. 8 | 9 | ## `Flux.createStore` 10 | 11 | ```js 12 | var TodoStore = Flux.createStore({ 13 | 14 | todos: [ 15 | {text: 'hello'}, 16 | {text: 'world'} 17 | ], 18 | 19 | actions: { 20 | 'todo:add': 'addTodo', 21 | 'todo:remove': 'removeTodo' 22 | }, 23 | 24 | addTodo: function (todo) { 25 | this.todos.push({text: todo.text}); 26 | this.emit('change'); 27 | }, 28 | 29 | removeTodo: function (todoToComplete) { 30 | this.todos = this.todos.filter(function (todo) { 31 | return todoToComplete.text !== todo.text 32 | }); 33 | this.emit('change'); 34 | }, 35 | 36 | getState: function () { 37 | return { 38 | todos: this.todos 39 | } 40 | } 41 | }); 42 | ``` 43 | 44 | ### `initialize` 45 | 46 | You may define an `initialize` function to run setup code on construction. In `initialize`, 47 | you may **perform server actions**, *but **action creators** are a simpler entity for executing server actions.* 48 | 49 | ```javascript 50 | var TodoStore = Flux.createStore({ 51 | 52 | todos: [ 53 | {text: 'hello'}, 54 | {text: 'world'} 55 | ], 56 | 57 | initialize: function (url) { 58 | var self = this; 59 | 60 | $.getJSON(url, {}, function (data) { 61 | self.todos = data.todos; 62 | self.emit('change'); 63 | }); 64 | } 65 | }); 66 | 67 | var myTodos = new TodoStore('/todos'); 68 | ``` 69 | 70 | ### `getState()` 71 | 72 | You must define a `getState` method on your store. This method should return an object 73 | containing the state data needed by your view(s). It is called by the React mixin on 74 | `componentDidMount`, and the returned data is placed into `this.state.stores[storeName]` on 75 | React components with the Delorean `storeListener` mixin applied (see the [View (or React Component)](./views.md) 76 | documentation for more). 77 | 78 | ### Advanced Data Structure with `scheme` 79 | 80 | Schemes are simply structure definitions of the DeLorean. 81 | 82 | ```js 83 | var Artist = Flux.createStore({ 84 | scheme: { 85 | firstName: { 86 | default: 'Unknown' 87 | }, 88 | lastName: { 89 | default: 'Artist' 90 | }, 91 | fullName: { 92 | deps: ['firstName', 'lastName'], 93 | calculate: function () { 94 | return this.firstName + ' ' + this.lastName; 95 | } 96 | } 97 | } 98 | }); 99 | ``` 100 | 101 | Supported Scheme Options 102 | 103 | **`default`**: Value of the specified property will be set to this value, and applied as `state`, even when never manually set. 104 | 105 | **`calculate`**: A function that returns a computed value. This feature can be used to generate a value off of two other store 106 | properties, or used to parse data into a more desirable format for your components or views. It is passed the value 107 | passed to `set`, and is called in the context of your store. It is also re-called whenever any of it's dependencies 108 | (`deps`) changes. 109 | 110 | **`deps`**: An array of other property names from `scheme`, whose changes should cause the property to recalculate.. `deps` 111 | allows delorean to efficiently recalculate properties only when necessary. The `calculate` function will only be 112 | called when the store is created, and when one of the property in it's `deps` is `set` or calculated. Note: a calculated 113 | property can be dependent on another calculatd property, but circular dependencies are not supported, so be careful! 114 | 115 | 116 | For basic schemes, there is a shorthand syntax where you can set the `default` value or `calculate` method 117 | directly on the property name. Note that when this syntax is used, `calculate` will only be called when the 118 | property is `set` directly. It is best used for parsing (data transforms) on properties or for calculated 119 | properties where dependencies will not change. 120 | 121 | ```js 122 | var Artist = Flux.createStore({ 123 | scheme: { 124 | firstName: 'Unknown', 125 | lastName: 'Artist', 126 | fullName: function (value) { 127 | return this.firstName + ' ' + this.lastName; 128 | } 129 | } 130 | }); 131 | 132 | var UserSearch = Flux.createStore({ 133 | scheme: { 134 | // Defining a scheme property as a function provides a standard way to do data transforms (when required) 135 | results: function (serverResponse) { 136 | var user; 137 | for (var i = 0; i < serverResponse.length; i++) { 138 | user = serverResponse[i]; 139 | user.fullName = user.firstName + ' ' + user.lastName; 140 | } 141 | return serverResposne; 142 | } 143 | } 144 | }); 145 | ``` 146 | 147 | #### `set` method to change data defined on `scheme` 148 | 149 | You must use the `set` method to mutate the data of the scheme. It'll change the 150 | data, recalculate the appropriate properties and calls `emit('change')` so your 151 | views or component update. `set` accepts a key and value, or an object with all the 152 | key/value pairs being set. 153 | 154 | ```js 155 | var artist = new Artist(); 156 | artist.set('firstName', 'Michael'); 157 | artist.set('lastName', 'Jackson'); 158 | 159 | artist.set({ 160 | firstName: 'Salvador', 161 | lastName: 'Dali' 162 | }); 163 | ``` 164 | 165 | *Note: If you try to set a value doesn't exist in `scheme` it'll throw an error.* 166 | 167 | In Todo example it may be better: 168 | 169 | ```js 170 | var TodoStore = Flux.createStore({ 171 | scheme: { 172 | todos: { 173 | default: [], 174 | calculate: function () { 175 | return this.todos.concat().sort(function (a, b) { return a > b }); 176 | } 177 | } 178 | } 179 | }); 180 | ``` 181 | 182 | ### `emitChange()` or `emit('change')` 183 | 184 | When your data changes, you need to call `emitChange()` or `emit('change')` to publish 185 | a change signal for views. 186 | 187 | ### `emitRollback()` or `emit('rollback')` 188 | 189 | When something goes wrong with your store, you may want to emit a `rollback` event. When 190 | emitted, it informs other related stores to roll back as well. 191 | 192 | #### Using `Array.observe` and `Object.observe`, or `listenChanges` 193 | 194 | You don't have to call `emitChange()` or `emit('change')` everytime. You may use the **`observe`** feature 195 | of **ES.next**. 196 | 197 | ```javascript 198 | var TodoStore = Flux.createStore({ 199 | 200 | todos: [ 201 | {text: 'hello'}, 202 | {text: 'world'} 203 | ], 204 | 205 | initialize: function (url) { 206 | var self = this; 207 | 208 | // It will update store and Views everytime 209 | // you changed the data. 210 | Array.observe(this.todos, function () { 211 | self.emit('change'); 212 | }); 213 | 214 | $.getJSON(url, {}, function (data) { 215 | self.todos = data.todos; 216 | // You don't have to emit 'change' event. 217 | }); 218 | } 219 | }); 220 | 221 | var myTodos = new TodoStore('/todos'); 222 | ``` 223 | 224 | Also you may use the **`listenChanges`** method, which intelligently uses `Array.observe` 225 | or `Object.observe` for you. 226 | 227 | ```javascript 228 | ... 229 | initialize: function (url) { 230 | var self = this; 231 | 232 | // It will basically runs `Array.observe` or `Object.observe` 233 | this.listenChanges(this.todos); 234 | 235 | $.getJSON(url, {}, function (data) { 236 | self.todos = data.todos; 237 | }); 238 | } 239 | ... 240 | ``` 241 | 242 | ### Protect your state from failures using `rollback` 243 | 244 | Sometimes stores may fail, and you will want to revert your data. In these cases, you'll need 245 | a rollback mechanism. When a store says it needs to be rolled back, **every sibling 246 | store on the same dispatcher will be informed**. 247 | 248 | ```javascript 249 | ... 250 | todos: [], 251 | 252 | initialize: function (url) { 253 | var self = this; 254 | 255 | this.rollback(function () { 256 | // bring old todos back, also it will tell another stores 257 | // to be rolled back. 258 | self.todos = self.oldTodos.slice(0); 259 | self.emit('change'); 260 | }); 261 | }, 262 | 263 | addTodo: function (data) { 264 | var self = this; 265 | 266 | // Let's backup the data 267 | self.oldTodos = self.todos.slice(0); 268 | 269 | // Now apply the view 270 | self.todos.push({text: data}); 271 | self.emitChange(); 272 | 273 | // Now try to react to the server 274 | $.post('/todos', {text: data}, function (response) { 275 | if (response.status === false) { 276 | // if something goes wrong with the server, emit rollback. 277 | self.emitRollback(); 278 | } 279 | }, 'json'); 280 | } 281 | ... 282 | ``` 283 | -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | You can easily start using **DeLorean.js**. 4 | 5 | First of all, let's create the DOM (view) we'll need. 6 | 7 | ```html 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | Now, let's create an `app.js` file. 24 | 25 | ### Step 1: Action Creation 26 | 27 | And then let's handle the events: 28 | 29 | ```javascript 30 | document.getElementById('addItem').onclick = function () { 31 | ActionCreator.addItem(); 32 | }; 33 | ``` 34 | 35 | Sure, it will create an error when you click the button: 36 | 37 | ``` 38 | ReferenceError: ActionCreator is not defined 39 | ``` 40 | 41 | Then let's create an **Action Creator** called `ActionCreator`. Action Creators 42 | are simple JavaScript objects. You can use them as **controllers**. 43 | 44 | ```javascript 45 | var ActionCreator = { 46 | addItem: function () { 47 | // We'll going to call dispatcher methods. 48 | MyAppDispatcher.addItem({random: Math.random()}); 49 | } 50 | }; 51 | ``` 52 | 53 | ### Step 2: Dispatching Actions to the Stores 54 | 55 | But now, we'll need a dispatcher. Now we'll use DeLorean's dispatcher. 56 | 57 | ```javascript 58 | var MyAppDispatcher = DeLorean.Flux.createDispatcher({ 59 | addItem: function (data) { 60 | this.dispatch('addItem', data); 61 | } 62 | }); 63 | ``` 64 | 65 | It's now looking like actually another action creator but dispatchers are 66 | units that informs stores one by one. So we need to define the special function 67 | called `getStores`. 68 | 69 | ```javascript 70 | var MyAppDispatcher = DeLorean.Flux.createDispatcher({ 71 | addItem: function (data) { 72 | this.dispatch('addItem', data); 73 | }, 74 | 75 | getStores: function () { 76 | return { 77 | myStore: myStore 78 | } 79 | } 80 | }); 81 | ``` 82 | 83 | ### Step 3: Stores to Manage Data 84 | 85 | Now let's create the store. You need to define an object called `actions`. It's 86 | simply forwarders for dispatcher. When a dispatcher connected to the store 87 | dispatches an event, the `actions` object will forward it to the related method 88 | with arguments. 89 | 90 | Also you have to call `this.emit('change')` when you update your data. 91 | 92 | ```javascript 93 | var myStore = DeLorean.Flux.createStore({ 94 | list: [], 95 | actions: { 96 | // Remember the `dispatch('addItem')` 97 | 'addItem': 'addItemMethod' 98 | }, 99 | addItemMethod: function (data) { 100 | this.list.push('ITEM: ' + data.random); 101 | 102 | // You need to say your store is changed. 103 | this.emit('change'); 104 | } 105 | }); 106 | ``` 107 | 108 | Make sure you put this code before the one from step 2 though, because we were using `myStore` in creating the `MyAppDispatcher`, and that will keep a reference to the store internally. 109 | 110 | ### Step 4: Completing the Cycle: Views 111 | 112 | Now everything seems okay, but **we didn't completed the cycle yet**. 113 | 114 | ```javascript 115 | var list = document.getElementById('list'); 116 | 117 | // Store emits the `change` event when changed. 118 | myStore.onChange(function () { 119 | list.innerHTML = ''; // Best thing for this example. 120 | 121 | myStore.list.forEach(function (item) { 122 | var listItem = document.createElement('li'); 123 | listItem.innerHTML = item; 124 | list.appendChild(listItem); 125 | }); 126 | }); 127 | ``` 128 | 129 | ### Step 5: Using `scheme`s 130 | 131 | You already know stores, now let's learn how to manage easier. 132 | 133 | ```javascript 134 | var MyAppStore = DeLorean.Flux.createStore({ 135 | scheme: { 136 | list: { 137 | default: [], 138 | calculate: function (value) { 139 | return value.concat().map(function (item) { 140 | return 'ITEM: ' + item; 141 | }); 142 | } 143 | } 144 | }, 145 | actions: { 146 | // Remember the `dispatch('addItem')` 147 | 'addItem': 'addItemMethod' 148 | }, 149 | addItemMethod: function (data) { 150 | // It automatically emits change event. 151 | this.set('list', this.list.concat(data.random)); 152 | } 153 | }); 154 | var myStore = new MyAppStore(); 155 | ``` 156 | -------------------------------------------------------------------------------- /docs/views.md: -------------------------------------------------------------------------------- 1 | # Views (or Components) 2 | 3 | ## Combining to React 4 | 5 | You bring all the flow together with the Views (or components), actually *the Action generators*. 6 | Use the **`Flux.mixins.storeListener`** mixin to get a component into the Flux system. 7 | All components to which you have applied the `storeListener` mixin will be able to access stores. 8 | 9 | ```js 10 | // Child components don't have to have the storeListener. 11 | 12 | var TodoItemView = React.createClass({ 13 | 14 | render: function (todo) { 15 | return
  • {this.props.todo.text}
  • 16 | }, 17 | 18 | handleClick: function () { 19 | TodoActionCreator.removeTodo(this.props.todo); 20 | // or, this.props.dispatcher.removeTodo(this.props.todo); 21 | } 22 | 23 | }); 24 | 25 | var TodoListView = React.createClass({ 26 | 27 | mixins: [Flux.mixins.storeListener], 28 | 29 | render: function () { 30 | var self = this; 31 | return
      32 | {this.getStore('todoStore').todos.map(function (todo) { 33 | return 34 | })} 35 |
    36 | } 37 | 38 | }); 39 | ``` 40 | 41 | ### `storeDidChange` and `storesDidChange` 42 | 43 | These methods are triggered when a store is changed, and all stores are changed. You can use 44 | these methods in your components to perform specific actions after a store changes. Please note 45 | that the **`Flux.mixins.storeListener`** will automatically update your component's `state` and force 46 | a render, so it is not required that you define these methods to perform routine re-renders. 47 | 48 | ```js 49 | var TodoListView = React.createClass({ 50 | 51 | mixins: [Flux.mixins.storeListener], 52 | 53 | // when all stores are updated 54 | storesDidChange: function () { 55 | console.log("All stores are now updated."); 56 | }, 57 | 58 | // when a store updates 59 | storeDidChange: function (storeName) { 60 | console.log(storeName + " store is now updated."); 61 | }, 62 | 63 | render: function () { 64 | // ... 65 | } 66 | 67 | }); 68 | ``` 69 | 70 | ### `watchStores` 71 | 72 | In larger applications, it may become inefficient to watch all stores for changes. The `watchStores` property 73 | is an array, defined on your component, containing the names of stores you want the component to watch for changes. 74 | This property is optional, and when omitted, all stores associated with the `dispatcher` will be watched. 75 | Store name strings should match the keys of the stores returned in the `dispatchers`'s `getStores` method. 76 | 77 | 78 | ```js 79 | var TodoListView = React.createClass({ 80 | 81 | mixins: [Flux.mixins.storeListener], 82 | 83 | // Only watch the todoStore, omitting this property will watch all stores on the dispatcher 84 | watchStores: ['todoStore'], 85 | 86 | ... 87 | ``` 88 | 89 | ### `trigger` 90 | 91 | `trigger` is a method exposed by the **`Flux.mixins.storeListener`**. It is a simple way to trigger action from a component. 92 | See the [Dispatcher docs](./dispatchers.md) for more. 93 | 94 | 95 | ### `getStore(storeName)` 96 | 97 | It returns the related store to the component. 98 | 99 | ```js 100 | ... 101 | return
      102 | {this.getStore('todoStore').todos.map(function (todo) { 103 | return 104 | })} 105 |
    106 | ... 107 | ``` 108 | 109 | ## Combining to Flight.js 110 | 111 | Since DeLorean.Flux doesn't require React, you can use it everywhere, including **Flight.js** 112 | 113 | ```javascript 114 | var TodoCountUI = flight.component(function () { 115 | 116 | this.render = function () { 117 | this.$node.html(TodoListDispatcher.getStore('todoStore').todos.length); 118 | }; 119 | 120 | this.after('initialize', function() { 121 | // You should listen changes 122 | TodoListDispatcher.on('change:all', this.render.bind(this)); 123 | this.render(); 124 | }); 125 | }); 126 | ``` 127 | -------------------------------------------------------------------------------- /examples/firebase-chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FireBase + DeLorean Chat 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 |
    23 |
    24 |
    25 |
    26 |
    27 |

    My Chat App

    28 |
    29 |
    30 |
    31 |
    32 | 38 |
    39 |
    40 |
    41 |
    42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/firebase-chat/js/app.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var firebase = new Firebase("https://deloreanjs.firebaseio.com/"); 4 | 5 | var Flux = DeLorean.Flux; 6 | 7 | var Messages = Flux.createStore({ 8 | actions: { 9 | 'NEW_MESSAGE': 'newMessage' 10 | }, 11 | 12 | messages: [], 13 | newMessage: function (message) { 14 | this.messages.push(message); 15 | this.emit('change'); 16 | }, 17 | 18 | getState: function () { 19 | return { 20 | messages: this.messages 21 | }; 22 | } 23 | }); 24 | 25 | var messages = new Messages(); 26 | 27 | var MessagesDispatcher = Flux.createDispatcher({ 28 | newMessage: function (message) { 29 | this.dispatch('NEW_MESSAGE', message); 30 | }, 31 | getStores: function () { 32 | return { 33 | messageStore: messages 34 | }; 35 | } 36 | }); 37 | 38 | var MessageActions = { 39 | newMessage: function (sender, text) { 40 | MessagesDispatcher.newMessage({sender: sender, text: text}); 41 | }, 42 | sendMessage: function (sender, text) { 43 | firebase.push({sender: sender, text: text}); 44 | } 45 | } 46 | 47 | firebase.on('child_added', function (snapshot) { 48 | var message = snapshot.val(); 49 | MessageActions.newMessage(message.sender, message.text); 50 | }); 51 | 52 | var MessagesView = React.createClass({displayName: 'MessagesView', 53 | mixins: [Flux.mixins.storeListener], 54 | 55 | render: function () { 56 | var messages = this.stores.messageStore.store.messages.map(function (message) { 57 | return React.DOM.div(null, React.DOM.strong(null, message.sender, ": "), React.DOM.span(null, message.text)); 58 | }); 59 | return React.DOM.div(null, messages); 60 | } 61 | }); 62 | 63 | var MessagesSender = React.createClass({displayName: 'MessagesSender', 64 | getInitialState: function () { 65 | return {message: ''}; 66 | }, 67 | handleChange: function () { 68 | var value = this.refs.message.getDOMNode().value; 69 | this.setState({message: value}); 70 | }, 71 | handleKeyUp: function (e) { 72 | if (e.keyCode == 13) { 73 | var message = this.state.message; 74 | MessageActions.sendMessage('someone', message); 75 | this.setState({message: ''}); 76 | } 77 | }, 78 | render: function () { 79 | return React.DOM.input({type: "text", 80 | ref: "message", 81 | onChange: this.handleChange, 82 | onKeyUp: this.handleKeyUp, 83 | value: this.state.message, 84 | className: "form-control", 85 | id: "message", placeholder: "write your message here"}); 86 | } 87 | }); 88 | 89 | React.renderComponent(MessagesView({dispatcher: MessagesDispatcher}), document.getElementById('messages')); 90 | React.renderComponent(MessagesSender({dispatcher: MessagesDispatcher}), document.getElementById('sender')); 91 | -------------------------------------------------------------------------------- /examples/firebase-chat/js/app.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var firebase = new Firebase("https://deloreanjs.firebaseio.com/"); 4 | 5 | var Flux = DeLorean.Flux; 6 | 7 | var Messages = Flux.createStore({ 8 | actions: { 9 | 'NEW_MESSAGE': 'newMessage' 10 | }, 11 | 12 | messages: [], 13 | newMessage: function (message) { 14 | this.messages.push(message); 15 | this.emit('change'); 16 | }, 17 | 18 | getState: function () { 19 | return { 20 | messages: this.messages 21 | }; 22 | } 23 | }); 24 | 25 | var messages = new Messages(); 26 | 27 | var MessagesDispatcher = Flux.createDispatcher({ 28 | newMessage: function (message) { 29 | this.dispatch('NEW_MESSAGE', message); 30 | }, 31 | getStores: function () { 32 | return { 33 | messageStore: messages 34 | }; 35 | } 36 | }); 37 | 38 | var MessageActions = { 39 | newMessage: function (sender, text) { 40 | MessagesDispatcher.newMessage({sender: sender, text: text}); 41 | }, 42 | sendMessage: function (sender, text) { 43 | firebase.push({sender: sender, text: text}); 44 | } 45 | } 46 | 47 | firebase.on('child_added', function (snapshot) { 48 | var message = snapshot.val(); 49 | MessageActions.newMessage(message.sender, message.text); 50 | }); 51 | 52 | var MessagesView = React.createClass({ 53 | mixins: [Flux.mixins.storeListener], 54 | 55 | render: function () { 56 | var messages = this.stores.messageStore.store.messages.map(function (message) { 57 | return
    {message.sender}: {message.text}
    ; 58 | }); 59 | return
    {messages}
    ; 60 | } 61 | }); 62 | 63 | var MessagesSender = React.createClass({ 64 | getInitialState: function () { 65 | return {message: ''}; 66 | }, 67 | handleChange: function () { 68 | var value = this.refs.message.getDOMNode().value; 69 | this.setState({message: value}); 70 | }, 71 | handleKeyUp: function (e) { 72 | if (e.keyCode == 13) { 73 | var message = this.state.message; 74 | MessageActions.sendMessage('someone', message); 75 | this.setState({message: ''}); 76 | } 77 | }, 78 | render: function () { 79 | return ; 86 | } 87 | }); 88 | 89 | React.renderComponent(, document.getElementById('messages')); 90 | React.renderComponent(, document.getElementById('sender')); 91 | -------------------------------------------------------------------------------- /examples/todomvc-flightjs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Flux + Flight.js 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
      12 |
      13 | 14 |
      15 |
      There are 0 todos.
      16 |
      17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/todomvc-flightjs/js/main.js: -------------------------------------------------------------------------------- 1 | var Flux = DeLorean.Flux; 2 | 3 | var TodoStore = Flux.createStore({ 4 | 5 | todos: [ 6 | {text: 'hello'}, 7 | {text: 'world'} 8 | ], 9 | 10 | initialize: function (todos) { 11 | var self = this; 12 | if (todos) { 13 | this.todos = this.todos.concat(todos); 14 | this.emit('change'); 15 | } 16 | 17 | // Auto change 18 | // Array.observe(this.todos, function () { 19 | // self.emit('change'); 20 | // }); 21 | // this.listenChanges(this.todos); 22 | }, 23 | 24 | actions: { 25 | 'todo:add': 'addTodo', 26 | 'todo:remove': 'removeTodo', 27 | 'todo:reset': 'resetTodos' 28 | }, 29 | 30 | addTodo: function (todo) { 31 | this.todos.push({text: todo.text}); 32 | this.emit('change'); 33 | }, 34 | 35 | removeTodo: function (todoToComplete) { 36 | var filteredData = this.todos.filter(function (todo) { 37 | return todoToComplete.text !== todo.text 38 | }); 39 | // this.listenChanges(filteredData); 40 | 41 | this.todos = filteredData; 42 | this.emit('change'); 43 | }, 44 | 45 | resetTodos: function (todos) { 46 | this.todos = todos; 47 | // this.listenChanges(this.todos); 48 | this.emit('change'); 49 | } 50 | 51 | }); 52 | 53 | /* Create a Todo Store with a data */ 54 | 55 | var myTodos = new TodoStore([ 56 | {text: 'foo'}, 57 | {text: 'bar'} 58 | ]); 59 | window.myTodos = myTodos; 60 | 61 | /* Generate List dispatcher with TodoStore. */ 62 | 63 | var TodoListDispatcher = Flux.createDispatcher({ 64 | 65 | addTodo: function (todo) { 66 | this.dispatch('todo:add', todo); 67 | }, 68 | 69 | removeTodo: function (todo) { 70 | if (confirm('Do you really want to delete this todo?')) { 71 | this.dispatch('todo:remove', todo) 72 | .then(function () { 73 | alert('Item is deleted successfully.'); 74 | }); 75 | } 76 | }, 77 | 78 | reset: function (todos) { 79 | this.dispatch('todo:reset', todos); 80 | }, 81 | 82 | getStores: function () { 83 | return { 84 | todoStore: myTodos 85 | }; 86 | } 87 | 88 | }); 89 | 90 | /* Action generators are simple functions */ 91 | 92 | var TodoActionCreator = { 93 | 94 | getAllMessages: function () { 95 | // It's an example for async requests. 96 | setTimeout(function () { 97 | TodoListDispatcher.reset([ 98 | {text: 1}, 99 | {text: 2}, 100 | {text: 3} 101 | ]); 102 | }, 1000); 103 | }, 104 | 105 | addTodo: function (todo) { 106 | TodoListDispatcher.addTodo(todo); 107 | }, 108 | 109 | removeTodo: function (todo) { 110 | TodoListDispatcher.removeTodo(todo); 111 | }, 112 | 113 | addAsyncTodo: function (todo) { 114 | setTimeout(function () { 115 | TodoListDispatcher.addTodo(todo); 116 | }, 1000); 117 | } 118 | 119 | }; 120 | 121 | //////////////////////////////////////////// 122 | 123 | // Flight Components 124 | var TodoListUI = flight.component(function () { 125 | 126 | this.render = function () { 127 | var self = this; 128 | this.$node.html(''); 129 | 130 | TodoListDispatcher.stores.todoStore.store.todos.forEach(function (todo) { 131 | var todoItem = $('
    • ').text(todo.text); 132 | todoItem.data('todo', todo); 133 | 134 | self.$node.append(todoItem); 135 | }); 136 | }; 137 | 138 | this.removeTodo = function (e) { 139 | TodoActionCreator.removeTodo($(e.target).data('todo')); 140 | }; 141 | 142 | this.addTodo = function (event, todo) { 143 | TodoActionCreator.addTodo(todo); 144 | }; 145 | 146 | this.after('initialize', function() { 147 | this.on(document, 'todo:add', this.addTodo); 148 | this.$node.on('dblclick', this.removeTodo); 149 | 150 | // You should listen changes of all stores 151 | TodoListDispatcher.on('change:all', this.render.bind(this)); 152 | this.render(); 153 | 154 | TodoActionCreator.addAsyncTodo({text: 'this is async'}); 155 | }); 156 | }); 157 | 158 | var TodoCountUI = flight.component(function () { 159 | 160 | this.render = function () { 161 | this.$node.html(TodoListDispatcher.stores.todoStore.store.todos.length); 162 | }; 163 | 164 | this.after('initialize', function() { 165 | // You should listen changes 166 | TodoListDispatcher.on('change:all', this.render.bind(this)); 167 | this.render(); 168 | }); 169 | }); 170 | 171 | var TodoAddUI = flight.component(function () { 172 | 173 | this.addTodo = function(e) { 174 | var input = this.$node.find('input'); 175 | this.trigger('todo:add', {text: input.val().trim()}); 176 | 177 | input.val(''); 178 | e.preventDefault(); 179 | } 180 | 181 | this.after('initialize', function() { 182 | this.on('submit', this.addTodo); 183 | }); 184 | }); 185 | 186 | TodoListUI.attachTo('#todos'); 187 | TodoAddUI.attachTo('#new-item'); 188 | TodoCountUI.attachTo('#count span'); 189 | -------------------------------------------------------------------------------- /examples/todomvc/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt)-> 2 | 3 | grunt.loadNpmTasks 'grunt-react' 4 | grunt.loadNpmTasks 'grunt-browserify' 5 | 6 | grunt.initConfig 7 | 8 | react: 9 | example: 10 | files: 11 | 'asset/js/index.js': 'asset/jsx/index.jsx' 12 | 13 | browserify: 14 | example: 15 | files: 16 | 'asset/js/app.js': ['asset/js/index.js'] 17 | 18 | grunt.registerTask 'default', ['react:example', 'browserify:example'] 19 | -------------------------------------------------------------------------------- /examples/todomvc/README.md: -------------------------------------------------------------------------------- 1 | # DeLorean.js TodoMVC Example 2 | 3 | ```bash 4 | grunt 5 | open index.html 6 | ``` 7 | -------------------------------------------------------------------------------- /examples/todomvc/asset/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/delorean/53fba8ce95f08221a8a076a04e2968c97ebb88b6/examples/todomvc/asset/css/app.css -------------------------------------------------------------------------------- /examples/todomvc/asset/js/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Flux = require('../../../../').Flux; 5 | 6 | var Router = require('director').Router; 7 | 8 | /* Generate Generic Store */ 9 | 10 | var TodoStore = Flux.createStore({ 11 | 12 | scheme: { 13 | firstname: 'John', 14 | surname: 'Doe', 15 | fullname: { 16 | default: 'woot', 17 | deps: ['firstname', 'surname'], 18 | calculate: function (value) { 19 | return value.toUpperCase() + ' ' + this.firstname; 20 | } 21 | }, 22 | todos: { 23 | default: [ 24 | {text: 'b'}, 25 | {text: 'c'}, 26 | {text: 'a'} 27 | ], 28 | calculate: function () { 29 | var self = this; 30 | return this.todos.map(function (todo) { 31 | return {text: todo.text.toString().toUpperCase()}; 32 | }); 33 | } 34 | } 35 | }, 36 | 37 | initialize: function (todos) { 38 | var self = this; 39 | if (todos) { 40 | this.todos = this.set('todos', this.todos.concat(todos)); 41 | } 42 | 43 | // Auto change 44 | // Array.observe(this.todos, function () { 45 | // self.emit('change'); 46 | // }); 47 | this.listenChanges(this.todos); 48 | }, 49 | 50 | actions: { 51 | 'todo:add': 'addTodo', 52 | 'todo:remove': 'removeTodo', 53 | 'todo:reset': 'resetTodos' 54 | }, 55 | 56 | addTodo: function (todo) { 57 | this.set('todos', this.todos.concat({text: todo.text})); 58 | }, 59 | 60 | removeTodo: function (todoToComplete) { 61 | var filteredData = this.todos.filter(function (todo) { 62 | return todoToComplete.text !== todo.text 63 | }); 64 | this.listenChanges(filteredData); 65 | 66 | this.set('todos', filteredData); 67 | this.emit('change'); 68 | }, 69 | 70 | resetTodos: function (todos) { 71 | this.set('todos', todos); 72 | this.listenChanges(this.todos); 73 | this.emit('change'); 74 | } 75 | 76 | }); 77 | 78 | /* Create a Todo Store with a data */ 79 | 80 | var myTodos = new TodoStore([ 81 | {text: 'foo'}, 82 | {text: 'bar'} 83 | ]); 84 | window.myTodos = myTodos; 85 | 86 | /* Generate List dispatcher with TodoStore. */ 87 | 88 | var TodoListDispatcher = Flux.createDispatcher({ 89 | 90 | addTodo: function (todo) { 91 | this.dispatch('todo:add', todo); 92 | }, 93 | 94 | removeTodo: function (todo) { 95 | if (confirm('Do you really want to delete this todo?')) { 96 | this.dispatch('todo:remove', todo) 97 | .then(function () { 98 | alert('Item is deleted successfully.'); 99 | }); 100 | } 101 | }, 102 | 103 | reset: function (todos) { 104 | this.dispatch('todo:reset', todos); 105 | }, 106 | 107 | getStores: function () { 108 | return { 109 | todoStore: myTodos 110 | } 111 | } 112 | 113 | }); 114 | 115 | /* Static Dispatcher */ 116 | 117 | var TodoDispatcher = Flux.createDispatcher({ 118 | 119 | getStores: function () { 120 | return { 121 | todoStore: myTodos 122 | } 123 | } 124 | 125 | }); 126 | 127 | /* Action generators are simple functions */ 128 | 129 | var TodoActionCreator = { 130 | 131 | getAllMessages: function () { 132 | // It's an example for async requests. 133 | setTimeout(function () { 134 | TodoListDispatcher.reset([ 135 | {text: 1}, 136 | {text: 2}, 137 | {text: 3} 138 | ]); 139 | }, 1000); 140 | }, 141 | 142 | addTodo: function (todo) { 143 | TodoListDispatcher.addTodo(todo); 144 | }, 145 | 146 | removeTodo: function (todo) { 147 | TodoListDispatcher.removeTodo(todo); 148 | }, 149 | 150 | addAsyncTodo: function (todo) { 151 | setTimeout(function () { 152 | TodoListDispatcher.addTodo(todo); 153 | }, 1000); 154 | } 155 | 156 | }; 157 | 158 | /* React Components */ 159 | 160 | var TodoItemView = React.createClass({displayName: 'TodoItemView', 161 | 162 | render: function (todo) { 163 | return React.DOM.li({onClick: this.handleClick}, this.props.todo.text) 164 | }, 165 | 166 | handleClick: function () { 167 | TodoActionCreator.removeTodo(this.props.todo); 168 | } 169 | 170 | }); 171 | 172 | var TodoListView = React.createClass({displayName: 'TodoListView', 173 | 174 | mixins: [Flux.mixins.storeListener], 175 | 176 | storeDidChange: function () { 177 | console.log(arguments); 178 | }, 179 | 180 | render: function () { 181 | var self = this; 182 | 183 | return React.DOM.ul(null, 184 | this.stores.todoStore.store.todos.map(function (todo) { 185 | return TodoItemView({todo: todo}) 186 | }) 187 | ) 188 | } 189 | 190 | }); 191 | 192 | var TodoFormView = React.createClass({displayName: 'TodoFormView', 193 | 194 | mixins: [Flux.mixins.storeListener], 195 | 196 | render: function () { 197 | var self = this; 198 | return React.DOM.form({onSubmit: this.handleSubmit}, 199 | React.DOM.input({value: this.state.todo, onChange: this.handleChange}) 200 | ) 201 | }, 202 | 203 | handleChange: function (e) { 204 | this.setState({todo: e.target.value}); 205 | }, 206 | 207 | handleSubmit: function (e) { 208 | e.preventDefault(); 209 | TodoActionCreator.addTodo({text: this.state.todo}); 210 | this.setState({todo: ''}); 211 | } 212 | 213 | }); 214 | 215 | var ApplicationView = React.createClass({displayName: 'ApplicationView', 216 | 217 | mixins: [Flux.mixins.storeListener], 218 | 219 | render: function () { 220 | var self = this; 221 | return React.DOM.div(null, 222 | React.DOM.span(null, this.getStore('todoStore').fullname), 223 | TodoListView(null), 224 | TodoFormView(null), 225 | React.DOM.span(null, "There are ", this.getStore('todoStore').todos.length, " todos.") 226 | ) 227 | } 228 | 229 | }); 230 | 231 | window.mainView = React.renderComponent(ApplicationView({dispatcher: TodoListDispatcher}), 232 | document.getElementById('main')) 233 | 234 | var appRouter = new Router({ 235 | '/': function () { 236 | TodoActionCreator.addAsyncTodo({text: 'this is async'}); 237 | }, 238 | '/random': function () { 239 | TodoActionCreator.addTodo({text: Math.random()}); 240 | location.hash = '/'; 241 | } 242 | }); 243 | 244 | appRouter.init('/'); 245 | -------------------------------------------------------------------------------- /examples/todomvc/asset/jsx/index.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Flux = require('../../../../').Flux; 5 | 6 | var Router = require('director').Router; 7 | 8 | /* Generate Generic Store */ 9 | 10 | var TodoStore = Flux.createStore({ 11 | 12 | scheme: { 13 | firstname: 'John', 14 | surname: 'Doe', 15 | fullname: { 16 | default: 'woot', 17 | deps: ['firstname', 'surname'], 18 | calculate: function (value) { 19 | return value.toUpperCase() + ' ' + this.firstname; 20 | } 21 | }, 22 | todos: { 23 | default: [ 24 | {text: 'b'}, 25 | {text: 'c'}, 26 | {text: 'a'} 27 | ], 28 | calculate: function () { 29 | var self = this; 30 | return this.todos.map(function (todo) { 31 | return {text: todo.text.toString().toUpperCase()}; 32 | }); 33 | } 34 | } 35 | }, 36 | 37 | initialize: function (todos) { 38 | var self = this; 39 | if (todos) { 40 | this.todos = this.set('todos', this.todos.concat(todos)); 41 | } 42 | 43 | // Auto change 44 | // Array.observe(this.todos, function () { 45 | // self.emit('change'); 46 | // }); 47 | this.listenChanges(this.todos); 48 | }, 49 | 50 | actions: { 51 | 'todo:add': 'addTodo', 52 | 'todo:remove': 'removeTodo', 53 | 'todo:reset': 'resetTodos' 54 | }, 55 | 56 | addTodo: function (todo) { 57 | this.set('todos', this.todos.concat({text: todo.text})); 58 | }, 59 | 60 | removeTodo: function (todoToComplete) { 61 | var filteredData = this.todos.filter(function (todo) { 62 | return todoToComplete.text !== todo.text 63 | }); 64 | this.listenChanges(filteredData); 65 | 66 | this.set('todos', filteredData); 67 | this.emit('change'); 68 | }, 69 | 70 | resetTodos: function (todos) { 71 | this.set('todos', todos); 72 | this.listenChanges(this.todos); 73 | this.emit('change'); 74 | } 75 | 76 | }); 77 | 78 | /* Create a Todo Store with a data */ 79 | 80 | var myTodos = new TodoStore([ 81 | {text: 'foo'}, 82 | {text: 'bar'} 83 | ]); 84 | window.myTodos = myTodos; 85 | 86 | /* Generate List dispatcher with TodoStore. */ 87 | 88 | var TodoListDispatcher = Flux.createDispatcher({ 89 | 90 | addTodo: function (todo) { 91 | this.dispatch('todo:add', todo); 92 | }, 93 | 94 | removeTodo: function (todo) { 95 | if (confirm('Do you really want to delete this todo?')) { 96 | this.dispatch('todo:remove', todo) 97 | .then(function () { 98 | alert('Item is deleted successfully.'); 99 | }); 100 | } 101 | }, 102 | 103 | reset: function (todos) { 104 | this.dispatch('todo:reset', todos); 105 | }, 106 | 107 | getStores: function () { 108 | return { 109 | todoStore: myTodos 110 | } 111 | } 112 | 113 | }); 114 | 115 | /* Static Dispatcher */ 116 | 117 | var TodoDispatcher = Flux.createDispatcher({ 118 | 119 | getStores: function () { 120 | return { 121 | todoStore: myTodos 122 | } 123 | } 124 | 125 | }); 126 | 127 | /* Action generators are simple functions */ 128 | 129 | var TodoActionCreator = { 130 | 131 | getAllMessages: function () { 132 | // It's an example for async requests. 133 | setTimeout(function () { 134 | TodoListDispatcher.reset([ 135 | {text: 1}, 136 | {text: 2}, 137 | {text: 3} 138 | ]); 139 | }, 1000); 140 | }, 141 | 142 | addTodo: function (todo) { 143 | TodoListDispatcher.addTodo(todo); 144 | }, 145 | 146 | removeTodo: function (todo) { 147 | TodoListDispatcher.removeTodo(todo); 148 | }, 149 | 150 | addAsyncTodo: function (todo) { 151 | setTimeout(function () { 152 | TodoListDispatcher.addTodo(todo); 153 | }, 1000); 154 | } 155 | 156 | }; 157 | 158 | /* React Components */ 159 | 160 | var TodoItemView = React.createClass({ 161 | 162 | render: function (todo) { 163 | return
    • {this.props.todo.text}
    • 164 | }, 165 | 166 | handleClick: function () { 167 | TodoActionCreator.removeTodo(this.props.todo); 168 | } 169 | 170 | }); 171 | 172 | var TodoListView = React.createClass({ 173 | 174 | mixins: [Flux.mixins.storeListener], 175 | 176 | storeDidChange: function () { 177 | console.log(arguments); 178 | }, 179 | 180 | render: function () { 181 | var self = this; 182 | 183 | return
        184 | {this.stores.todoStore.store.todos.map(function (todo) { 185 | return 186 | })} 187 |
      188 | } 189 | 190 | }); 191 | 192 | var TodoFormView = React.createClass({ 193 | 194 | mixins: [Flux.mixins.storeListener], 195 | 196 | render: function () { 197 | var self = this; 198 | return
      199 | 200 |
      201 | }, 202 | 203 | handleChange: function (e) { 204 | this.setState({todo: e.target.value}); 205 | }, 206 | 207 | handleSubmit: function (e) { 208 | e.preventDefault(); 209 | TodoActionCreator.addTodo({text: this.state.todo}); 210 | this.setState({todo: ''}); 211 | } 212 | 213 | }); 214 | 215 | var ApplicationView = React.createClass({ 216 | 217 | mixins: [Flux.mixins.storeListener], 218 | 219 | render: function () { 220 | var self = this; 221 | return
      222 | {this.getStore('todoStore').fullname} 223 | 224 | 225 | There are {this.getStore('todoStore').todos.length} todos. 226 |
      227 | } 228 | 229 | }); 230 | 231 | window.mainView = React.renderComponent(, 232 | document.getElementById('main')) 233 | 234 | var appRouter = new Router({ 235 | '/': function () { 236 | TodoActionCreator.addAsyncTodo({text: 'this is async'}); 237 | }, 238 | '/random': function () { 239 | TodoActionCreator.addTodo({text: Math.random()}); 240 | location.hash = '/'; 241 | } 242 | }); 243 | 244 | appRouter.init('/'); 245 | -------------------------------------------------------------------------------- /examples/todomvc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React + Flux with DeLorean.js 6 | 7 | 8 | Add Random Todo 9 |
      10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delorean-todomvc", 3 | "version": "0.0.0", 4 | "description": "DeLorean.js TodoMVC Example", 5 | "author": "Fatih Kadir Akin", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "grunt": "^0.4.5", 9 | "grunt-browserify": "^2.1.4", 10 | "grunt-react": "^0.9.0" 11 | }, 12 | "dependencies": { 13 | "director": "^1.2.3", 14 | "react": "^0.11.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
        9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/tutorial/js/app.js: -------------------------------------------------------------------------------- 1 | document.getElementById('addItem').onclick = function () { 2 | ActionCreator.addItem(); 3 | }; 4 | 5 | var ActionCreator = { 6 | addItem: function () { 7 | // We'll going to call dispatcher methods. 8 | MyAppDispatcher.addItem({random: Math.random()}); 9 | } 10 | }; 11 | 12 | var myStore = DeLorean.Flux.createStore({ 13 | list: [], 14 | actions: { 15 | // Remember the `dispatch('addItem')` 16 | 'addItem': 'addItemMethod' 17 | }, 18 | addItemMethod: function (data) { 19 | this.list.push('ITEM: ' + data.random); 20 | 21 | // You need to say your store is changed. 22 | this.emit('change'); 23 | } 24 | }); 25 | 26 | var MyAppDispatcher = DeLorean.Flux.createDispatcher({ 27 | addItem: function (data) { 28 | this.dispatch('addItem', data); 29 | }, 30 | 31 | getStores: function () { 32 | return { 33 | myStore: myStore 34 | } 35 | } 36 | }); 37 | 38 | var list = document.getElementById('list'); 39 | 40 | // Store emits the `change` event when changed. 41 | myStore.onChange(function () { 42 | list.innerHTML = ''; // Best thing for this example. 43 | 44 | myStore.list.forEach(function (item) { 45 | var listItem = document.createElement('li'); 46 | listItem.innerHTML = item; 47 | list.appendChild(listItem); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delorean", 3 | "version": "1.0.0", 4 | "description": "Flux Library", 5 | "main": "src/delorean.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/grunt karma" 8 | }, 9 | "author": "Fatih Kadir Akin", 10 | "homepage": "http://deloreanjs.com", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/deloreanjs/delorean.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "grunt": "^0.4.5", 18 | "grunt-browserify": "^2.1.4", 19 | "grunt-cli": "^0.1.13", 20 | "grunt-contrib-concat": "^0.5.0", 21 | "grunt-contrib-connect": "^0.8.0", 22 | "grunt-contrib-jshint": "^0.10.0", 23 | "grunt-contrib-uglify": "^0.5.1", 24 | "grunt-contrib-watch": "^0.6.1", 25 | "grunt-docco": "^0.3.3", 26 | "grunt-jscs-checker": "^0.6.2", 27 | "grunt-karma": "^0.8.3", 28 | "grunt-livereload": "^0.1.3", 29 | "grunt-release": "git+https://github.com/f/grunt-release.git#c17216608469c5ecbb43458289763e09839932ba", 30 | "grunt-requirejs": "^0.4.2", 31 | "jasmine": "^2.0.1", 32 | "karma": "^0.12.22", 33 | "karma-chrome-launcher": "^0.1.4", 34 | "karma-coverage": "^0.2.6", 35 | "karma-jasmine": "^0.1.5", 36 | "karma-phantomjs-launcher": "^0.1.4" 37 | }, 38 | "dependencies": { 39 | "es6-promise": "^1.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/delorean.js: -------------------------------------------------------------------------------- 1 | (function (DeLorean) { 2 | 'use strict'; 3 | 4 | // There are two main concepts in Flux structure: **Dispatchers** and **Stores**. 5 | // Action Creators are simply helpers but doesn't require any framework level 6 | // abstraction. 7 | 8 | var Dispatcher, Store; 9 | 10 | // ## Private Helper Functions 11 | 12 | // Helper functions are private functions to be used in codebase. 13 | // It's better using two underscore at the beginning of the function. 14 | 15 | /* `__hasOwn` function is a shortcut for `Object#hasOwnProperty` */ 16 | function __hasOwn(object, prop) { 17 | return Object.prototype.hasOwnProperty.call(object, prop); 18 | } 19 | 20 | // Use `__generateActionName` function to generate action names. 21 | // E.g. If you create an action with name `hello` it will be 22 | // `action:hello` for the Flux. 23 | function __generateActionName(name) { 24 | return 'action:' + name; 25 | } 26 | 27 | /* It's used by the schemes to save the original version (not calculated) 28 | of the data. */ 29 | function __generateOriginalName(name) { 30 | return 'original:' + name; 31 | } 32 | 33 | // `__findDispatcher` is a private function for **React components**. 34 | function __findDispatcher(view) { 35 | // Provide a useful error message if no dispatcher is found 36 | if (DeLorean.dispatcher == null) { 37 | throw 'No dispatcher found. The DeLoreanJS mixin requires a "dispatcher" has been created using Flux.createDispatcher.'; 38 | } 39 | return DeLorean.dispatcher; 40 | } 41 | 42 | // `__clone` creates a deep copy of an object. 43 | function __clone(obj) { 44 | if (obj === null || typeof obj !== 'object') { return obj; } 45 | var copy = obj.constructor(); 46 | for (var attr in obj) { 47 | if (__hasOwn(obj, attr)) { 48 | copy[attr] = __clone(obj[attr]); 49 | } 50 | } 51 | return copy; 52 | } 53 | 54 | // `__extend` adds props to obj 55 | function __extend(obj, props) { 56 | props = __clone(props); 57 | for (var prop in props) { 58 | if (props.hasOwnProperty(prop)) { 59 | obj[prop] = props[prop]; 60 | } 61 | } 62 | return obj; 63 | } 64 | 65 | // ## Dispatcher 66 | 67 | // The dispatcher is **the central hub** that **manages all data flow** in 68 | // a Flux application. It is essentially a _registry of callbacks into the 69 | // stores_. Each store registers itself and provides a callback. When the 70 | // dispatcher responds to an action, all stores in the application are sent 71 | // the data payload provided by the action via the callbacks in the registry. 72 | Dispatcher = (function () { 73 | 74 | // ### Dispatcher Helpers 75 | 76 | // Rollback listener adds a `rollback` event listener to the bunch of 77 | // stores. 78 | function __rollbackListener(stores) { 79 | 80 | function __listener() { 81 | for (var i in stores) { 82 | stores[i].listener.emit('__rollback'); 83 | } 84 | } 85 | 86 | /* If any of them fires `rollback` event, all of the stores 87 | will be emitted to be rolled back with `__rollback` event. */ 88 | for (var j in stores) { 89 | stores[j].listener.on('rollback', __listener); 90 | } 91 | } 92 | 93 | // ### Dispatcher Prototype 94 | function Dispatcher(stores) { 95 | var self = this; 96 | // `DeLorean.EventEmitter` is `require('events').EventEmitter` by default. 97 | // you can change it using `DeLorean.Flux.define('EventEmitter', AnotherEventEmitter)` 98 | DeLorean.EventEmitter.defaultMaxListeners = 50; 99 | this.listener = new DeLorean.EventEmitter(); 100 | this.stores = stores; 101 | /* Stores should be listened for rollback events. */ 102 | __rollbackListener(Object.keys(stores).map(function (key) { 103 | return stores[key]; 104 | })); 105 | } 106 | 107 | // `dispatch` method dispatch the event with `data` (or **payload**) 108 | Dispatcher.prototype.dispatch = function () { 109 | var self = this, stores, deferred, args; 110 | args = Array.prototype.slice.call(arguments); 111 | 112 | this.listener.emit.apply(this.listener, ['dispatch'].concat(args)); 113 | 114 | /* Stores are key-value pairs. Collect store instances into an array. */ 115 | stores = (function () { 116 | var stores = [], store; 117 | for (var storeName in self.stores) { 118 | store = self.stores[storeName]; 119 | /* Store value must be an _instance of Store_. */ 120 | if (!store instanceof Store) { 121 | throw 'Given store is not a store instance'; 122 | } 123 | stores.push(store); 124 | } 125 | return stores; 126 | }()); 127 | 128 | // Store instances should wait for finish. So you can know if all the 129 | // stores are dispatched properly. 130 | deferred = this.waitFor(stores, args[0]); 131 | 132 | /* Payload should send to all related stores. */ 133 | for (var storeName in self.stores) { 134 | self.stores[storeName].dispatchAction.apply(self.stores[storeName], args); 135 | } 136 | 137 | // `dispatch` returns deferred object you can just use **promise** 138 | // for dispatching: `dispatch(..).then(..)`. 139 | return deferred; 140 | }; 141 | 142 | // `waitFor` is actually a _semi-private_ method. Because it's kind of internal 143 | // and you don't need to call it from outside most of the times. It takes 144 | // array of store instances (`[Store, Store, Store, ...]`). It will create 145 | // a promise and return it. _Whenever store changes, it resolves the promise_. 146 | Dispatcher.prototype.waitFor = function (stores, actionName) { 147 | var self = this, promises; 148 | promises = (function () { 149 | var __promises = [], promise; 150 | 151 | /* `__promiseGenerator` generates a simple promise that resolves itself when 152 | related store is changed. */ 153 | function __promiseGenerator(store) { 154 | // resolve on change 155 | // reject on rollback 156 | // cleanup_{actionName} will be fired after the action handler (in store.dispatchAction) to cleanup unused events 157 | return new DeLorean.Promise(function (resolve, reject) { 158 | store.listener.once('change', resolve); 159 | store.listener.once('rollback', reject); 160 | store.listener.once('cleanup_' + actionName, function () { 161 | store.listener.removeListener('change', resolve); 162 | store.listener.removeListener('rollback', reject); 163 | }); 164 | }); 165 | } 166 | 167 | for (var i in stores) { 168 | // Only generate promises for stores that ae listening for this action 169 | if (stores[i].actions && stores[i].actions[actionName] != null) { 170 | promise = __promiseGenerator(stores[i]); 171 | __promises.push(promise); 172 | } 173 | } 174 | return __promises; 175 | }()); 176 | // When all the promises are resolved, dispatcher emits `change:all` event. 177 | return DeLorean.Promise.all(promises).then(function () { 178 | self.listener.emit('change:all'); 179 | }); 180 | }; 181 | 182 | // `registerAction` method adds a method to the prototype. So you can just use 183 | // `dispatcherInstance.actionName()`. 184 | Dispatcher.prototype.registerAction = function (action, callback) { 185 | /* The callback must be a function. */ 186 | if (typeof callback === 'function') { 187 | this[action] = callback.bind(this.stores); 188 | } else { 189 | throw 'Action callback should be a function.'; 190 | } 191 | }; 192 | 193 | // `register` method adds an global action callback to the dispatcher. 194 | Dispatcher.prototype.register = function (callback) { 195 | /* The callback must be a function. */ 196 | if (typeof callback === 'function') { 197 | this.listener.on('dispatch', callback); 198 | } else { 199 | throw 'Global callback should be a function.'; 200 | } 201 | }; 202 | 203 | // `getStore` returns the store from stores hash. 204 | // You can also use `dispatcherInstance.stores[storeName]` but 205 | // it checks if the store really exists. 206 | Dispatcher.prototype.getStore = function (storeName) { 207 | if (!this.stores[storeName]) { 208 | throw 'Store ' + storeName + ' does not exist.'; 209 | } 210 | return this.stores[storeName].getState(); 211 | }; 212 | 213 | // ### Shortcuts 214 | 215 | Dispatcher.prototype.on = function () { 216 | return this.listener.on.apply(this.listener, arguments); 217 | }; 218 | 219 | Dispatcher.prototype.off = function () { 220 | return this.listener.removeListener.apply(this.listener, arguments); 221 | }; 222 | 223 | Dispatcher.prototype.emit = function () { 224 | return this.listener.emit.apply(this.listener, arguments); 225 | }; 226 | 227 | return Dispatcher; 228 | }()); 229 | 230 | // ## Store 231 | 232 | // Stores contain the application state and logic. Their role is somewhat similar 233 | // to a model in a traditional MVC, but they manage the state of many objects. 234 | // Unlike MVC models, they are not instances of one object, nor are they the 235 | // same as Backbone's collections. More than simply managing a collection of 236 | // ORM-style objects, stores manage the application state for a particular 237 | // domain within the application. 238 | Store = (function () { 239 | 240 | // ### Store Prototype 241 | function Store(args) { 242 | if (!this.state) { 243 | this.state = {}; 244 | } 245 | 246 | // `DeLorean.EventEmitter` is `require('events').EventEmitter` by default. 247 | // you can change it using `DeLorean.Flux.define('EventEmitter', AnotherEventEmitter)` 248 | this.listener = new DeLorean.EventEmitter(); 249 | this.bindActions(); 250 | this.buildScheme(); 251 | 252 | this.initialize.apply(this, arguments); 253 | } 254 | 255 | Store.prototype.initialize = function () { 256 | 257 | }; 258 | 259 | Store.prototype.get = function (arg) { 260 | return this.state[arg]; 261 | }; 262 | 263 | // `set` method updates the data defined at the `scheme` of the store. 264 | Store.prototype.set = function (arg1, value) { 265 | var changedProps = []; 266 | if (typeof arg1 === 'object') { 267 | for (var keyName in arg1) { 268 | changedProps.push(keyName); 269 | this.setValue(keyName, arg1[keyName]); 270 | } 271 | } else { 272 | changedProps.push(arg1); 273 | this.setValue(arg1, value); 274 | } 275 | this.recalculate(changedProps); 276 | return this.state[arg1]; 277 | }; 278 | 279 | // `set` method updates the data defined at the `scheme` of the store. 280 | Store.prototype.setValue = function (key, value) { 281 | var scheme = this.scheme, definition; 282 | if (scheme && this.scheme[key]) { 283 | definition = scheme[key]; 284 | 285 | // This will allow you to directly set falsy values before falling back to the definition default 286 | this.state[key] = (typeof value !== 'undefined') ? value : definition.default; 287 | 288 | if (typeof definition.calculate === 'function') { 289 | this.state[__generateOriginalName(key)] = value; 290 | this.state[key] = definition.calculate.call(this, value); 291 | } 292 | } else { 293 | // Scheme **must** include the key you wanted to set. 294 | if (console != null) { 295 | console.warn('Scheme must include the key, ' + key + ', you are trying to set. ' + key + ' will NOT be set on the store.'); 296 | } 297 | } 298 | return this.state[key]; 299 | }; 300 | 301 | // Removes the scheme format and standardizes all the shortcuts. 302 | // If you run `formatScheme({name: 'joe'})` it will return you 303 | // `{name: {default: 'joe'}}`. Also if you run `formatScheme({fullname: function () {}})` 304 | // it will return `{fullname: {calculate: function () {}}}`. 305 | Store.prototype.formatScheme = function (scheme) { 306 | var formattedScheme = {}, definition, defaultValue, calculatedValue; 307 | for (var keyName in scheme) { 308 | definition = scheme[keyName]; 309 | defaultValue = null; 310 | calculatedValue = null; 311 | 312 | formattedScheme[keyName] = {default: null}; 313 | 314 | /* {key: 'value'} will be {key: {default: 'value'}} */ 315 | defaultValue = (definition && typeof definition === 'object') ? 316 | definition.default : definition; 317 | formattedScheme[keyName].default = defaultValue; 318 | 319 | /* {key: function () {}} will be {key: {calculate: function () {}}} */ 320 | if (definition && typeof definition.calculate === 'function') { 321 | calculatedValue = definition.calculate; 322 | /* Put a dependency array on formattedSchemes with calculate defined */ 323 | if (definition.deps) { 324 | formattedScheme[keyName].deps = definition.deps; 325 | } else { 326 | formattedScheme[keyName].deps = []; 327 | } 328 | 329 | } else if (typeof definition === 'function') { 330 | calculatedValue = definition; 331 | } 332 | if (calculatedValue) { 333 | formattedScheme[keyName].calculate = calculatedValue; 334 | } 335 | } 336 | return formattedScheme; 337 | }; 338 | 339 | /* Applying `scheme` to the store if exists. */ 340 | Store.prototype.buildScheme = function () { 341 | var scheme, calculatedData, keyName, definition, dependencyMap, dependents, dep, changedProps = []; 342 | 343 | if (typeof this.scheme === 'object') { 344 | /* Scheme must be formatted to standardize the keys. */ 345 | scheme = this.scheme = this.formatScheme(this.scheme); 346 | dependencyMap = this.__dependencyMap = {}; 347 | 348 | /* Set the defaults first */ 349 | for (keyName in scheme) { 350 | definition = scheme[keyName]; 351 | this.state[keyName] = __clone(definition.default); 352 | } 353 | 354 | /* Set the calculations */ 355 | for (keyName in scheme) { 356 | definition = scheme[keyName]; 357 | if (definition.calculate) { 358 | // Create a dependency map - {keyName: [arrayOfKeysThatDependOnIt]} 359 | dependents = definition.deps || []; 360 | 361 | for (var i = 0; i < dependents.length; i++) { 362 | dep = dependents[i]; 363 | if (dependencyMap[dep] == null) { 364 | dependencyMap[dep] = []; 365 | } 366 | dependencyMap[dep].push(keyName); 367 | } 368 | 369 | this.state[__generateOriginalName(keyName)] = definition.default; 370 | this.state[keyName] = definition.calculate.call(this, definition.default); 371 | changedProps.push(keyName); 372 | } 373 | } 374 | // Recalculate any properties dependent on those that were just set 375 | this.recalculate(changedProps); 376 | } 377 | }; 378 | 379 | Store.prototype.recalculate = function (changedProps) { 380 | var scheme = this.scheme, dependencyMap = this.__dependencyMap, didRun = [], definition, keyName, dependents, dep; 381 | // Only iterate over the properties that just changed 382 | for (var i = 0; i < changedProps.length; i++) { 383 | dependents = dependencyMap[changedProps[i]]; 384 | // If there are no properties dependent on this property, do nothing 385 | if (dependents == null) { 386 | continue; 387 | } 388 | // Iterate over the dependendent properties 389 | for (var d = 0; d < dependents.length; d++) { 390 | dep = dependents[d]; 391 | // Do nothing if this value has already been recalculated on this change batch 392 | if (didRun.indexOf(dep) !== -1) { 393 | continue; 394 | } 395 | // Calculate this value 396 | definition = scheme[dep]; 397 | this.state[dep] = definition.calculate.call(this, 398 | this.state[__generateOriginalName(dep)] || definition.default); 399 | 400 | // Make sure this does not get calculated again in this change batch 401 | didRun.push(dep); 402 | } 403 | } 404 | // Update Any deps on the deps 405 | if (didRun.length > 0) { 406 | this.recalculate(didRun); 407 | } 408 | this.listener.emit('change'); 409 | }; 410 | 411 | Store.prototype.getState = function () { 412 | return this.state; 413 | }; 414 | 415 | Store.prototype.clearState = function () { 416 | this.state = {}; 417 | return this; 418 | }; 419 | 420 | Store.prototype.resetState = function () { 421 | this.buildScheme(); 422 | this.listener.emit('change'); 423 | return this; 424 | }; 425 | 426 | // Stores must have a `actions` hash of `actionName: methodName` 427 | // `methodName` is the `this.store`'s prototype method.. 428 | Store.prototype.bindActions = function () { 429 | var callback; 430 | 431 | this.emitChange = this.listener.emit.bind(this.listener, 'change'); 432 | this.emitRollback = this.listener.emit.bind(this.listener, 'rollback'); 433 | this.rollback = this.listener.on.bind(this.listener, '__rollback'); 434 | this.emit = this.listener.emit.bind(this.listener); 435 | 436 | for (var actionName in this.actions) { 437 | if (__hasOwn(this.actions, actionName)) { 438 | callback = this.actions[actionName]; 439 | if (typeof this[callback] !== 'function') { 440 | throw 'Callback \'' + callback + '\' defined for action \'' + actionName + '\' should be a method defined on the store!'; 441 | } 442 | /* And `actionName` should be a name generated by `__generateActionName` */ 443 | this.listener.on(__generateActionName(actionName), this[callback].bind(this)); 444 | } 445 | } 446 | }; 447 | 448 | // `dispatchAction` called from a dispatcher. You can also call anywhere but 449 | // you probably won't need to do. It simply **emits an event with a payload**. 450 | Store.prototype.dispatchAction = function (actionName, data) { 451 | this.listener.emit(__generateActionName(actionName), data); 452 | // The cleanup_{actionName} event removes any remaining listeners after the action was fully handled 453 | this.listener.emit('cleanup_' + actionName); 454 | }; 455 | 456 | // ### Shortcuts 457 | 458 | // `listenChanges` is a shortcut for `Object.observe` usage. You can just use 459 | // `Object.observe(object, function () { ... })` but everytime you use it you 460 | // repeat yourself. DeLorean has a shortcut doing this properly. 461 | Store.prototype.listenChanges = function (object) { 462 | var self = this, observer; 463 | if (!Object.observe) { 464 | console.error('Store#listenChanges method uses Object.observe, you should fire changes manually.'); 465 | return; 466 | } 467 | 468 | observer = Array.isArray(object) ? Array.observe : Object.observe; 469 | 470 | observer(object, function (changes) { 471 | self.listener.emit('change', changes); 472 | }); 473 | }; 474 | 475 | // `onChange` simply listens changes and calls a callback. Shortcut for 476 | // a `on('change')` command. 477 | Store.prototype.onChange = function (callback) { 478 | this.listener.on('change', callback); 479 | }; 480 | 481 | return Store; 482 | }()); 483 | 484 | // ### Flux Wrapper 485 | DeLorean.Flux = { 486 | 487 | // `createStore` generates a store based on the definition 488 | createStore: function (definition) { 489 | /* store parameter must be an `object` */ 490 | if (typeof definition !== 'object') { 491 | throw 'Stores should be defined by passing the definition to the constructor'; 492 | } 493 | 494 | // extends the store with the definition attributes 495 | var Child = function () { return Store.apply(this, arguments); }; 496 | var Surrogate = function () { this.constructor = Child; }; 497 | Surrogate.prototype = Store.prototype; 498 | Child.prototype = new Surrogate(); 499 | 500 | __extend(Child.prototype, definition); 501 | 502 | return new Child(); 503 | }, 504 | 505 | // `createDispatcher` generates a dispatcher with actions to dispatch. 506 | /* `actionsToDispatch` should be an object. */ 507 | createDispatcher: function (actionsToDispatch) { 508 | var actionsOfStores, dispatcher, callback, triggers, triggerMethod; 509 | 510 | // If it has `getStores` method it should be get and pass to the `Dispatcher` 511 | if (typeof actionsToDispatch.getStores === 'function') { 512 | actionsOfStores = actionsToDispatch.getStores(); 513 | } 514 | 515 | /* If there are no stores defined, it's an empty object. */ 516 | dispatcher = new Dispatcher(actionsOfStores || {}); 517 | 518 | /* Now call `registerAction` method for every action. */ 519 | for (var actionName in actionsToDispatch) { 520 | if (__hasOwn(actionsToDispatch, actionName)) { 521 | /* `getStores` & `viewTriggers` are special properties, it's not an action. Also an extra check to make sure we're binding to a function */ 522 | if (actionName !== 'getStores' && actionName !== 'viewTriggers' && typeof actionsToDispatch[actionName] === 'function') { 523 | callback = actionsToDispatch[actionName]; 524 | dispatcher.registerAction(actionName, callback.bind(dispatcher)); 525 | } 526 | } 527 | } 528 | 529 | /* Bind triggers */ 530 | triggers = actionsToDispatch.viewTriggers; 531 | for (var triggerName in triggers) { 532 | triggerMethod = triggers[triggerName]; 533 | if (typeof dispatcher[triggerMethod] === 'function') { 534 | dispatcher.on(triggerName, dispatcher[triggerMethod]); 535 | } else { 536 | if (console != null) { 537 | console.warn(triggerMethod + ' should be a method defined on your dispatcher. The ' + triggerName + ' trigger will not be bound to any method.'); 538 | } 539 | } 540 | } 541 | 542 | // Allow only a single dispatcher 543 | if (DeLorean.dispatcher != null) { 544 | if (console != null) { 545 | console.warn('You are attempting to create more than one dispatcher. DeLorean is intended to be used with a single dispatcher. This latest dispatcher created will overwrite any previous versions.'); 546 | } 547 | } 548 | 549 | // Create an internal reference to the dispathcer instance. This allows it to be found by the mixins. 550 | DeLorean.dispatcher = dispatcher; 551 | 552 | return dispatcher; 553 | }, 554 | // ### `DeLorean.Flux.define` 555 | // It's a key to _hack_ DeLorean easily. You can just inject something 556 | // you want to define. 557 | define: function (key, value) { 558 | DeLorean[key] = value; 559 | } 560 | }; 561 | 562 | // Store and Dispatcher are the only base classes of DeLorean. 563 | DeLorean.Dispatcher = Dispatcher; 564 | DeLorean.Store = Store; 565 | 566 | // ## Built-in React Mixin 567 | DeLorean.Flux.mixins = { 568 | 569 | // This mixin adds the this.trigger method to the component 570 | // Components can then trigger actions in flux w/out watching stores and having their state 571 | trigger: { 572 | componentWillMount: function () { 573 | this.__dispatcher = __findDispatcher(this); 574 | }, 575 | trigger: function () { 576 | this.__dispatcher.emit.apply(this.__dispatcher, arguments); 577 | } 578 | }, 579 | // It should be inserted to the React components which 580 | // used in Flux. 581 | // Simply `mixin: [Flux.mixins.storeListener]` will work. 582 | storeListener: { 583 | 584 | trigger: function () { 585 | this.__dispatcher.emit.apply(this.__dispatcher, arguments); 586 | }, 587 | 588 | // After the component mounted, listen changes of the related stores 589 | componentDidMount: function () { 590 | var self = this, store, storeName; 591 | 592 | /* `__changeHandler` is a **listener generator** to pass to the `onChange` function. */ 593 | function __changeHandler(store, storeName) { 594 | return function () { 595 | var state, args; 596 | self.setState(self.getStoreStates()); 597 | // When something changes it calls the components `storeDidChanged` method if exists. 598 | if (self.storeDidChange) { 599 | args = [storeName].concat(Array.prototype.slice.call(arguments, 0)); 600 | self.storeDidChange.apply(self, args); 601 | } 602 | }; 603 | } 604 | 605 | // Remember the change handlers so they can be removed later 606 | this.__changeHandlers = {}; 607 | 608 | /* Generate and bind the change handlers to the stores. */ 609 | for (storeName in this.__watchStores) { 610 | if (__hasOwn(this.stores, storeName)) { 611 | store = this.stores[storeName]; 612 | this.__changeHandlers[storeName] = __changeHandler(store, storeName); 613 | store.onChange(this.__changeHandlers[storeName]); 614 | } 615 | } 616 | }, 617 | 618 | // When a component unmounted, it should stop listening. 619 | componentWillUnmount: function () { 620 | for (var storeName in this.__changeHandlers) { 621 | if (__hasOwn(this.stores, storeName)) { 622 | var store = this.stores[storeName]; 623 | store.listener.removeListener('change', this.__changeHandlers[storeName]); 624 | } 625 | } 626 | }, 627 | 628 | getInitialState: function () { 629 | var self = this, state, storeName, watchStores; 630 | 631 | /* The dispatcher should be easy to access and it should use `__findDispatcher` 632 | method to find the parent dispatchers. */ 633 | this.__dispatcher = __findDispatcher(this); 634 | 635 | // If `storesDidChange` method presents, it'll be called after all the stores 636 | // were changed. 637 | if (this.storesDidChange) { 638 | this.__dispatcher.on('change:all', function () { 639 | self.storesDidChange(); 640 | }); 641 | } 642 | 643 | // Since `dispatcher.stores` is harder to write, there's a shortcut for it. 644 | // You can use `this.stores` from the React component. 645 | this.stores = this.__dispatcher.stores; 646 | 647 | this.__watchStores = {}; 648 | 649 | // Allow watchStores to be passed as a prop 650 | watchStores = this.watchStores || this.props.watchStores; 651 | 652 | if (watchStores != null) { 653 | for (var i = 0; i < watchStores.length; i++) { 654 | storeName = watchStores[i]; 655 | this.__watchStores[storeName] = this.stores[storeName]; 656 | } 657 | } else { 658 | this.__watchStores = this.stores; 659 | if (console != null && Object.keys != null && Object.keys(this.stores).length > 4) { 660 | console.warn('Your component is watching changes on all stores, you may want to define a "watchStores" property in order to only watch stores relevant to this component.'); 661 | } 662 | } 663 | 664 | return this.getStoreStates(); 665 | }, 666 | 667 | getStoreStates: function () { 668 | var state = {stores: {}}, store; 669 | 670 | /* Set `state.stores` for all present stores with a `setState` method defined. */ 671 | for (var storeName in this.__watchStores) { 672 | if (__hasOwn(this.stores, storeName)) { 673 | state.stores[storeName] = this.__watchStores[storeName].getState(); 674 | } 675 | } 676 | return state; 677 | }, 678 | 679 | // `getStore` is a shortcut to get the store from the state. 680 | getStore: function (storeName) { 681 | if (console != null && typeof this.__watchStores[storeName] === 'undefined') { 682 | var message; 683 | message = 'Attempt to getStore ' + storeName + ' failed. '; 684 | message += typeof this.stores[storeName] === 'undefined' ? 'It is not defined on the dispatcher. ' : 'It is not being watched by the component. '; 685 | message += this.constructor != null && this.constructor.displayName != null ? 'Check the ' + this.constructor.displayName + ' component.' : ''; 686 | console.warn(message); 687 | } 688 | return this.state.stores[storeName]; 689 | } 690 | } 691 | }; 692 | 693 | // ## DeLorean API 694 | // DeLorean can be used in **CommonJS** projects. 695 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 696 | 697 | var requirements = require('./requirements'); 698 | for (var requirement in requirements) { 699 | DeLorean.Flux.define(requirement, requirements[requirement]); 700 | } 701 | module.exports = DeLorean; 702 | 703 | // It can be also used in **AMD** projects, too. 704 | // And if there is no module system initialized, just pass the DeLorean 705 | // to the `window`. 706 | } else { 707 | if (typeof define === 'function' && define.amd) { 708 | define(['./requirements.js'], function (requirements) { 709 | // Import Modules in require.js pattern 710 | for (var requirement in requirements) { 711 | DeLorean.Flux.define(requirement, requirements[requirement]); 712 | } 713 | 714 | return DeLorean; 715 | }); 716 | } else { 717 | window.DeLorean = DeLorean; 718 | } 719 | } 720 | 721 | })({}); 722 | -------------------------------------------------------------------------------- /src/requirements.js: -------------------------------------------------------------------------------- 1 | // ## Dependency injection file. 2 | 3 | // You can change dependencies using `DeLorean.Flux.define`. There are 4 | // two dependencies now: `EventEmitter` and `Promise` 5 | var requirements; 6 | 7 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 8 | module.exports = requirements = { 9 | // DeLorean uses **Node.js native EventEmitter** for event emittion 10 | EventEmitter: require('events').EventEmitter, 11 | // and **es6-promise** for Deferred object management. 12 | Promise: require('es6-promise').Promise 13 | }; 14 | } else if (typeof define === 'function' && define.amd) { 15 | define(function (require, exports, module) { 16 | var events = require('events'), 17 | promise = require('es6-promise'); 18 | 19 | // Return the module value - http://requirejs.org/docs/api.html#cjsmodule 20 | // Using simplified wrapper 21 | return { 22 | // DeLorean uses **Node.js native EventEmitter** for event emittion 23 | EventEmitter: require('events').EventEmitter, 24 | // and **es6-promise** for Deferred object management. 25 | Promise: require('es6-promise').Promise 26 | }; 27 | }); 28 | } else { 29 | window.DeLorean = DeLorean; 30 | } 31 | 32 | // It's better you don't change them if you really need to. 33 | 34 | // This library needs to work for Browserify and also standalone. 35 | // If DeLorean is defined, it means it's called from the browser, not 36 | // the browserify. 37 | 38 | if (typeof DeLorean !== 'undefined') { 39 | for (var requirement in requirements) { 40 | DeLorean.Flux.define(requirement, requirements[requirement]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | basePath: '..', 4 | singleRun: true, 5 | autoWatch: true, 6 | frameworks: ['jasmine'], 7 | files: [ 8 | 'test/spec/common.js', 9 | 'test/vendor/react-0.11.1.js', 10 | 'src/delorean.js', 11 | 'dist/.tmp/delorean-requires.js', 12 | 'test/spec/**/*Spec.js' 13 | ], 14 | browsers: ['PhantomJS'], 15 | reporters: ['progress', 'coverage'], 16 | preprocessors: { 17 | 'src/**/*.js': ['coverage'] 18 | }, 19 | coverageReporter: { 20 | reporters: [ 21 | {type: 'html', dir: 'coverage/'}, 22 | {type: 'text'} 23 | ] 24 | } 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /test/spec/common.js: -------------------------------------------------------------------------------- 1 | if (!Function.prototype.bind) { 2 | Function.prototype.bind = function (oThis) { 3 | if (typeof this !== 'function') { 4 | // closest thing possible to the ECMAScript 5 5 | // internal IsCallable function 6 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 7 | } 8 | 9 | var aArgs = Array.prototype.slice.call(arguments, 1), 10 | fToBind = this, 11 | FNOP = function () {}, 12 | fBound = function () { 13 | try { 14 | return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, 15 | aArgs.concat(Array.prototype.slice.call(arguments))); 16 | } catch (e) { 17 | return this; 18 | } 19 | 20 | }; 21 | 22 | FNOP.prototype = this.prototype; 23 | fBound.prototype = new FNOP(); 24 | 25 | return fBound; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /test/spec/core/fluxSpec.js: -------------------------------------------------------------------------------- 1 | describe('Flux', function () { 2 | 3 | var initializeSpy = jasmine.createSpy('construction'); 4 | var listenerSpy = jasmine.createSpy('change'); 5 | var listenerSpy2 = jasmine.createSpy('change'); 6 | var calculateSpy = jasmine.createSpy('calculate'); 7 | 8 | var myStore = DeLorean.Flux.createStore({ 9 | state: { 10 | list: [], 11 | text: '' 12 | }, 13 | initialize: initializeSpy, 14 | actions: { 15 | // Remember the `dispatch('addItem')` 16 | addItem: 'addItemMethod', 17 | noChange: 'noChangeEvent' 18 | }, 19 | addItemMethod: function (data) { 20 | this.state.list.push('ITEM: ' + data.random); 21 | 22 | // You need to say your store is changed. 23 | this.emit('change'); 24 | }, 25 | noChangeEvent: function (data) { 26 | this.state.text = data; 27 | } 28 | }); 29 | 30 | var myStore2 = DeLorean.Flux.createStore({ 31 | state: { 32 | list: [] 33 | }, 34 | initialize: initializeSpy, 35 | actions: { 36 | // Remember the `dispatch('addItem')` 37 | addItem: 'addItemMethod' 38 | }, 39 | addItemMethod: function (data) { 40 | this.state.list.push('ANOTHER: ' + data.random); 41 | 42 | // You need to say your store is changed. 43 | this.emit('change'); 44 | } 45 | }); 46 | 47 | var MyAppDispatcher = DeLorean.Flux.createDispatcher({ 48 | addItem: function (data) { 49 | this.dispatch('addItem', data); 50 | }, 51 | 52 | getStores: function () { 53 | return { 54 | myStore: myStore, 55 | myStore2: myStore2 56 | }; 57 | } 58 | }); 59 | 60 | var ActionCreator = { 61 | addItem: function () { 62 | // We'll going to call dispatcher methods. 63 | MyAppDispatcher.addItem({random: 'hello world'}); 64 | } 65 | }; 66 | 67 | myStore.onChange(listenerSpy); 68 | myStore2.onChange(listenerSpy2); 69 | ActionCreator.addItem(); 70 | 71 | it('store should be initialized', function () { 72 | expect(initializeSpy).toHaveBeenCalled(); 73 | }); 74 | 75 | it('should call run action creator', function () { 76 | expect(listenerSpy).toHaveBeenCalled(); 77 | expect(listenerSpy2).toHaveBeenCalled(); 78 | }); 79 | 80 | it('should change data', function () { 81 | expect(myStore.getState().list.length).toBe(1); 82 | expect(myStore2.getState().list.length).toBe(1); 83 | 84 | ActionCreator.addItem(); 85 | expect(myStore.getState().list.length).toBe(2); 86 | expect(myStore2.getState().list.length).toBe(2); 87 | 88 | expect(myStore.getState().list[0]).toBe('ITEM: hello world'); 89 | expect(myStore2.getState().list[0]).toBe('ANOTHER: hello world'); 90 | }); 91 | 92 | it('should clear data', function () { 93 | myStore.clearState(); 94 | expect(myStore.getState()).toEqual({}); 95 | }); 96 | 97 | it('should cleanup unused events after firing an action handler', function () { 98 | MyAppDispatcher.dispatch('noChange', 'someText'); 99 | MyAppDispatcher.dispatch('noChange', 'someText'); 100 | MyAppDispatcher.dispatch('noChange', 'someText'); 101 | MyAppDispatcher.dispatch('noChange', 'someText'); 102 | changeListenerCount = myStore.listener.listeners('change').length; 103 | rollbackListenerCount = myStore.listener.listeners('rollback').length; 104 | // Note that the 'cleanup_{actionName}' event has not fired yet and removed the last 2 events (change & rolback), so there will be one remaining of each event at this point. 105 | // however, without the cleanup, there would be 4 of each after 4 calls 106 | expect(changeListenerCount).toEqual(1); 107 | expect(rollbackListenerCount).toEqual(1); 108 | }); 109 | 110 | it('dispatcher can listen events', function () { 111 | var spy = jasmine.createSpy('dispatcher listener'); 112 | MyAppDispatcher.on('hello', spy); 113 | MyAppDispatcher.listener.emit('hello'); 114 | expect(spy).toHaveBeenCalled(); 115 | }); 116 | 117 | it('dispatcher can listen events', function () { 118 | var spy = jasmine.createSpy('dispatcher listener'); 119 | MyAppDispatcher.on('hello', spy); 120 | MyAppDispatcher.off('hello', spy); 121 | MyAppDispatcher.listener.emit('hello'); 122 | 123 | expect(spy).not.toHaveBeenCalled(); 124 | }); 125 | 126 | var myStoreWithScheme = DeLorean.Flux.createStore({ 127 | actions: {}, 128 | scheme: { 129 | greeting: 'hello', 130 | place: { 131 | default: 'world' 132 | }, 133 | otherPlace: 'outerspace', 134 | greetPlace: { 135 | deps: ['greeting', 'place'], 136 | default: 'hey', 137 | calculate: function (value) { 138 | return value.toUpperCase() + ' ' + this.state.greeting + ', ' + this.state.place; 139 | } 140 | }, 141 | parsedValue: function (value) { 142 | return { 143 | a: value.b, 144 | b: value.a 145 | }; 146 | }, 147 | randomProp: 'random', 148 | anotherCalculated: { 149 | deps: ['randomProp'], 150 | default: null, 151 | calculate: calculateSpy 152 | }, 153 | dependentOnCalculated: { 154 | deps: ['greetPlace'], 155 | calculate: function () { 156 | return this.state.greetPlace; 157 | } 158 | }, 159 | objectDefault: { 160 | default: { 161 | name: 'Test default objects get cloned' 162 | } 163 | } 164 | } 165 | }); 166 | 167 | describe('scheme', function () { 168 | it('should cause default and calculated scheme properties to be created on instantiation', function () { 169 | expect(myStoreWithScheme.getState().greeting).toBe('hello'); 170 | expect(myStoreWithScheme.getState().place).toBe('world'); 171 | expect(myStoreWithScheme.getState().greetPlace).toBe('HEY hello, world'); 172 | }); 173 | 174 | it('should clone defaults that are objects, rather than applying them directly', function () { 175 | expect(myStoreWithScheme.scheme.objectDefault.default).not.toBe(myStoreWithScheme.getState().objectDefault); 176 | }); 177 | 178 | it('should re-calculate scheme properties with #calculate and deps defined', function () { 179 | myStoreWithScheme.set('greeting', 'goodbye'); 180 | expect(myStoreWithScheme.getState().greetPlace).toBe('HEY goodbye, world'); 181 | }); 182 | 183 | it('should set scheme set scheme properties that are functions to the return value', function () { 184 | var input = { 185 | a: 'a', 186 | b: 'b' 187 | }; 188 | myStoreWithScheme.set('parsedValue', input); 189 | expect(myStoreWithScheme.getState().parsedValue.a).toBe(input.b); 190 | }); 191 | 192 | it('should be able to accept an object when setting scheme', function () { 193 | myStoreWithScheme.set({ 194 | greeting: 'aloha', 195 | place: 'Hawaii' 196 | }); 197 | expect(myStoreWithScheme.getState().greeting).toBe('aloha'); 198 | expect(myStoreWithScheme.getState().place).toBe('Hawaii'); 199 | expect(myStoreWithScheme.getState().greetPlace).toBe('HEY aloha, Hawaii'); 200 | }); 201 | 202 | it('should call calculate only in instantiation and when a dependency is set', function () { 203 | expect(calculateSpy.calls.length).toBe(1); // should have been called once on intantiation 204 | myStoreWithScheme.set('otherPlace', 'hey'); 205 | expect(calculateSpy.calls.length).toBe(1); // should not have been called again, because otherPlace is not a dep 206 | }); 207 | 208 | it('should allow setting calculated properties directly', function () { 209 | myStoreWithScheme.set('greetPlace', 'Ahoy'); 210 | expect(myStoreWithScheme.getState().greetPlace).toBe('AHOY aloha, Hawaii'); 211 | }); 212 | 213 | it('should allow a calculated property to be dependent on another calculated property', function () { 214 | myStoreWithScheme.set({ 215 | greeting: 'hola', 216 | place: 'Spain' 217 | }); 218 | expect(myStoreWithScheme.getState().dependentOnCalculated).toBe('AHOY hola, Spain'); 219 | }); 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /test/spec/core/reactSpec.js: -------------------------------------------------------------------------------- 1 | describe('React Test', function () { 2 | 3 | var storeSpy = jasmine.createSpy('store spy'); 4 | var storesSpy = jasmine.createSpy('stores spy'); 5 | 6 | var myStore = DeLorean.Flux.createStore({ 7 | state: { 8 | list: [] 9 | }, 10 | actions: { 11 | // Remember the `dispatch('addItem')` 12 | addItem: 'addItemMethod' 13 | }, 14 | addItemMethod: function (data) { 15 | this.state.list.push('ITEM: ' + data.random); 16 | 17 | // You need to say your store is changed. 18 | this.emit('change'); 19 | } 20 | }); 21 | 22 | var myStore2 = DeLorean.Flux.createStore({ 23 | state: { 24 | list: [] 25 | }, 26 | actions: { 27 | // Remember the `dispatch('addItem')` 28 | addItem: 'addItemMethod' 29 | }, 30 | addItemMethod: function (data) { 31 | this.state.list.push('ANOTHER: ' + data.random); 32 | 33 | // You need to say your store is changed. 34 | this.emit('change'); 35 | } 36 | }); 37 | 38 | var MyAppDispatcher = DeLorean.Flux.createDispatcher({ 39 | addItem: function (data) { 40 | this.dispatch('addItem', data); 41 | }, 42 | 43 | getStores: function () { 44 | return { 45 | myStore: myStore, 46 | myStore2: myStore2 47 | }; 48 | } 49 | }); 50 | 51 | var ActionCreator = { 52 | addItem: function () { 53 | // We'll going to call dispatcher methods. 54 | MyAppDispatcher.addItem({random: 'hello world'}); 55 | } 56 | }; 57 | 58 | var el = document.createElement('div'); 59 | el.id = 'test'; 60 | document.body.appendChild(el); 61 | 62 | var ApplicationView = React.createClass({displayName: 'ApplicationView', 63 | 64 | mixins: [DeLorean.Flux.mixins.storeListener], 65 | 66 | storeDidChange: storeSpy, 67 | 68 | render: function () { 69 | return React.DOM.div(null, 70 | React.DOM.span(null, 'There are ', this.getStore('myStore').list.length, ' items.'), 71 | React.DOM.span(null, 'There are ', this.getStore('myStore2').list.length, ' items.') 72 | ); 73 | } 74 | 75 | }); 76 | 77 | var mainView = React.renderComponent(ApplicationView({dispatcher: MyAppDispatcher}), 78 | document.getElementById('test')); 79 | 80 | it('dispatcher can get stores itself', function () { 81 | expect(MyAppDispatcher.getStore('myStore')).toBe(myStore.getState()); 82 | expect(MyAppDispatcher.getStore('myStore2')).toBe(myStore2.getState()); 83 | }); 84 | 85 | it('should be no item before add', function () { 86 | expect(el.innerText).toBe('There are 0 items.There are 0 items.'); 87 | }); 88 | 89 | it('should have and item after add', function () { 90 | ActionCreator.addItem(); 91 | ActionCreator.addItem(); 92 | ActionCreator.addItem(); 93 | expect(el.innerText).toBe('There are 3 items.There are 3 items.'); 94 | expect(storeSpy).toHaveBeenCalledWith('myStore'); 95 | expect(storeSpy).toHaveBeenCalledWith('myStore2'); 96 | }); 97 | 98 | }); 99 | -------------------------------------------------------------------------------- /upgrade.md: -------------------------------------------------------------------------------- 1 | Prior to `1.0`, icrementing the second decimal place indicates a potential breaking change. 2 | 3 | **0.8.x to 0.9.x** 4 | 5 | - Stores are now singletons. The dispatcher's `getStores` method must go from... 6 | ```javascript 7 | getStores: function () 8 | return{ 9 | myStore: MyStore() 10 | } 11 | } 12 | ``` 13 | to... 14 | ```javascript 15 | getStores: function () 16 | return{ 17 | myStore: MyStore 18 | } 19 | } 20 | ``` 21 | where `MyStore` is the output of `Flux.createStore` 22 | 23 | - Scheme values are now set on a `store.state` object rather than directly on the store itself. If you are using `scheme`, and accessing and scheme properties directly within a store (rather than just through `this.set`), you will need to update your code to access the property from the `state` object... 24 | ```javascript 25 | this.mySchemeProp.push(newValue); 26 | ``` 27 | needs to become... 28 | ```javascript 29 | this.state.mySchemeProp.push(newValue); 30 | ``` --------------------------------------------------------------------------------