├── web ├── CNAME ├── assets │ └── alt.png ├── .gitignore ├── Gemfile ├── _includes │ ├── guidenav.html │ ├── guidesnav.html │ ├── sidenav.html │ ├── footer.html │ ├── head.html │ └── header.html ├── _layouts │ ├── default.html │ ├── post.html │ ├── docs.html │ ├── guide.html │ └── guides.html ├── scripts │ ├── search.js │ └── createIndex.js ├── blog.html ├── webpack.config.js ├── README.md ├── feed.xml ├── Gemfile.lock ├── _config.yml └── index.html ├── .babelrc ├── .eslintignore ├── examples ├── weathertabs │ ├── .gitignore │ ├── js │ │ ├── alt.js │ │ ├── app.js │ │ ├── actions │ │ │ ├── AppActions.js │ │ │ └── WeatherActions.js │ │ ├── stores │ │ │ ├── WeatherStore.js │ │ │ └── AppStore.js │ │ └── components │ │ │ ├── WeatherTab.jsx │ │ │ └── App.jsx │ ├── images │ │ └── loading.gif │ ├── index.html │ ├── package.json │ ├── README.md │ └── css │ │ └── app.css ├── chat │ ├── .gitignore │ ├── js │ │ ├── alt.js │ │ ├── actions │ │ │ ├── ChatThreadActionCreators.js │ │ │ ├── ChatServerActionCreators.js │ │ │ └── ChatMessageActionCreators.js │ │ ├── utils │ │ │ ├── ChatMessageDataUtils.js │ │ │ ├── ChatMessageUtils.js │ │ │ └── ChatWebAPIUtils.js │ │ ├── stores │ │ │ ├── __tests__ │ │ │ │ └── UnreadThreadStore-test.js │ │ │ ├── UnreadThreadStore.js │ │ │ ├── ThreadStore.js │ │ │ └── MessageStore.js │ │ ├── components │ │ │ ├── ChatApp.react.js │ │ │ ├── MessageListItem.react.js │ │ │ ├── MessageComposer.react.js │ │ │ ├── ThreadListItem.react.js │ │ │ ├── ThreadSection.react.js │ │ │ └── MessageSection.react.js │ │ ├── app.js │ │ └── ChatExampleData.js │ ├── index.html │ ├── README.md │ ├── package.json │ └── css │ │ └── chatapp.css └── todomvc │ ├── .gitignore │ ├── todomvc-common │ ├── bower.json │ ├── bg.png │ └── readme.md │ ├── js │ ├── alt.js │ ├── app.js │ ├── actions │ │ └── TodoActions.js │ ├── components │ │ ├── Header.react.js │ │ ├── MainSection.react.js │ │ ├── TodoApp.react.js │ │ ├── Footer.react.js │ │ ├── TodoTextInput.react.js │ │ └── TodoItem.react.js │ └── stores │ │ └── TodoStore.js │ ├── css │ └── app.css │ ├── index.html │ └── package.json ├── dist └── alt-with-runtime.js ├── AltContainer.js ├── AltNativeContainer.js ├── test ├── helpers │ ├── alt.js │ ├── SampleActions.js │ ├── SaaM.js │ ├── SampleApp2.jsx │ ├── SampleApp.jsx │ └── ReactComponent.js ├── babel │ └── index.js ├── statics-test.js ├── browser │ ├── index.html │ └── index.js ├── store-as-a-module.js ├── stores-get-alt.js ├── async-action-test.js ├── debug-alt-test.js ├── store-transforms-test.js ├── reducer-test.js ├── stores-with-colliding-names.js ├── value-stores-test.js ├── actions-dump-test.js ├── config-set-get-state-test.js ├── listen-to-actions.js ├── testing-utils.js ├── isomorphic-test.js ├── store-model-test.js ├── alt-config-object.js ├── before-and-after-test.js ├── bound-listeners-test.js ├── failed-dispatch-test.js ├── final-store.js ├── alt-manager-util.js ├── functional-tools-test.js ├── atomic-transactions-test.js └── batching-test.js ├── src ├── utils │ ├── debug │ │ ├── alt.js │ │ ├── DebugActions.js │ │ ├── ViewerStore.js │ │ └── AltStore.js │ ├── chromeDebug.js │ ├── makeHot.js │ ├── statics.js │ ├── withAltContext.js │ ├── functions.js │ ├── ImmutableUtil.js │ ├── Debugger.js │ ├── AltIso.js │ ├── atomic.js │ ├── AltTestingUtils.js │ ├── reducers.js │ ├── makeFinalStore.js │ ├── IsomorphicRenderer.js │ ├── fp.js │ ├── StoreExplorer.js │ ├── ActionListeners.js │ ├── TimeTravel.js │ ├── connect.js │ ├── AltManager.js │ ├── decorators.js │ └── connectToStores.js └── alt │ ├── addons.js │ ├── utils │ ├── AltUtils.js │ └── StateFunctions.js │ ├── actions │ └── index.js │ └── store │ └── AltStore.js ├── .npmignore ├── .gitignore ├── .travis.yml ├── .editorconfig ├── mixins ├── IsomorphicMixin.js ├── Subscribe.js ├── ListenerMixin.js ├── FluxyMixin.js ├── ReactStateMagicMixin.js └── AltManagerMixin.js ├── docs ├── prepare.md ├── rollback.md ├── recycle.md ├── takeSnapshot.md ├── flush.md ├── bootstrap.md ├── utils │ └── hotReload.md ├── actions.md ├── stores.md ├── index.md └── errors.md ├── components ├── AltNativeContainer.js └── AltContainer.js ├── dist.config.js ├── dist.min.config.js ├── bower.json ├── guides └── getting-started │ ├── actions.md │ ├── store.md │ ├── view.md │ ├── index.md │ └── wait-for.md ├── CONTRIBUTING.md └── package.json /web/CNAME: -------------------------------------------------------------------------------- 1 | alt.js.org 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/utils/TimeTravel.js 2 | -------------------------------------------------------------------------------- /examples/weathertabs/.gitignore: -------------------------------------------------------------------------------- 1 | /js/bundle.js 2 | -------------------------------------------------------------------------------- /dist/alt-with-runtime.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../'); 2 | -------------------------------------------------------------------------------- /examples/chat/.gitignore: -------------------------------------------------------------------------------- 1 | /js/bundle.js 2 | /js/bundle.min.js 3 | -------------------------------------------------------------------------------- /examples/todomvc/.gitignore: -------------------------------------------------------------------------------- 1 | /js/bundle.js 2 | /js/bundle.min.js 3 | -------------------------------------------------------------------------------- /AltContainer.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./components/AltContainer.js') 2 | -------------------------------------------------------------------------------- /AltNativeContainer.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./components/AltNativeContainer.js') 2 | -------------------------------------------------------------------------------- /web/assets/alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/variable/alt/master/web/assets/alt.png -------------------------------------------------------------------------------- /examples/weathertabs/js/alt.js: -------------------------------------------------------------------------------- 1 | var Alt = require('../../../'); 2 | module.exports = new Alt(); 3 | -------------------------------------------------------------------------------- /test/helpers/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from '../../dist/alt-with-runtime' 2 | export default new Alt() 3 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .bundle 3 | .ruby-version 4 | _site 5 | docs 6 | guide 7 | guides 8 | -------------------------------------------------------------------------------- /examples/chat/js/alt.js: -------------------------------------------------------------------------------- 1 | var Alt = require('alt') 2 | var alt = new Alt() 3 | 4 | module.exports = alt 5 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "0.1.9" 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/debug/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from '../../' 2 | 3 | const alt = new Alt() 4 | 5 | export default alt 6 | -------------------------------------------------------------------------------- /test/helpers/SampleActions.js: -------------------------------------------------------------------------------- 1 | import alt from './alt' 2 | 3 | export default alt.generateActions('fire') 4 | -------------------------------------------------------------------------------- /examples/todomvc/js/alt.js: -------------------------------------------------------------------------------- 1 | var Alt = require('../../../') 2 | var alt = new Alt() 3 | 4 | module.exports = alt 5 | -------------------------------------------------------------------------------- /web/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.1.2' 3 | 4 | gem 'rake' 5 | gem 'jekyll' 6 | gem 'sass' 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | TODO 2 | bower.json 3 | coverage 4 | examples 5 | npm-debug.log 6 | src 7 | test/browser/tests.js 8 | web 9 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/variable/alt/master/examples/todomvc/todomvc-common/bg.png -------------------------------------------------------------------------------- /examples/weathertabs/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/variable/alt/master/examples/weathertabs/images/loading.gif -------------------------------------------------------------------------------- /test/babel/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/external-helpers') 2 | require('babel/register-without-polyfill')({ 3 | stage: 0 4 | }) 5 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Bower component for some common utilities we use in every app 4 | 5 | 6 | ## License 7 | 8 | MIT 9 | -------------------------------------------------------------------------------- /test/helpers/SaaM.js: -------------------------------------------------------------------------------- 1 | export const displayName = 'SaaM' 2 | 3 | export const state = 1 4 | 5 | export function reduce(state, payload) { 6 | return state + 1 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | coverage 3 | node_modules 4 | npm-debug.log 5 | test/browser/tests.js 6 | lib 7 | utils/* 8 | flux.js 9 | flux-build.js 10 | flux-build.min.js 11 | -------------------------------------------------------------------------------- /src/utils/chromeDebug.js: -------------------------------------------------------------------------------- 1 | /*global window*/ 2 | export default function chromeDebug(alt) { 3 | if (typeof window !== 'undefined') window['alt.js.org'] = alt 4 | return alt 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: npm run lint && npm run coverage 3 | after_script: cat ./coverage/lcov.info | coveralls 4 | node_js: 5 | - "0.10" 6 | - "0.12" 7 | - iojs-v1.8.1 8 | -------------------------------------------------------------------------------- /web/_includes/guidenav.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /web/_includes/guidesnav.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /web/_includes/sidenav.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /examples/todomvc/js/app.js: -------------------------------------------------------------------------------- 1 | var TodoApp = require('./components/TodoApp.react') 2 | var React = require('react') 3 | React.render( 4 | React.createElement(TodoApp, {}), 5 | document.getElementById('todoapp') 6 | ) 7 | -------------------------------------------------------------------------------- /examples/weathertabs/js/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var App = require('./components/App.jsx'); 3 | 4 | React.render( 5 | React.createElement(App), 6 | document.getElementById('weather-tabs') 7 | ); 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /examples/chat/js/actions/ChatThreadActionCreators.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | class ChatThreadActions { 4 | constructor() { 5 | this.generateActions('clickThread') 6 | } 7 | } 8 | 9 | module.exports = alt.createActions(ChatThreadActions) 10 | -------------------------------------------------------------------------------- /src/utils/makeHot.js: -------------------------------------------------------------------------------- 1 | function makeHot(alt, Store, name = Store.displayName) { 2 | if (module.hot) { 3 | module.hot.dispose(() => { 4 | delete alt.stores[name] 5 | }) 6 | } 7 | 8 | return alt.createStore(Store, name) 9 | } 10 | 11 | export default makeHot 12 | -------------------------------------------------------------------------------- /examples/chat/js/actions/ChatServerActionCreators.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | class ChatServerActions { 4 | constructor() { 5 | this.generateActions('receiveCreatedMessage', 'receiveAll') 6 | } 7 | } 8 | 9 | module.exports = alt.createActions(ChatServerActions) 10 | -------------------------------------------------------------------------------- /web/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 | {{ content }} 12 |
13 | 14 | {% include footer.html %} 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/weathertabs/js/actions/AppActions.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | module.exports = alt.createActions(class AppActions { 4 | constructor() { 5 | this.generateActions( 6 | 'setManager', 7 | 'addLocation', 8 | 'setLocation', 9 | 'deleteLocation' 10 | ); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /src/utils/debug/DebugActions.js: -------------------------------------------------------------------------------- 1 | import alt from './alt' 2 | 3 | export default alt.generateActions( 4 | 'addDispatch', 5 | 'clear', 6 | 'loadRecording', 7 | 'replay', 8 | 'revert', 9 | 'saveRecording', 10 | 'selectData', 11 | 'setAlt', 12 | 'startReplay', 13 | 'stopReplay', 14 | 'togglePauseReplay', 15 | 'toggleRecording' 16 | ) 17 | -------------------------------------------------------------------------------- /src/utils/statics.js: -------------------------------------------------------------------------------- 1 | function addStatics(obj) { 2 | return (Component) => { 3 | Object.keys(obj).forEach((key) => { 4 | Component[key] = obj[key] 5 | }) 6 | return Component 7 | } 8 | } 9 | 10 | export default function statics(obj, Component) { 11 | return Component 12 | ? addStatics(obj)(Component) 13 | : addStatics(obj) 14 | } 15 | -------------------------------------------------------------------------------- /web/scripts/search.js: -------------------------------------------------------------------------------- 1 | import SearchApp from 'alt-search-docs' 2 | import React from 'react' 3 | 4 | const github = /goatslacker.github.io\/alt/ 5 | 6 | if (github.test(location.href)) { 7 | location.href = location.href.replace(github, 'alt.js.org') 8 | } 9 | 10 | React.render( 11 | , 12 | document.getElementById('alt-search-app') 13 | ) 14 | -------------------------------------------------------------------------------- /examples/todomvc/js/actions/TodoActions.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | class TodoActions { 4 | constructor() { 5 | this.generateActions( 6 | 'create', 7 | 'updateText', 8 | 'toggleComplete', 9 | 'toggleCompleteAll', 10 | 'destroy', 11 | 'destroyCompleted' 12 | ) 13 | } 14 | } 15 | 16 | module.exports = alt.createActions(TodoActions) 17 | -------------------------------------------------------------------------------- /test/helpers/SampleApp2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import IsomorphicRenderer from '../../utils/IsomorphicRenderer' 3 | import Alt from '../../dist/alt-with-runtime' 4 | 5 | const alt = new Alt() 6 | 7 | const App = React.createClass({ 8 | render() { 9 | return ( 10 |
chillin
11 | ) 12 | } 13 | }) 14 | 15 | export default IsomorphicRenderer(alt, App) 16 | -------------------------------------------------------------------------------- /examples/chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flux • Chat 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | 7 |
8 |

{{ page.title }}

9 |

{{ page.date | date: "%b %-d, %Y" }}{% if page.author %} • {{ page.author }}{% endif %}{% if page.meta %} • {{ page.meta }}{% endif %}

10 |
11 | 12 |
13 | {{ content }} 14 |
15 | -------------------------------------------------------------------------------- /mixins/IsomorphicMixin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { create: createIsomorphicMixin } 3 | 4 | function createIsomorphicMixin(alt) { 5 | return { 6 | componentWillMount: function () { 7 | if (!this.props.altStores) { 8 | throw new ReferenceError( 9 | 'altStores was not provided as a property to the react element' 10 | ) 11 | } 12 | alt.bootstrap(JSON.stringify(this.props.altStores)) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/withAltContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function withAltContext(flux) { 4 | return function (Component) { 5 | return React.createClass({ 6 | childContextTypes: { 7 | flux: React.PropTypes.object 8 | }, 9 | 10 | getChildContext() { 11 | return { flux } 12 | }, 13 | 14 | render() { 15 | return React.createElement(Component, this.props) 16 | } 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/weathertabs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AltManager - Weather Tabs App 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/prepare.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Prepare 4 | description: Prepare a payload for bootstrapping 5 | permalink: /docs/prepare/ 6 | --- 7 | 8 | # prepare 9 | 10 | > (Store: AltStore, payload: mixed): string 11 | 12 | Given a store and a payload this functions returns a serialized string you can use to bootstrap that particular store. 13 | 14 | ```js 15 | const data = alt.prepare(TodoStore, { 16 | todos: [{ 17 | text: 'Buy some milk' 18 | ]} 19 | }); 20 | 21 | alt.bootstrap(data); 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/chat/js/actions/ChatMessageActionCreators.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | var ChatWebAPIUtils = require('../utils/ChatWebAPIUtils') 4 | var ChatMessageDataUtils = require('../utils/ChatMessageDataUtils') 5 | 6 | class ChatMessageActions { 7 | createMessage(text) { 8 | this.dispatch(text) 9 | 10 | var message = ChatMessageDataUtils.getCreatedMessageData(text) 11 | ChatWebAPIUtils.createMessage(message) 12 | } 13 | } 14 | 15 | module.exports = alt.createActions(ChatMessageActions) 16 | -------------------------------------------------------------------------------- /components/AltNativeContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AltNativeContainer. 3 | * 4 | * @see AltContainer 5 | */ 6 | var React = require('react-native') 7 | var mixinContainer = require('./mixinContainer') 8 | var assign = require('../utils/functions').assign 9 | 10 | var AltNativeContainer = React.createClass(assign({ 11 | displayName: 'AltNativeContainer', 12 | 13 | render: function () { 14 | return this.altRender(React.View) 15 | } 16 | }, mixinContainer(React))) 17 | 18 | module.exports = AltNativeContainer 19 | -------------------------------------------------------------------------------- /examples/chat/js/utils/ChatMessageDataUtils.js: -------------------------------------------------------------------------------- 1 | var ThreadStore = require('../stores/ThreadStore') 2 | 3 | class Chatmessage2Utils { 4 | static getCreatedMessageData(text) { 5 | var timestamp = Date.now() 6 | return { 7 | id: 'm_' + timestamp, 8 | threadID: ThreadStore.getCurrentID(), 9 | authorName: 'Bill', // hard coded for the example 10 | date: new Date(timestamp), 11 | text: text, 12 | isRead: true 13 | } 14 | } 15 | } 16 | 17 | module.exports = Chatmessage2Utils 18 | -------------------------------------------------------------------------------- /test/statics-test.js: -------------------------------------------------------------------------------- 1 | import Alt from '../' 2 | import statics from '../utils/statics' 3 | import { Component } from 'react' 4 | import { assert } from 'chai' 5 | 6 | const alt = new Alt() 7 | 8 | @statics({ 9 | a() { } 10 | }) 11 | class Foo extends Component { } 12 | 13 | const Bar = statics({ b() { } }, Foo) 14 | 15 | export default { 16 | 'statics': { 17 | 'static methods are added to a component'() { 18 | assert.isFunction(Bar.a, 'works as decorator') 19 | assert.isFunction(Bar.b, 'works as function') 20 | } 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /web/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/utils/debug/ViewerStore.js: -------------------------------------------------------------------------------- 1 | import alt from './alt' 2 | import DebugActions from './DebugActions' 3 | 4 | export default alt.createStore(class { 5 | static displayName = 'ViewerStore' 6 | 7 | static config = { 8 | getState(state) { 9 | return { 10 | selectedData: state.selectedData 11 | } 12 | } 13 | } 14 | 15 | constructor() { 16 | this.selectedData = {} 17 | 18 | this.bindActions(DebugActions) 19 | } 20 | 21 | selectData(data) { 22 | this.selectedData = data 23 | console.log(data) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/utils/functions.js: -------------------------------------------------------------------------------- 1 | export const isFunction = x => typeof x === 'function' 2 | 3 | export function isPromise(obj) { 4 | return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' 5 | } 6 | 7 | export function eachObject(f, o) { 8 | o.forEach((from) => { 9 | Object.keys(Object(from)).forEach((key) => { 10 | f(key, from[key]) 11 | }) 12 | }) 13 | } 14 | 15 | export function assign(target, ...source) { 16 | eachObject((key, value) => target[key] = value, source) 17 | return target 18 | } 19 | -------------------------------------------------------------------------------- /dist.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | context: __dirname + '/src', 3 | entry: { 4 | 'alt': ['./alt/index.js'], 5 | 'alt-with-addons': ['./alt/addons.js'] 6 | }, 7 | output: { 8 | path: __dirname + '/dist', 9 | filename: '[name].js', 10 | library: 'Alt', 11 | libraryTarget: 'umd' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.js$/, 16 | loader: 'babel', 17 | exclude: /node_modules/ 18 | }] 19 | }, 20 | externals: { 21 | 'react': 'react', 22 | 'react/addons': 'react/addons' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/ImmutableUtil.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable' 2 | 3 | function immutable(StoreModel) { 4 | StoreModel.config = { 5 | setState(currentState, nextState) { 6 | this.state = nextState 7 | return this.state 8 | }, 9 | 10 | getState(currentState) { 11 | return currentState 12 | }, 13 | 14 | onSerialize(state) { 15 | return state.toJS() 16 | }, 17 | 18 | onDeserialize(data) { 19 | return Immutable.fromJS(data) 20 | } 21 | } 22 | 23 | return StoreModel 24 | } 25 | 26 | export default immutable 27 | -------------------------------------------------------------------------------- /dist.min.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | context: __dirname + '/src', 3 | entry: { 4 | 'alt': ['./alt/index.js'], 5 | 'alt-with-addons': ['./alt/addons.js'] 6 | }, 7 | output: { 8 | path: __dirname + '/dist', 9 | filename: '[name].min.js', 10 | library: 'Alt', 11 | libraryTarget: 'umd' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.js$/, 16 | loader: 'babel', 17 | exclude: /node_modules/ 18 | }] 19 | }, 20 | externals: { 21 | 'react': 'react', 22 | 'react/addons': 'react/addons' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /web/_layouts/docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 |
13 | {% include sidenav.html %} 14 |
15 |
16 |
17 | {{ content }} 18 |
19 |
20 |
21 |
22 | 23 | {% include footer.html %} 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/_layouts/guide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 |
13 | {% include guidenav.html %} 14 |
15 |
16 |
17 | {{ content }} 18 |
19 |
20 |
21 |
22 | 23 | {% include footer.html %} 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/rollback.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Rollback 4 | description: Reset stores back to previous state 5 | permalink: /docs/rollback/ 6 | --- 7 | 8 | # rollback 9 | 10 | > (): undefined 11 | 12 | If you've screwed up the state, or you just feel like rolling back you can call `alt.rollback()`. Rollback is pretty dumb in the sense that it's not automatic in case of errors, and it only rolls back to the last saved snapshot, meaning you have to save a snapshot first in order to roll back. 13 | 14 | ```js 15 | // reset state to last saved snapshot 16 | alt.rollback(); 17 | ``` 18 | -------------------------------------------------------------------------------- /web/_layouts/guides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 |
13 | {% include guidesnav.html %} 14 |
15 |
16 |
17 | {{ content }} 18 |
19 |
20 |
21 |
22 | 23 | {% include footer.html %} 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/recycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Recycle 4 | description: Reset stores back to original state 5 | permalink: /docs/recycle/ 6 | --- 7 | 8 | # recycle 9 | 10 | > (...storeNames: ?string|AltStore): undefined 11 | 12 | If you wish to reset a particular, or all, store's state back to their original initial state you would call `recycle`. Recycle takes a splat of stores you would like reset. If no argument is provided then all stores are reset. 13 | 14 | ```js 15 | // recycle just MyStore 16 | alt.recycle(MyStore); 17 | 18 | // recycle all stores 19 | alt.recycle(); 20 | ``` 21 | -------------------------------------------------------------------------------- /mixins/Subscribe.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Subscribe = { 4 | create: function (context) { 5 | context._AltMixinRegistry = context._AltMixinRegistry || [] 6 | }, 7 | 8 | add: function (context, store, handler) { 9 | context._AltMixinRegistry.push(store.listen(handler)) 10 | }, 11 | 12 | destroy: function (context) { 13 | context._AltMixinRegistry.forEach(function (f) { f() }) 14 | context._AltMixinRegistry = [] 15 | }, 16 | 17 | listeners: function (context) { 18 | return context._AltMixinRegistry 19 | } 20 | } 21 | 22 | module.exports = Subscribe 23 | -------------------------------------------------------------------------------- /test/helpers/SampleApp.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import IsomorphicRenderer from '../../utils/IsomorphicRenderer' 3 | import Alt from '../../dist/alt-with-runtime' 4 | 5 | const alt = new Alt() 6 | 7 | const FooStore = alt.createStore({ 8 | displayName: 'FooStore', 9 | state: { test: 'hello' } 10 | }) 11 | 12 | const App = React.createClass({ 13 | getInitialState() { 14 | return FooStore.getState() 15 | }, 16 | 17 | render() { 18 | return ( 19 |
{this.state.test}
20 | ) 21 | } 22 | }) 23 | 24 | export default IsomorphicRenderer(alt, App) 25 | -------------------------------------------------------------------------------- /examples/todomvc/css/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * base.css overrides 10 | */ 11 | 12 | /** 13 | * We are not changing from display:none, but rather re-rendering instead. 14 | * Therefore this needs to be displayed normally by default. 15 | */ 16 | #todo-list li .edit { 17 | display: inline; 18 | } 19 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 8 | 9 | 10 | 11 |
12 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/chat/README.md: -------------------------------------------------------------------------------- 1 | ## Flux Chat Example 2 | 3 | This is an example application we've created to show an example of how a Flux 4 | app is structured, and how you might use waitFor to make sure the Stores' 5 | registered callbacks are called in the correct order. 6 | 7 | ## Running 8 | 9 | You must have [npm](https://www.npmjs.org/) installed on your computer. 10 | From the root project directory run these commands from the command line: 11 | 12 | `npm install` 13 | 14 | This will install all dependencies. 15 | 16 | To build the project, first run this command: 17 | 18 | `npm run build` 19 | 20 | Open `index.html` in your browser to view the app. 21 | 22 | -------------------------------------------------------------------------------- /examples/weathertabs/js/stores/WeatherStore.js: -------------------------------------------------------------------------------- 1 | class WeatherStore { 2 | constructor(alt) { 3 | this.bindActions(alt.getActions('WeatherActions')); 4 | this.loading = false; 5 | this.weather = null; 6 | this.showRaw = false; 7 | this.location = null; 8 | } 9 | 10 | loadWeather(location) { 11 | this.location = location; 12 | this.loading = true; 13 | } 14 | 15 | setLoading(isLoading) { 16 | this.loading = isLoading; 17 | } 18 | 19 | setWeather(weather) { 20 | this.weather = weather; 21 | } 22 | 23 | showRaw(showRaw) { 24 | this.showRaw = showRaw; 25 | } 26 | } 27 | 28 | module.exports = WeatherStore; 29 | -------------------------------------------------------------------------------- /test/store-as-a-module.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import Alt from '../' 3 | 4 | import * as StoreModel from './helpers/SaaM' 5 | 6 | const alt = new Alt() 7 | const actions = alt.generateActions('increment') 8 | const store = alt.createStore(StoreModel) 9 | 10 | export default { 11 | 'Stores as a Module': { 12 | 'store state is there'() { 13 | assert.equal(store.getState(), 1, 'store data is initialized to 1') 14 | 15 | actions.increment() 16 | 17 | assert.equal(store.getState(), 2, 'store data was updated') 18 | 19 | actions.increment() 20 | 21 | assert.equal(store.getState(), 3, 'incremented again') 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/weathertabs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weather-tabs", 3 | "version": "0.0.1", 4 | "description": "Example Alt/Flux app using AltManager to display weather", 5 | "repository": "https://github.com/goatslacker/alt", 6 | "main": "js/app.js", 7 | "dependencies": { 8 | "jquery": "^2.1.3", 9 | "react": "^0.12.2", 10 | "babelify": "^5.0.3", 11 | "browserify": "~4.2.2" 12 | }, 13 | "scripts": { 14 | "preinstall": "cd ../.. ; npm install", 15 | "build": "browserify js/app.js -t babelify --outfile js/bundle.js", 16 | "clean": "rm -R ./node_modules; npm install; npm run build" 17 | }, 18 | "author": "Alvin Sng " 19 | } 20 | -------------------------------------------------------------------------------- /test/stores-get-alt.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import Alt from '../dist/alt-with-runtime' 3 | 4 | const alt = new Alt() 5 | 6 | export default { 7 | 'the stores get the alt instance'() { 8 | class MyStore { 9 | constructor(alt) { 10 | assert.instanceOf(alt, Alt, 'alt is an instance of Alt') 11 | } 12 | } 13 | 14 | alt.createStore(MyStore, 'MyStore', alt) 15 | }, 16 | 17 | 'the actions get the alt instance'() { 18 | class MyActions { 19 | constructor(alt) { 20 | assert.instanceOf(alt, Alt, 'alt is an instance of Alt') 21 | } 22 | } 23 | 24 | alt.createActions(MyActions, undefined, alt) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mixins/ListenerMixin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var Subscribe = require('./Subscribe') 3 | 4 | var ListenerMixin = { 5 | componentWillMount: function () { 6 | Subscribe.create(this) 7 | }, 8 | 9 | componentWillUnmount: function () { 10 | Subscribe.destroy(this) 11 | }, 12 | 13 | listenTo: function (store, handler) { 14 | Subscribe.add(this, store, handler) 15 | }, 16 | 17 | listenToMany: function (stores, handler) { 18 | stores.forEach(function (store) { 19 | this.listenTo(store, handler) 20 | }, this) 21 | }, 22 | 23 | getListeners: function () { 24 | return Subscribe.listeners(this) 25 | } 26 | } 27 | 28 | module.exports = ListenerMixin 29 | -------------------------------------------------------------------------------- /test/async-action-test.js: -------------------------------------------------------------------------------- 1 | import Alt from '../' 2 | import { assert } from 'chai' 3 | 4 | const alt = new Alt() 5 | 6 | const actions = alt.createActions(class AsyncActions { 7 | displayName = 'AsyncActions' 8 | fetch() { 9 | return Promise.resolve('foo') 10 | } 11 | }) 12 | 13 | const store = alt.createStore(class FooStore { 14 | displayName = 'FooStore' 15 | constructor() { 16 | this.dispatched = false 17 | this.bindActions(actions) 18 | } 19 | onFetch() { 20 | this.dispatched = true 21 | } 22 | }) 23 | 24 | export default { 25 | 'async actions'() { 26 | actions.fetch() 27 | assert(store.state.dispatched === false, 'async action is not automatically dispatched') 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/todomvc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Alt - TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Double-click to edit a todo

13 |

Alt example created by Josh Perez

14 |

View components created by Bill Fisher

15 |

Part of TodoMVC

16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/blog.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Blog 4 | description: Web log for the alt project 5 | permalink: /blog/ 6 | --- 7 | 8 |
9 | 10 |
11 |

Posts

12 |

subscribe via RSS

13 |
14 | 15 |
    16 | {% for post in site.posts %} 17 |
  • 18 |
    19 | {{ post.date | date: "%b %-d, %Y" }} 20 |

    21 | {{ post.title }} 22 |

    23 |
    24 |
  • 25 | {% endfor %} 26 |
27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /src/utils/debug/AltStore.js: -------------------------------------------------------------------------------- 1 | import alt from './alt' 2 | import DebugActions from './DebugActions' 3 | 4 | export default alt.createStore(class { 5 | static displayName = 'AltStore' 6 | 7 | static config = { 8 | getState(state) { 9 | return { 10 | stores: state.stores 11 | } 12 | } 13 | } 14 | 15 | constructor() { 16 | this.alt = null 17 | this.stores = [] 18 | 19 | this.bindActions(DebugActions) 20 | 21 | this.exportPublicMethods({ 22 | alt: () => this.alt, 23 | stores: () => this.stores, 24 | }) 25 | } 26 | 27 | setAlt(altInst) { 28 | this.alt = altInst 29 | this.stores = Object.keys(this.alt.stores).map((name) => { 30 | return this.alt.stores[name] 31 | }) 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /examples/todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-alt", 3 | "version": "1.0.0", 4 | "description": "Example Flux architecture using alt.", 5 | "repository": "https://github.com/goatslacker/alt", 6 | "main": "js/app.js", 7 | "dependencies": { 8 | "object-assign": "^2.0.0", 9 | "react": "^0.12.2" 10 | }, 11 | "devDependencies": { 12 | "babelify": "^5.0.3", 13 | "browserify": "~4.2.2" 14 | }, 15 | "scripts": { 16 | "preinstall" : "cd ../.. ; npm install", 17 | "build": "browserify . > js/bundle.js", 18 | "test": "jest" 19 | }, 20 | "author": "Josh Perez ", 21 | "browserify": { 22 | "transform": [ 23 | "babelify" 24 | ] 25 | }, 26 | "jest": { 27 | "rootDir": "./js" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: path.join(__dirname, 'scripts', 'search.js'), 6 | output: { 7 | filename: path.join(__dirname, 'assets', 'search.js') 8 | }, 9 | module: { 10 | loaders: [ 11 | { test: /\.js$/, 12 | include: [ 13 | path.join(__dirname, 'scripts'), 14 | path.join(__dirname, '..', 'node_modules', 'alt-search-docs', 'src'), 15 | path.join(__dirname, '..', 'node_modules', 'alt-search-docs', 'node_modules/react-text-highlight/src'), 16 | ], 17 | loader: 'babel-loader?stage=0' 18 | }, 19 | { test: /\.css$/, 20 | loader: 'style-loader!css-loader' 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/takeSnapshot.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Snapshots 4 | description: Take a snapshot of your entire application's state 5 | permalink: /docs/takeSnapshot/ 6 | --- 7 | 8 | # takeSnapshot 9 | 10 | > (...storeNames: ?string|AltStore): string 11 | 12 | Take snapshot provides you with the entire application's state serialized to JSON, by default, but you may also pass in stores to take a snapshot of a subset of the application's state. 13 | 14 | Snapshots are a core component of alt. The idea is that at any given point in time you can `takeSnapshot` and have your entire application's state 15 | serialized for persistence, transferring, logging, or debugging. 16 | 17 | ```js 18 | var snapshot = alt.takeSnapshot(); 19 | var partialSnapshot = alt.takeSnapshot(Store1, Store3); 20 | ``` 21 | -------------------------------------------------------------------------------- /src/alt/addons.js: -------------------------------------------------------------------------------- 1 | import Alt from './' 2 | 3 | import ActionListeners from '../utils/ActionListeners' 4 | import AltManager from '../utils/AltManager' 5 | import DispatcherRecorder from '../utils/DispatcherRecorder' 6 | 7 | import atomic from '../utils/atomic' 8 | import connectToStores from '../utils/connectToStores' 9 | import chromeDebug from '../utils/chromeDebug' 10 | import makeFinalStore from '../utils/makeFinalStore' 11 | import withAltContext from '../utils/withAltContext' 12 | 13 | import AltContainer from '../../AltContainer' 14 | 15 | Alt.addons = { 16 | ActionListeners, 17 | AltContainer, 18 | AltManager, 19 | DispatcherRecorder, 20 | atomic, 21 | chromeDebug, 22 | connectToStores, 23 | makeFinalStore, 24 | withAltContext 25 | } 26 | 27 | export default Alt 28 | -------------------------------------------------------------------------------- /src/utils/Debugger.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | import DebugActions from './debug/DebugActions' 3 | import DispatcherDebugger from './DispatcherDebugger' 4 | import React from 'react' 5 | import StoreExplorer from './StoreExplorer' 6 | 7 | class Debugger extends React.Component { 8 | componentDidMount() { 9 | DebugActions.setAlt(this.props.alt) 10 | } 11 | 12 | renderInspectorWindow() { 13 | return this.props.inspector 14 | ? 15 | : null 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 |

Debug

22 | 23 | 24 | {this.renderInspectorWindow()} 25 |
26 | ) 27 | } 28 | } 29 | 30 | export default Debugger 31 | -------------------------------------------------------------------------------- /test/debug-alt-test.js: -------------------------------------------------------------------------------- 1 | import Alt from '../' 2 | import { assert } from 'chai' 3 | 4 | export default { 5 | 'debug mode': { 6 | beforeEach() { 7 | global.window = {} 8 | }, 9 | 10 | 'enable debug mode'() { 11 | const alt = new Alt() 12 | Alt.debug('an identifier', alt) 13 | 14 | assert.isArray(global.window['alt.js.org']) 15 | assert(global.window['alt.js.org'].length === 1) 16 | assert.isString(global.window['alt.js.org'][0].name) 17 | assert(global.window['alt.js.org'][0].alt === alt) 18 | }, 19 | 20 | afterEach() { 21 | delete global.window 22 | } 23 | }, 24 | 25 | 'isomorphic debug mode': { 26 | 'enable debug mode does not make things explode'() { 27 | const alt = new Alt() 28 | Alt.debug(alt) 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/weathertabs/js/stores/AppStore.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt'); 2 | 3 | var AppActions = require('../actions/AppActions'); 4 | 5 | module.exports = alt.createStore(class AppStore { 6 | constructor() { 7 | this.bindActions(AppActions); 8 | this.location = null; 9 | this.manager = null; 10 | } 11 | 12 | setManager(manager) { 13 | this.manager = manager; 14 | } 15 | 16 | addLocation(location) { 17 | this.manager.getOrCreate(location); 18 | this.location = location; 19 | } 20 | 21 | setLocation(location) { 22 | this.location = location; 23 | } 24 | 25 | deleteLocation(location) { 26 | this.manager.delete(location); 27 | this.location = null; 28 | for (var i in this.manager.all()) { 29 | this.location = i; 30 | break; 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/utils/AltIso.js: -------------------------------------------------------------------------------- 1 | import Iso from 'iso' 2 | import * as Render from './Render' 3 | 4 | export default { 5 | define: Render.withData, 6 | 7 | render(alt, Component, props) { 8 | if (typeof window === 'undefined') { 9 | return Render.toString(alt, Component, props).then((obj) => { 10 | return { 11 | html: Iso.render(obj.html, obj.state, { iso: 1 }) 12 | } 13 | }).catch((err) => { 14 | // return the empty markup in html when there's an error 15 | return { 16 | err, 17 | html: Iso.render() 18 | } 19 | }) 20 | } else { 21 | Iso.bootstrap((state, meta, node) => { 22 | alt.bootstrap(state) 23 | Render.toDOM(Component, props, node, meta.iso) 24 | }) 25 | return Promise.resolve() 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/flush.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Flush 4 | description: Reset stores back to initial state 5 | permalink: /docs/flush/ 6 | --- 7 | 8 | # flush 9 | 10 | > (): string 11 | 12 | Flush takes a snapshot of the current application state and then resets all the stores back to their original initial state. This is useful if you're using alt stores as singletons and doing server side rendering. 13 | 14 | In this particular scenario you would load the data in via `bootstrap` and then use `flush` once the view markup has been created. This makes sure that your components have the proper data and then resets your stores so they are ready for the next request. This is all not effected by concurrency since bootstrap, flush, and render are all synchronous processes. 15 | 16 | ```js 17 | const applicationState = alt.flush(); 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/weathertabs/README.md: -------------------------------------------------------------------------------- 1 | ## AltManager example: Weather Tabs App 2 | 3 | This is an example React/Flux/Alt app that shows the use of AltManager, a 4 | utility class for Alt.js. AltManager allows for multiple alt instances which is 5 | necessary to build apps that encapsulates a mini app inside of a large app. In 6 | this example we have a simple weather searcher. Each search you make will 7 | create a new tab which itself is a new Alt instance and has its own internal 8 | store & actions. Whatever you do in the alt instance is persisted even after 9 | you move to another tab. You can delete tabs which will delete that alt instance 10 | 11 | ## Install 12 | 1) run `npm run clean` 13 | 2) open the index.html file 14 | 15 | ## Docs 16 | See /utils/AltManager.js for details on how to use AltManager.js 17 | See /mixins/AltManagerMixin.js for details on mixin usage. 18 | -------------------------------------------------------------------------------- /examples/weathertabs/js/actions/WeatherActions.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | class WeatherActions { 4 | constructor() { 5 | this.generateActions( 6 | 'setLoading', 7 | 'setWeather', 8 | 'showRaw' 9 | ); 10 | } 11 | 12 | loadWeather(location) { 13 | this.dispatch(location); 14 | $.ajax('http://api.openweathermap.org/data/2.5/weather', { 15 | crossDomain: true, 16 | data: { q: location }, 17 | success: (data) => { 18 | // we put in a fake delay here to show the loading icon 19 | setTimeout(() => { 20 | if (data.weather) { 21 | this.actions.setWeather(data); 22 | } 23 | this.actions.setLoading(false); 24 | }, 500); 25 | }, 26 | error: () => { 27 | this.actions.setLoading(false); 28 | } 29 | }); 30 | } 31 | } 32 | 33 | module.exports = WeatherActions; 34 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alt", 3 | "version": "0.17.1", 4 | "homepage": "https://github.com/goatslacker/alt", 5 | "authors": [ 6 | "Josh Perez " 7 | ], 8 | "description": "Alt is a flux implementation that is small (~4.3kb & 400 LOC), well tested, terse, insanely flexible, and forward thinking.", 9 | "main": "dist/alt.js", 10 | "devDependencies": { 11 | "babel": "^4.0.1", 12 | "coveralls": "^2.11.2", 13 | "istanbul": "^0.3.5", 14 | "mocha": "^2.1.0" 15 | }, 16 | "keywords": [ 17 | "alt", 18 | "es6", 19 | "flow", 20 | "flux", 21 | "react", 22 | "unidirectional" 23 | ], 24 | "license": "MIT", 25 | "ignore": [ 26 | "**/.*", 27 | "node_modules", 28 | "bower_components", 29 | "test", 30 | "tests", 31 | "examples", 32 | "src", 33 | "coverage-test.js", 34 | "coverage", 35 | "TODO" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /test/store-transforms-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import Alt from '../dist/alt-with-runtime' 3 | 4 | const alt = new Alt() 5 | 6 | alt.storeTransforms.push(function (Store) { 7 | Store.test = 'hello' 8 | return Store 9 | }) 10 | 11 | class Store { 12 | constructor() { 13 | this.x = 0 14 | } 15 | } 16 | 17 | class Store2 { 18 | constructor() { 19 | this.y = 0 20 | } 21 | } 22 | 23 | export default { 24 | 'store transforms': { 25 | 'when creating stores alt goes through its series of transforms'() { 26 | const store = alt.createStore(Store) 27 | assert(alt.storeTransforms.length === 1) 28 | assert.isDefined(store.test) 29 | assert(store.test === 'hello', 'store that adds hello to instance transform') 30 | }, 31 | 32 | 'unsaved stores get the same treatment'() { 33 | const store2 = alt.createUnsavedStore(Store) 34 | assert.isDefined(store2.test) 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/atomic.js: -------------------------------------------------------------------------------- 1 | import makeFinalStore from './makeFinalStore' 2 | import { isFunction } from './functions' 3 | 4 | function makeAtomicClass(alt, StoreModel) { 5 | class AtomicClass extends StoreModel { 6 | constructor() { 7 | super() 8 | this.on('error', () => alt.rollback()) 9 | } 10 | } 11 | AtomicClass.displayName = StoreModel.displayName || StoreModel.name 12 | return AtomicClass 13 | } 14 | 15 | function makeAtomicObject(alt, StoreModel) { 16 | StoreModel.lifecycle = StoreModel.lifecycle || {} 17 | StoreModel.lifecycle.error = () => { 18 | alt.rollback() 19 | } 20 | return StoreModel 21 | } 22 | 23 | export default function atomic(alt) { 24 | var finalStore = makeFinalStore(alt) 25 | 26 | finalStore.listen(() => alt.takeSnapshot()) 27 | 28 | return (StoreModel) => { 29 | return isFunction(StoreModel) 30 | ? makeAtomicClass(alt, StoreModel) 31 | : makeAtomicObject(alt, StoreModel) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/bootstrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Bootstrap 4 | description: Bootstrapping all your application state 5 | permalink: /docs/bootstrap/ 6 | --- 7 | 8 | # bootstrap 9 | 10 | > (data: string): undefined 11 | 12 | The `alt.bootstrap()` function takes in a snapshot you've saved and reloads every store's state with the data provided in that snapshot. 13 | 14 | Bootstrap is great if you're running an isomorphic app, or if you're persisting state to localstorage and then retrieving it on init later on. You can save a snapshot on the server side, send it down, and then bootstrap it back on the client. 15 | 16 | If you're bootstrapping then it is recommended you pass in a [unique Identifier to createStore](createStore.md#createstore), name of the class is good enough, to createStore so that it can be referenced later for bootstrapping. 17 | 18 | ```js 19 | alt.bootstrap(JSON.stringify({ 20 | MyStore: { 21 | key: 'value', 22 | key2: 'value2' 23 | } 24 | })); 25 | ``` 26 | -------------------------------------------------------------------------------- /web/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/utils/hotReload.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: Webpack Hot Module Replacement 4 | description: Hot reload on stores for use with webpack 5 | permalink: /docs/hot-reload/ 6 | --- 7 | 8 | # HMR (hot module replacement) 9 | 10 | When working with webpack and something like [react-hot-loader](https://github.com/gaearon/react-hot-loader) changing stores often causes your stores to be re-created (since they're wrapped) while this isn't a big deal it does cause a surplus of stores which pollutes the alt namespace and the alt debugger. With this util you're able to get proper hot replacement for stores, changing code within a store will reload it properly. 11 | 12 | To use this just export a hot store using `makeHot` rather than using `alt.createStore`. 13 | 14 | ```js 15 | import alt from '../alt'; 16 | import makeHot from 'alt/utils/makeHot'; 17 | 18 | class TodoStore { 19 | static displayName = 'TodoStore' 20 | 21 | constructor() { 22 | this.todos = {}; 23 | } 24 | } 25 | 26 | export default makeHot(alt, TodoStore); 27 | ``` 28 | -------------------------------------------------------------------------------- /test/reducer-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import Alt from '../' 3 | import sinon from 'sinon' 4 | import { combine, reduceWith } from '../utils/reducers' 5 | 6 | const alt = new Alt() 7 | 8 | const actions = alt.generateActions('fire', 'foo', 'bar') 9 | 10 | const store = alt.createStore({ 11 | state: 21, 12 | 13 | displayName: 'ValueStore', 14 | 15 | reduce: combine( 16 | reduceWith([actions.fire, actions.BAR], (state, payload) => { 17 | return state + 1 18 | }) 19 | ) 20 | }) 21 | 22 | export default { 23 | 'value stores': { 24 | beforeEach() { 25 | alt.recycle() 26 | }, 27 | 28 | 'reducer utils help ease the pain of switch statements'() { 29 | const spy = sinon.spy() 30 | const unlisten = store.listen(spy) 31 | 32 | actions.fire() 33 | actions.foo() 34 | actions.bar() 35 | 36 | assert(store.getState() === 23, 'state is correct') 37 | assert.ok(spy.calledTwice, 'spy was only called twice') 38 | 39 | unlisten() 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/chat/js/stores/__tests__/UnreadThreadStore-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../UnreadThreadStore'); 2 | 3 | describe('UnreadThreadStore', function() { 4 | 5 | var alt; 6 | var callback; 7 | var UnreadThreadStore; 8 | 9 | beforeEach(function() { 10 | alt = require('../../alt'); 11 | alt.dispatcher = { register: jest.genMockFunction() }; 12 | UnreadThreadStore = require('../UnreadThreadStore'); 13 | callback = alt.dispatcher.register.mock.calls[0][0]; 14 | }); 15 | 16 | it('registers a callback with the dispatcher', function() { 17 | expect(alt.dispatcher.register.mock.calls.length).toBe(1); 18 | }); 19 | 20 | it('provides the unread thread count', function() { 21 | var ThreadStore = require('../ThreadStore'); 22 | ThreadStore.getAll.mockReturnValueOnce( 23 | { 24 | foo: {lastMessage: {isRead: false}}, 25 | bar: {lastMessage: {isRead: false}}, 26 | baz: {lastMessage: {isRead: true}} 27 | } 28 | ); 29 | expect(UnreadThreadStore.getCount()).toBe(2); 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /examples/chat/js/utils/ChatMessageUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | module.exports = { 14 | 15 | convertRawMessage: function(rawMessage, currentThreadID) { 16 | return { 17 | id: rawMessage.id, 18 | threadID: rawMessage.threadID, 19 | authorName: rawMessage.authorName, 20 | date: new Date(rawMessage.timestamp), 21 | text: rawMessage.text, 22 | isRead: rawMessage.threadID === currentThreadID 23 | }; 24 | } 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /guides/getting-started/actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: guide 3 | title: Creating Actions 4 | description: How to create actions for managing state 5 | permalink: /guide/actions/ 6 | --- 7 | 8 | # Creating Actions 9 | 10 | The first actions we create will be simple, they'll take in an array of locations we'll pass in at the start of the application and just dispatch them to the store. 11 | 12 | We create an action by creating a class, the class' prototype methods will become the actions. The class syntax is completely optional you can use regular constructors and prototypes. 13 | 14 | Inside those actions you can use `this.dispatch` to dispatch your payload through the Dispatcher and onto the stores. Finally, make sure you export the created actions using `alt.createActions`. 15 | 16 | --- 17 | 18 | `actions/LocationActions.js` 19 | 20 | ```js 21 | var alt = require('../alt'); 22 | 23 | class LocationActions { 24 | updateLocations(locations) { 25 | this.dispatch(locations); 26 | } 27 | } 28 | 29 | module.exports = alt.createActions(LocationActions); 30 | ``` 31 | 32 | [Continue to next step...](store.md) 33 | -------------------------------------------------------------------------------- /examples/chat/js/components/ChatApp.react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | * 12 | * @jsx React.DOM 13 | */ 14 | 15 | var MessageSection = require('./MessageSection.react'); 16 | var React = require('react'); 17 | var ThreadSection = require('./ThreadSection.react'); 18 | 19 | var ChatApp = React.createClass({ 20 | 21 | render: function() { 22 | return ( 23 |
24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | }); 31 | 32 | module.exports = ChatApp; 33 | -------------------------------------------------------------------------------- /examples/chat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alt-chat", 3 | "version": "0.0.1", 4 | "description": "Example Flux chat application primarily intended to explain the use of Dispatcher.waitFor().", 5 | "repository": "https://github.com/goatslacker/alt", 6 | "main": "js/app.js", 7 | "dependencies": { 8 | "alt": "^0.13.5", 9 | "react": "^0.12.2" 10 | }, 11 | "devDependencies": { 12 | "babel-jest": "^4.0.0", 13 | "babelify": "^5.0.3", 14 | "browserify": "~4.2.2", 15 | "envify": "^3.2.0", 16 | "jest-cli": "^0.4.0", 17 | "uglify-js": "~2.4.15" 18 | }, 19 | "scripts": { 20 | "build": "NODE_ENV=production browserify . | uglifyjs -cm > js/bundle.min.js", 21 | "test": "jest" 22 | }, 23 | "authors": [ 24 | "Bill Fisher", 25 | "Josh Perez " 26 | ], 27 | "browserify": { 28 | "transform": [ 29 | "babelify", 30 | "envify" 31 | ] 32 | }, 33 | "jest": { 34 | "scriptPreprocessor": "../node_modules/babel-jest", 35 | "unmockedModulePathPatterns": [ 36 | "../node_modules/alt", 37 | "alt.js" 38 | ], 39 | "rootDir": "./js" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/AltTestingUtils.js: -------------------------------------------------------------------------------- 1 | import { assign } from './functions' 2 | 3 | const noop = function () { } 4 | 5 | const AltTestingUtils = { 6 | createStoreSpy: (alt) => { 7 | return { 8 | displayName: 'ALT_TEST_STORE', 9 | alt: alt, 10 | bindAction: noop, 11 | bindActions: noop, 12 | bindListeners: noop, 13 | dispatcher: alt.dispatcher, 14 | emitChange: noop, 15 | exportAsync: noop, 16 | exportPublicMethods: noop, 17 | getInstance: noop, 18 | on: noop, 19 | registerAsync: noop, 20 | setState: noop, 21 | waitFor: noop 22 | } 23 | }, 24 | 25 | makeStoreTestable(alt, UnwrappedStore) { 26 | const StorePrototype = AltTestingUtils.createStoreSpy(alt) 27 | class DerivedStore extends UnwrappedStore { 28 | constructor() { 29 | super() 30 | } 31 | } 32 | assign(DerivedStore.prototype, StorePrototype) 33 | return new DerivedStore() 34 | }, 35 | 36 | mockGetState(state = {}) { 37 | return { 38 | getState: () => { 39 | return state 40 | } 41 | } 42 | } 43 | } 44 | 45 | module.exports = AltTestingUtils 46 | -------------------------------------------------------------------------------- /examples/chat/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | * 12 | * @jsx React.DOM 13 | */ 14 | 15 | // This file bootstraps the entire application. 16 | 17 | var ChatApp = require('./components/ChatApp.react'); 18 | var ChatExampleData = require('./ChatExampleData'); 19 | var ChatWebAPIUtils = require('./utils/ChatWebAPIUtils'); 20 | var React = require('react'); 21 | 22 | ChatExampleData.init(); // load example data into localstorage 23 | 24 | ChatWebAPIUtils.getAllMessages(); 25 | 26 | React.render( 27 | , 28 | document.getElementById('react') 29 | ); 30 | -------------------------------------------------------------------------------- /examples/chat/js/stores/UnreadThreadStore.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | var ChatThreadActionCreators = require('../actions/ChatThreadActionCreators') 4 | var ChatServerActionCreators = require('../actions/ChatServerActionCreators') 5 | 6 | var MessageStore = require('../stores/MessageStore') 7 | var ThreadStore = require('../stores/ThreadStore') 8 | 9 | class UnreadThreadStore { 10 | constructor() { 11 | this.bindActions(ChatThreadActionCreators) 12 | this.bindActions(ChatServerActionCreators) 13 | } 14 | 15 | onClickThread(threadID) { 16 | this.wait() 17 | } 18 | 19 | onReceiveRawMessages(rawMessages) { 20 | this.wait() 21 | } 22 | 23 | wait() { 24 | this.waitFor([ 25 | ThreadStore.dispatchToken, 26 | MessageStore.dispatchToken 27 | ]) 28 | } 29 | 30 | static getCount() { 31 | var threads = ThreadStore.getAll() 32 | var unreadCount = 0 33 | for (var id in threads) { 34 | if (!threads[id].lastMessage.isRead) { 35 | unreadCount++ 36 | } 37 | } 38 | return unreadCount 39 | } 40 | } 41 | 42 | module.exports = alt.createStore(UnreadThreadStore, 'UnreadThreadStore') 43 | -------------------------------------------------------------------------------- /src/utils/reducers.js: -------------------------------------------------------------------------------- 1 | import { assign } from './functions' 2 | 3 | function getId(x) { 4 | return x.id || x 5 | } 6 | 7 | /* istanbul ignore next */ 8 | function shallowEqual(a, b) { 9 | if (typeof a !== 'object' || typeof b !== 'object') return a === b 10 | if (a === b) return true 11 | if (!a || !b) return false 12 | for (let k in a) { 13 | if (a.hasOwnProperty(k) && (!b.hasOwnProperty(k) || a[k] !== b[k])) { 14 | return false 15 | } 16 | } 17 | for (let k in b) { 18 | if (b.hasOwnProperty(k) && !a.hasOwnProperty(k)) { 19 | return false 20 | } 21 | } 22 | return true 23 | } 24 | 25 | export function combine(...restReducers) { 26 | const reducers = assign.apply(null, [{}].concat(restReducers)) 27 | return function (state, payload) { 28 | const newState = reducers.hasOwnProperty(payload.action) 29 | ? reducers[payload.action](state, payload.data) 30 | : state 31 | 32 | if (shallowEqual(state, newState)) this.preventDefault() 33 | 34 | return newState 35 | } 36 | } 37 | 38 | export function reduceWith(actions, reduce) { 39 | return actions.reduce((total, action) => { 40 | total[getId(action)] = reduce 41 | return total 42 | }, {}) 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/makeFinalStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * makeFinalStore(alt: AltInstance): AltStore 3 | * 4 | * > Creates a `FinalStore` which is a store like any other except that it 5 | * waits for all other stores in your alt instance to emit a change before it 6 | * emits a change itself. 7 | * 8 | * Want to know when a particular dispatch has completed? This is the util 9 | * you want. 10 | * 11 | * Good for: taking a snapshot and persisting it somewhere, saving data from 12 | * a set of stores, syncing data, etc. 13 | * 14 | * Usage: 15 | * 16 | * ```js 17 | * var FinalStore = makeFinalStore(alt); 18 | * 19 | * FinalStore.listen(function () { 20 | * // all stores have now changed 21 | * }); 22 | * ``` 23 | */ 24 | 25 | function FinalStore() { 26 | this.dispatcher.register((payload) => { 27 | const stores = Object.keys(this.alt.stores).reduce((arr, store) => { 28 | arr.push(this.alt.stores[store].dispatchToken) 29 | return arr 30 | }, []) 31 | 32 | this.waitFor(stores) 33 | this.setState({ payload: payload }) 34 | this.emitChange() 35 | }) 36 | } 37 | 38 | export default function makeFinalStore(alt) { 39 | return alt.FinalStore 40 | ? alt.FinalStore 41 | : (alt.FinalStore = alt.createUnsavedStore(FinalStore)) 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/IsomorphicRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * IsomorphicRenderer(alt: AltInstance, App: ReactElement): mixed 3 | * 4 | * > The glue that it takes to render a react element isomorphically 5 | * 6 | * ** This util depends on iso and react ** 7 | * 8 | * Usage: 9 | * 10 | * ```js 11 | * var IsomorphicRenderer = require('alt/utils/IsomorphicRenderer'); 12 | * var React = require('react'); 13 | * var Alt = require('alt'); 14 | * var alt = new Alt(); 15 | * 16 | * var App = React.createClass({ 17 | * render() { 18 | * return ( 19 | *
Hello World
20 | * ); 21 | * } 22 | * }); 23 | * 24 | * module.exports = IsomorphicRenderer(alt, App); 25 | * ``` 26 | */ 27 | 28 | import Iso from 'iso' 29 | import React from 'react' 30 | 31 | export default function IsomorphicRenderer(alt, App) { 32 | /*eslint-disable */ 33 | if (typeof window === 'undefined') { 34 | /*eslint-enable */ 35 | return () => { 36 | const app = React.renderToString(React.createElement(App)) 37 | const markup = Iso.render(app, alt.takeSnapshot()) 38 | alt.flush() 39 | return markup 40 | } 41 | } else { 42 | Iso.bootstrap((state, _, node) => { 43 | const app = React.createElement(App) 44 | alt.bootstrap(state) 45 | React.render(app, node) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/alt/utils/AltUtils.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | const builtIns = Object.getOwnPropertyNames(NoopClass) 3 | const builtInProto = Object.getOwnPropertyNames(NoopClass.prototype) 4 | /*eslint-enable*/ 5 | 6 | export function getInternalMethods(Obj, isProto) { 7 | const excluded = isProto ? builtInProto : builtIns 8 | const obj = isProto ? Obj.prototype : Obj 9 | return Object.getOwnPropertyNames(obj).reduce((value, m) => { 10 | if (excluded.indexOf(m) !== -1) { 11 | return value 12 | } 13 | 14 | value[m] = obj[m] 15 | return value 16 | }, {}) 17 | } 18 | 19 | export function warn(msg) { 20 | /* istanbul ignore else */ 21 | if (typeof console !== 'undefined') { 22 | console.warn(new ReferenceError(msg)) 23 | } 24 | } 25 | 26 | export function uid(container, name) { 27 | let count = 0 28 | let key = name 29 | while (Object.hasOwnProperty.call(container, key)) { 30 | key = name + String(++count) 31 | } 32 | return key 33 | } 34 | 35 | export function formatAsConstant(name) { 36 | return name.replace(/[a-z]([A-Z])/g, (i) => { 37 | return `${i[0]}_${i[1].toLowerCase()}` 38 | }).toUpperCase() 39 | } 40 | 41 | export function dispatchIdentity(x, ...a) { 42 | this.dispatch(a.length ? [x].concat(a) : x) 43 | } 44 | 45 | /* istanbul ignore next */ 46 | function NoopClass() { } 47 | -------------------------------------------------------------------------------- /examples/todomvc/js/components/Header.react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @jsx React.DOM 10 | */ 11 | 12 | var React = require('react'); 13 | var TodoActions = require('../actions/TodoActions'); 14 | var TodoTextInput = require('./TodoTextInput.react'); 15 | 16 | var Header = React.createClass({ 17 | 18 | /** 19 | * @return {object} 20 | */ 21 | render: function() { 22 | return ( 23 | 31 | ); 32 | }, 33 | 34 | /** 35 | * Event handler called within TodoTextInput. 36 | * Defining this here allows TodoTextInput to be used in multiple places 37 | * in different ways. 38 | * @param {string} text 39 | */ 40 | _onSave: function(text) { 41 | if (text.trim()){ 42 | TodoActions.create(text); 43 | } 44 | 45 | } 46 | 47 | }); 48 | 49 | module.exports = Header; 50 | -------------------------------------------------------------------------------- /src/utils/fp.js: -------------------------------------------------------------------------------- 1 | const { push } = Array.prototype 2 | 3 | // Disabling no-shadow so we can sanely curry 4 | /*eslint-disable no-shadow*/ 5 | export function map(fn, stores) { 6 | return stores 7 | ? stores.map(store => fn(store.state)) 8 | : stores => map(fn, stores) 9 | } 10 | 11 | export function filter(fn, stores) { 12 | return stores 13 | ? stores.filter(store => fn(store.state)) 14 | : stores => filter(fn, stores) 15 | } 16 | 17 | export function reduce(fn, stores, acc = {}) { 18 | return stores 19 | ? stores.reduce((acc, store) => fn(acc, store.state), acc) 20 | : stores => reduce(fn, stores) 21 | } 22 | 23 | export function flatMap(fn, stores) { 24 | if (!stores) return (stores) => flatMap(fn, stores) 25 | 26 | return stores.reduce((result, store) => { 27 | let value = fn(store.state) 28 | if (Array.isArray(value)) { 29 | push.apply(result, value) 30 | } else { 31 | result.push(value) 32 | } 33 | return result 34 | }, []) 35 | } 36 | 37 | export function zipWith(fn, a, b) { 38 | if (!a && !b) { 39 | return (a, b) => zipWith(fn, a, b) 40 | } 41 | 42 | const length = Math.min(a.length, b.length) 43 | const result = Array(length) 44 | for (let i = 0; i < length; i += 1) { 45 | result[i] = fn(a[i].state, b[i].state) 46 | } 47 | return result 48 | } 49 | -------------------------------------------------------------------------------- /test/stores-with-colliding-names.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import sinon from 'sinon' 3 | import Alt from '../dist/alt-with-runtime' 4 | 5 | const alt = new Alt() 6 | 7 | alt.createStore(function MyStore() { }) 8 | 9 | export default { 10 | 'console warn for missing identifier': { 11 | beforeEach() { 12 | console.warn = sinon.stub() 13 | console.warn.returnsArg(0) 14 | }, 15 | 16 | 'stores with colliding names'() { 17 | const MyStore = (function () { 18 | return function MyStore() { } 19 | }()) 20 | alt.createStore(MyStore) 21 | 22 | assert.isObject(alt.stores.MyStore1, 'a store was still created') 23 | 24 | }, 25 | 26 | 'colliding names via identifier'() { 27 | class auniquestore { } 28 | alt.createStore(auniquestore, 'MyStore') 29 | 30 | assert.isObject(alt.stores.MyStore1, 'a store was still created') 31 | }, 32 | 33 | 'not providing a store name via anonymous function'() { 34 | alt.createStore(function () { }) 35 | 36 | assert.isObject(alt.stores[''], 'a store with no name was still created') 37 | }, 38 | 39 | afterEach() { 40 | assert.ok(console.warn.calledOnce, 'the warning was called') 41 | assert.instanceOf(console.warn.returnValues[0], ReferenceError, 'value returned is an instanceof referenceerror') 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This is the source of the [alt website](github.io/goatslacker/alt), which is generated using [Github Pages](https://pages.github.com/) and [Jekyll](http://jekyllrb.com/). 4 | 5 | The source utilizes the same `docs` folder as the rest of the project, but converts the links from their usable format on github, to work with the website. 6 | 7 | ## Setup 8 | 9 | You must first install the Ruby dependencies specified in the `Gemfile` with [bundler](http://bundler.io/), `bundle install --path .bundle` 10 | 11 | ## Development 12 | 13 | This project uses [Rake](https://github.com/ruby/rake) to run commands to build/deploy the Ruby based Jekyll site. 14 | 15 | To view all the Rake commands available in the `Rakefile` run `bundle exec rake -T`. 16 | 17 | - Generate the Jekyll website (gets written to `_site/`) with `bundle exec rake build`. 18 | - Watch for changes and serve at `localhost:4000/alt/` with `bundle exec rake watch` or just `bundle exec rake` (because it is the default task). 19 | 20 | ## Deploying 21 | 22 | Changes on the website are made directly to the `master` branch like any other alt changes, but they will not be deployed to the site automatically. The maintainers will use the rake deploy task which will commit the latest site changes to the `gh-pages` branch, where github will handle propagating these so they are viewable online. 23 | -------------------------------------------------------------------------------- /test/value-stores-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import Alt from '../' 3 | import sinon from 'sinon' 4 | 5 | const alt = new Alt() 6 | 7 | const actions = alt.generateActions('fire') 8 | 9 | const store = alt.createStore({ 10 | state: 21, 11 | 12 | displayName: 'ValueStore', 13 | 14 | reduce(state, payload) { 15 | return state + 1 16 | } 17 | }) 18 | 19 | const store2 = alt.createStore({ 20 | state: [1, 2, 3], 21 | 22 | displayName: 'Value2Store', 23 | 24 | reduce(state, payload) { 25 | return state.concat(state[state.length - 1] + 1) 26 | } 27 | }) 28 | 29 | export default { 30 | 'value stores': { 31 | beforeEach() { 32 | alt.recycle() 33 | }, 34 | 35 | 'stores can contain state as any value'(done) { 36 | assert(store.state === 21, 'store state is value') 37 | assert(store.getState() === 21, 'getState returns value too') 38 | 39 | const unlisten = store.listen((state) => { 40 | assert(state === 22, 'incremented store state') 41 | unlisten() 42 | done() 43 | }) 44 | 45 | assert(JSON.parse(alt.takeSnapshot()).ValueStore === 21, 'snapshot ok') 46 | 47 | actions.fire() 48 | }, 49 | 50 | 'store with array works too'() { 51 | assert.deepEqual(store2.state, [1, 2, 3]) 52 | actions.fire() 53 | assert.deepEqual(store2.state, [1, 2, 3, 4]) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/chat/js/components/MessageListItem.react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | * 12 | * @jsx React.DOM 13 | */ 14 | 15 | var React = require('react'); 16 | 17 | var ReactPropTypes = React.PropTypes; 18 | 19 | var MessageListItem = React.createClass({ 20 | 21 | propTypes: { 22 | message: ReactPropTypes.object 23 | }, 24 | 25 | render: function() { 26 | var message = this.props.message; 27 | return ( 28 |
  • 29 |
    {message.authorName}
    30 |
    31 | {message.date.toLocaleTimeString()} 32 |
    33 |
    {message.text}
    34 |
  • 35 | ); 36 | } 37 | 38 | }); 39 | 40 | module.exports = MessageListItem; 41 | -------------------------------------------------------------------------------- /web/feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | 5 | 6 | 7 | {{ site.title | xml_escape }} 8 | {{ site.description | xml_escape }} 9 | {{ site.url }}{{ site.baseurl }}/ 10 | 11 | {{ site.time | date_to_rfc822 }} 12 | {{ site.time | date_to_rfc822 }} 13 | Jekyll v{{ jekyll.version }} 14 | {% for post in site.posts limit:10 %} 15 | 16 | {{ post.title | xml_escape }} 17 | {{ post.content | xml_escape }} 18 | {{ post.date | date_to_rfc822 }} 19 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 20 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 21 | {% for tag in post.tags %} 22 | {{ tag | xml_escape }} 23 | {% endfor %} 24 | {% for cat in post.categories %} 25 | {{ cat | xml_escape }} 26 | {% endfor %} 27 | 28 | {% endfor %} 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/actions-dump-test.js: -------------------------------------------------------------------------------- 1 | import Alt from '../' 2 | import { assert } from 'chai' 3 | 4 | const alt = new Alt() 5 | 6 | alt.generateActions('one', 'two') 7 | const test = alt.generateActions('three') 8 | alt.generateActions('one') 9 | 10 | alt.createActions(class FooActions { 11 | static displayName = 'FooActions' 12 | one() {} 13 | two() {} 14 | }) 15 | 16 | const pojo = alt.createActions({ 17 | displayName: 'Pojo', 18 | one() { }, 19 | two() { } 20 | }) 21 | 22 | alt.createActions({ 23 | one() { }, 24 | two() { } 25 | }) 26 | 27 | alt.createAction('test', function () { }) 28 | 29 | export default { 30 | 'actions obj'() { 31 | assert.isObject(alt.actions, 'actions exist') 32 | assert.isFunction(alt.actions.global.test, 'test exists') 33 | assert(Object.keys(alt.actions.global).length === 5, 'global actions contain stuff from createAction and generateActions') 34 | assert(Object.keys(alt.actions.FooActions).length === 2, '2 actions namespaced on FooActions') 35 | assert.isObject(alt.actions.Pojo, 'pojo named action exists') 36 | assert(Object.keys(alt.actions.Pojo).length == 2, 'pojo has 2 actions associated with it') 37 | 38 | assert.isDefined(alt.actions.global.three, 'three action is defined in global') 39 | 40 | assert.isDefined(alt.actions.Unknown.one, 'one exists in Unknown') 41 | assert.isDefined(alt.actions.global.one1, 'one1 was created because of a name clash') 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/StoreExplorer.js: -------------------------------------------------------------------------------- 1 | import AltStore from './debug/AltStore' 2 | import DebugActions from './debug/DebugActions' 3 | import React from 'react' 4 | import connectToStores from './connectToStores' 5 | 6 | class StoreExplorer extends React.Component { 7 | constructor() { 8 | super() 9 | 10 | this.selectStore = this.selectStore.bind(this) 11 | } 12 | 13 | componentDidMount() { 14 | DebugActions.setAlt(this.props.alt) 15 | } 16 | 17 | selectStore(ev) { 18 | const data = ev.target.dataset 19 | const store = this.props.alt.stores[data.name] 20 | if (store) DebugActions.selectData(store.getState()) 21 | } 22 | 23 | render() { 24 | return ( 25 |
    26 |

    Stores

    27 |
      28 | {this.props.stores.map((store) => { 29 | return ( 30 |
    • 36 | {store.displayName} 37 |
    • 38 | ) 39 | })} 40 |
    41 |
    42 | ) 43 | } 44 | } 45 | 46 | export default connectToStores({ 47 | getPropsFromStores() { 48 | return { 49 | stores: AltStore.stores() 50 | } 51 | }, 52 | 53 | getStores() { 54 | return [AltStore] 55 | } 56 | }, StoreExplorer) 57 | -------------------------------------------------------------------------------- /test/config-set-get-state-test.js: -------------------------------------------------------------------------------- 1 | import Alt from '../' 2 | import { assert } from 'chai' 3 | import sinon from 'sinon' 4 | 5 | 6 | export default { 7 | 'Config state getter and setter': { 8 | 'setting state'() { 9 | const setState = sinon.stub().returns({ 10 | foo: 'bar' 11 | }) 12 | 13 | const alt = new Alt({ setState }) 14 | 15 | const action = alt.generateActions('fire') 16 | 17 | const store = alt.createStore({ 18 | displayName: 'store', 19 | bindListeners: { 20 | fire: action.fire 21 | }, 22 | state: { x: 1 }, 23 | fire() { 24 | this.setState({ x: 2 }) 25 | } 26 | }) 27 | 28 | assert(store.getState().x === 1) 29 | 30 | action.fire() 31 | 32 | assert.ok(setState.calledOnce) 33 | assert(setState.args[0].length === 2) 34 | assert(store.getState().foo === 'bar') 35 | }, 36 | 37 | 'getting state'() { 38 | const getState = sinon.stub().returns({ 39 | foo: 'bar' 40 | }) 41 | 42 | const alt = new Alt({ getState }) 43 | 44 | const store = alt.createStore({ 45 | displayName: 'store', 46 | state: { x: 1 } 47 | }) 48 | 49 | assert.isUndefined(store.getState().x) 50 | 51 | assert.ok(getState.calledOnce) 52 | assert(getState.args[0].length === 1) 53 | assert(store.getState().foo === 'bar') 54 | }, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/listen-to-actions.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import Alt from '../dist/alt-with-runtime' 3 | 4 | import ActionListeners from '../utils/ActionListeners' 5 | 6 | const alt = new Alt() 7 | 8 | class MyActions { 9 | static displayName = 'ActionListenerActions' 10 | 11 | constructor() { 12 | this.generateActions('updateName') 13 | } 14 | } 15 | 16 | const myActions = alt.createActions(MyActions) 17 | 18 | const listener = new ActionListeners(alt) 19 | 20 | export default { 21 | 'listen to actions globally'() { 22 | const id = listener.addActionListener(myActions.UPDATE_NAME, (name, details) => { 23 | assert(name === 'yes', 'proper data was passed in') 24 | assert(details.namespace === 'ActionListenerActions') 25 | assert(details.name === 'updateName') 26 | }) 27 | 28 | assert.isString(id, 'the dispatcher id is returned for the listener') 29 | 30 | myActions.updateName('yes') 31 | 32 | listener.removeActionListener(id) 33 | 34 | assert.doesNotThrow(() => myActions.updateName('no'), 'no error was thrown by action since listener was removed') 35 | 36 | listener.addActionListener(myActions.UPDATE_NAME, (name) => { 37 | assert(name === 'mud', 'proper data was passed in again') 38 | }) 39 | 40 | myActions.updateName('mud') 41 | 42 | listener.removeAllActionListeners() 43 | 44 | myActions.updateName('bill') 45 | assert.doesNotThrow(() => myActions.updateName('bill'), 'all listeners were removed') 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/testing-utils.js: -------------------------------------------------------------------------------- 1 | import Alt from '../dist/alt-with-runtime' 2 | import AltTestingUtils from '../utils/AltTestingUtils' 3 | import { assert } from 'chai' 4 | 5 | const alt = new Alt() 6 | 7 | const petActions = alt.generateActions('buyPet', 'sellPet') 8 | 9 | class UnwrappedPetStore { 10 | constructor() { 11 | this.bindActions(petActions) // buyPet, sellPet 12 | 13 | this.pets = {hamsters: 2, dogs: 0, cats: 3} 14 | this.storeName = "Pete's Pets" 15 | this.revenue = 0 16 | } 17 | 18 | onBuyPet({cost, pet}) { 19 | this.pets[pet]++ 20 | this.revenue -= this.roundMoney(cost) 21 | } 22 | 23 | onSellPet({price, pet}) { 24 | this.pets[pet]-- 25 | this.revenue += this.roundMoney(price) 26 | } 27 | 28 | // this is inaccessible from our alt wrapped store 29 | roundMoney(money) { 30 | // rounds to cents 31 | return Math.round(money * 100) / 100 32 | } 33 | 34 | static getInventory() { 35 | return this.getState().pets 36 | } 37 | } 38 | 39 | export default { 40 | 'make a store testable by auto mocking alt.createStore'() { 41 | var context = AltTestingUtils.mockGetState({ pets: [1, 2, 3] }) 42 | var emptyContext = AltTestingUtils.mockGetState() 43 | 44 | var unwrappedStore = AltTestingUtils.makeStoreTestable(alt, UnwrappedPetStore) 45 | assert(unwrappedStore.roundMoney(21.221234) === 21.22) 46 | assert(unwrappedStore.roundMoney(11.2561341) === 11.26) 47 | assert(UnwrappedPetStore.getInventory.call(context)[0] === 1) 48 | assert.isUndefined(UnwrappedPetStore.getInventory.call(emptyContext)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/weathertabs/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Lato', sans-serif, "Arial"; 3 | font-size: 16px; 4 | background: #eee; 5 | margin: 0; 6 | } 7 | 8 | h1 { 9 | background: #333; 10 | margin: 0; 11 | padding: 20px 20px; 12 | color: #eee; 13 | border-bottom: solid 3px #aaa; 14 | } 15 | 16 | .search-box { 17 | width: 600px; 18 | margin: 40px auto 60px auto; 19 | } 20 | 21 | .search-location { 22 | padding: 6px; 23 | width: 380px; 24 | margin-right: 10px; 25 | font-size: 20px; 26 | border-radius: 3px; 27 | border: solid 1px #ccc; 28 | } 29 | 30 | button { 31 | font-size: 20px; 32 | padding: 6px 14px; 33 | background: #333; 34 | color: #eee; 35 | border: solid 1px #999; 36 | border-radius: 3px; 37 | } 38 | 39 | .content { 40 | padding: 0 20px; 41 | } 42 | 43 | .pull-right { 44 | float:right; 45 | } 46 | 47 | .weather-tab { 48 | padding: 20px; 49 | background: #ccc; 50 | border-radius: 3px; 51 | } 52 | 53 | .loading img { 54 | margin: 0 auto; 55 | display: block; 56 | padding: 50px 0; 57 | } 58 | 59 | .nav { 60 | list-style: none; 61 | padding: 0; 62 | margin: 0 20px; 63 | } 64 | 65 | .nav li { 66 | display: inline; 67 | margin: 0; 68 | font-size: 20px; 69 | padding: 10px 0; 70 | background: #333; 71 | position: relative; 72 | bottom: 10px; 73 | border-top-left-radius: 3px; 74 | border-top-right-radius: 3px; 75 | } 76 | 77 | .nav li a { 78 | color: white; 79 | padding: 0 1em; 80 | text-decoration: none; 81 | } 82 | 83 | .nav li.selected { 84 | background: #ccc; 85 | padding-top: 18px; 86 | } 87 | 88 | .nav li.selected a { 89 | color: #000; 90 | } 91 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | If you think there's room for improvement or see it broken feel free to submit a patch. 4 | We'll accept a patch through any medium: GitHub pull requests, gists, emails, 5 | gitter.im snippets, etc. 6 | 7 | Your patch should adhere to these guidelines: 8 | 9 | * The [coding style](#coding-style) is similar. 10 | * All tests should pass `npm test` and coverage should remain at 100% `npm run coverage`. 11 | * No linting errors are present `npm run lint`. 12 | * The commit history is clean (no merge commits). 13 | * We thank you for your patch. 14 | 15 | ## How to get set up 16 | 17 | Fork the project and clone it to your computer. Then you'll need npm to install 18 | the project's dependencies. Just run: 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | To make sure everything is ok you should run the tests: 25 | 26 | ```bash 27 | npm test 28 | ``` 29 | 30 | ## Coding Style 31 | 32 | We use [EditorConfig](http://editorconfig.org/) for basics and encourage you 33 | to install its plugin on your text editor of choice. This will get you 25% of 34 | the way there. 35 | 36 | The only hard-line rule is that the code should look uniform. We loosely follow 37 | the [Airbnb JS style guide](https://github.com/airbnb/javascript) with a few 38 | notable exceptions. 39 | 40 | * You shouldn't have to use [semicolons](https://medium.com/@goatslacker/no-you-dont-need-semicolons-148d936b9cf2). The build file adds them in anyway. 41 | * Do not rely on any ES6 shim/sham features (Map, WeakMap, Proxy, etc). 42 | * Use `//` for comments. And comment anything you feel isn't obvious. 43 | 44 | ## License 45 | 46 | All of our code and files are licensed under MIT. 47 | -------------------------------------------------------------------------------- /test/browser/index.js: -------------------------------------------------------------------------------- 1 | const assign = require('object-assign') 2 | 3 | const tests = assign( 4 | {}, 5 | require('../index'), 6 | require('../listen-to-actions'), 7 | require('../final-store'), 8 | require('../recorder'), 9 | require('../setting-state'), 10 | require('../stores-get-alt'), 11 | require('../stores-with-colliding-names'), 12 | require('../testing-utils'), 13 | require('../use-event-emitter'), 14 | require('../store-as-a-module'), 15 | require('../es3-module-pattern') 16 | ) 17 | 18 | // This code is directly from mocha/lib/interfaces/exports.js 19 | // with a few modifications 20 | function manualExports(exports, suite) { 21 | var suites = [suite] 22 | 23 | visit(exports) 24 | 25 | function visit(obj) { 26 | var suite 27 | for (var key in obj) { 28 | if ('function' == typeof obj[key]) { 29 | var fn = obj[key] 30 | switch (key) { 31 | case 'before': 32 | suites[0].beforeAll(fn) 33 | break 34 | case 'after': 35 | suites[0].afterAll(fn) 36 | break 37 | case 'beforeEach': 38 | suites[0].beforeEach(fn) 39 | break 40 | case 'afterEach': 41 | suites[0].afterEach(fn) 42 | break 43 | default: 44 | suites[0].addTest(new Mocha.Test(key, fn)) 45 | } 46 | } else { 47 | var suite = Mocha.Suite.create(suites[0], key) 48 | suites.unshift(suite) 49 | visit(obj[key]) 50 | suites.shift() 51 | } 52 | } 53 | } 54 | } 55 | 56 | manualExports(tests, mocha.suite) 57 | 58 | mocha.setup('exports') 59 | mocha.checkLeaks() 60 | mocha.run() 61 | -------------------------------------------------------------------------------- /src/utils/ActionListeners.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ActionListeners(alt: AltInstance): ActionListenersInstance 3 | * 4 | * > Globally listen to individual actions 5 | * 6 | * If you need to listen to an action but don't want the weight of a store 7 | * then this util is what you can use. 8 | * 9 | * Usage: 10 | * 11 | * ```js 12 | * var actionListener = new ActionListeners(alt); 13 | * 14 | * actionListener.addActionListener(Action.ACTION_NAME, function (data) { 15 | * // do something with data 16 | * }) 17 | * ``` 18 | */ 19 | 20 | function ActionListeners(alt) { 21 | this.dispatcher = alt.dispatcher 22 | this.listeners = {} 23 | } 24 | 25 | /* 26 | * addActionListener(symAction: symbol, handler: function): number 27 | * Adds a listener to a specified action and returns the dispatch token. 28 | */ 29 | ActionListeners.prototype.addActionListener = function (symAction, handler) { 30 | const id = this.dispatcher.register((payload) => { 31 | /* istanbul ignore else */ 32 | if (symAction === payload.action) { 33 | handler(payload.data, payload.details) 34 | } 35 | }) 36 | this.listeners[id] = true 37 | return id 38 | } 39 | 40 | /* 41 | * removeActionListener(id: number): undefined 42 | * Removes the specified dispatch registration. 43 | */ 44 | ActionListeners.prototype.removeActionListener = function (id) { 45 | delete this.listeners[id] 46 | this.dispatcher.unregister(id) 47 | } 48 | 49 | /** 50 | * Remove all listeners. 51 | */ 52 | ActionListeners.prototype.removeAllActionListeners = function () { 53 | Object.keys(this.listeners).forEach( 54 | this.removeActionListener.bind(this) 55 | ) 56 | this.listeners = {} 57 | } 58 | 59 | export default ActionListeners 60 | -------------------------------------------------------------------------------- /examples/chat/js/components/MessageComposer.react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | * 12 | * @jsx React.DOM 13 | */ 14 | 15 | var ChatMessageActionCreators = require('../actions/ChatMessageActionCreators'); 16 | var React = require('react'); 17 | 18 | var ENTER_KEY_CODE = 13; 19 | 20 | var MessageComposer = React.createClass({ 21 | 22 | getInitialState: function() { 23 | return {text: ''}; 24 | }, 25 | 26 | render: function() { 27 | return ( 28 |