├── .gitattributes ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CNAME ├── HISTORY.md ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── bower.json ├── build ├── prerelease-docs.sh ├── release-docs.sh ├── release.sh └── updateVersion.js ├── dist ├── marty.js ├── marty.min.js └── marty.min.js.gz ├── docs ├── Gemfile ├── Gemfile.lock ├── _api │ ├── action-creators │ │ └── index.md │ ├── app-mixin │ │ └── index.md │ ├── application │ │ └── index.md │ ├── constants │ │ └── index.md │ ├── containers │ │ └── index.md │ ├── inject-app │ │ └── index.md │ ├── queries │ │ └── index.md │ ├── state-mixin │ │ └── index.md │ ├── state-sources │ │ ├── cookie.md │ │ ├── http.md │ │ ├── index.md │ │ ├── json-storage.md │ │ ├── local-storage.md │ │ ├── location.md │ │ └── session-storage.md │ ├── stores │ │ └── index.md │ ├── test-utils │ │ └── index.md │ └── top-level-api │ │ └── index.md ├── _config.yml ├── _data │ ├── api_section_order.yml │ └── guides_section_order.yml ├── _devtools │ └── index.md ├── _guides │ ├── action-creators │ │ ├── actions-vs-action-creators.md │ │ ├── handling-errors.md │ │ ├── index.md │ │ └── migrating-from-v8.md │ ├── application │ │ ├── automatic-registration.md │ │ └── index.md │ ├── constants │ │ └── index.md │ ├── containers │ │ └── index.md │ ├── developer-tools │ │ └── index.md │ ├── dispatcher │ │ └── index.md │ ├── fetching-state │ │ └── index.md │ ├── flux │ │ └── index.md │ ├── further-reading │ │ └── index.md │ ├── getting-started │ │ └── index.md │ ├── isomorphism │ │ ├── index.md │ │ └── marty-express.md │ ├── marty-native │ │ └── index.md │ ├── queries │ │ └── index.md │ ├── state-mixin │ │ └── index.md │ ├── state-sources │ │ └── index.md │ ├── stores │ │ ├── immutable-data-collections.md │ │ └── index.md │ └── upgrading │ │ ├── 08_09.md │ │ ├── 09_10.md │ │ └── index.md ├── _layouts │ ├── default.html │ ├── home.html │ ├── page.html │ └── post.html ├── _plugins │ ├── link.rb │ ├── sample.rb │ └── version.rb ├── _posts │ ├── 2015-03-24-marty-v0.9.md │ ├── 2015-05-27-marty-v0.10.md │ └── 2015-08-02-marty-last.md ├── _support │ └── index.md ├── css │ ├── bootstrap.min.css │ ├── codemirror.css │ ├── docs.css │ ├── font-awesome.min.css │ └── syntax.css ├── favicon.ico ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── img │ ├── data-flow.png │ ├── devtools-action.png │ ├── devtools-data-flow.png │ ├── qubit.png │ └── renderToString-diagnostics.png ├── index.md └── js │ ├── codemirror.js │ ├── headings.js │ └── style.js ├── examples ├── requirejs │ ├── index.html │ └── main.js └── window │ └── index.html ├── karma.conf.js ├── marty.d.ts ├── marty.js ├── package.json └── test-utils.js /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/**/* -diff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | npm-debug.log 4 | .DS_STORE 5 | log/* 6 | tmp 7 | _gh_pages 8 | _site 9 | *.diff 10 | *.err 11 | *.log 12 | *.orig 13 | *.rej 14 | *.swo 15 | *.swp 16 | *.vi 17 | *.zip 18 | *~ 19 | ._* 20 | .cache 21 | .DS_Store 22 | .idea 23 | .project 24 | .settings 25 | .tmproj 26 | *.esproj 27 | *.sublime-project 28 | *.sublime-workspace 29 | nbproject 30 | Thumbs.db 31 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 3 | "requireCurlyBraces": ["for", "while", "do", "try", "catch"], 4 | "requireOperatorBeforeLineBreak": [ 5 | "=", 6 | "+", 7 | "-", 8 | "/", 9 | "*", 10 | "==", 11 | "===", 12 | "!=", 13 | "!==", 14 | ">", 15 | ">=", 16 | "<", 17 | "<=" 18 | ], 19 | "excludeFiles": [ 20 | "dist/*" 21 | ], 22 | "maximumLineLength": { 23 | "value": 120, 24 | "allowComments": true, 25 | "allowRegex": true 26 | }, 27 | "validateQuoteMarks": { 28 | "escape": true, 29 | "mark": "'" 30 | }, 31 | "validateIndentation": 2, 32 | 33 | "disallowMultipleLineStrings": true, 34 | "disallowMixedSpacesAndTabs": true, 35 | "disallowTrailingWhitespace": true, 36 | "disallowKeywords": ["with"], 37 | "disallowKeywordsOnNewLine": ["else"], 38 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 39 | "requireSpaceAfterLineComment": true, 40 | "requireSpaceAfterBinaryOperators": true, 41 | "requireSpacesInConditionalExpression": true, 42 | "requireSpaceBeforeBlockStatements": true, 43 | "requireSpacesInAnonymousFunctionExpression": { 44 | "beforeOpeningRoundBrace": true, 45 | "beforeOpeningCurlyBrace": true 46 | }, 47 | "disallowSpacesInNamedFunctionExpression": { 48 | "beforeOpeningRoundBrace": true 49 | }, 50 | "disallowSpacesInFunctionDeclaration": { 51 | "beforeOpeningRoundBrace": true 52 | }, 53 | 54 | "validateJSDoc": { 55 | "checkParamNames": true, 56 | "requireParamTypes": true 57 | } 58 | } -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | dist/* -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "eqeqeq": true, 3 | "forin": false, 4 | "freeze": true, 5 | "immed": true, 6 | "latedef": "nofunc", 7 | "newcap": false, 8 | "noarg": true, 9 | "noempty": true, 10 | "nonew": true, 11 | "regexp": false, 12 | "undef": true, 13 | "unused": true, 14 | "trailing": true, 15 | "maxcomplexity": 12, 16 | "laxbreak": true, 17 | "sub": true, 18 | "expr": true, 19 | "browser": true, 20 | "node": true, 21 | "devel": true, 22 | "indent": 2, 23 | "maxlen": 180, 24 | "validthis": true, 25 | "esnext": true, 26 | "proto": true, 27 | "globals": { 28 | "define": true, 29 | "require": true, 30 | "describe": true, 31 | "context": true, 32 | "it": true, 33 | "expect": true, 34 | "before": true, 35 | "after": true, 36 | "beforeEach": true, 37 | "afterEach": true, 38 | "Promise": true, 39 | "fetch": true 40 | } 41 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | node_modules 3 | test 4 | .gitattributes 5 | .gitignore 6 | .jscsrc 7 | .jshintignore 8 | .jshintrc 9 | .travis.yml 10 | CNAME 11 | karma.conf.js 12 | Makefile -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | script: make test 5 | env: 6 | global: 7 | - ENV="CI" 8 | - SAUCE_USERNAME="jhollingworth" 9 | - SAUCE_ACCESS_KEY="4938c9e3-26ce-4661-b09b-785e49f3e892" 10 | cache: 11 | directories: 12 | - node_modules -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | martyjs.org 2 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 0.10.4 / 2015-06-15 2 | =================== 3 | - Container improvements [#20](https://github.com/martyjs/marty-lib/pull/20) 4 | - Fix Unexpected end of input in react native apps [#18](https://github.com/martyjs/marty-lib/pull/18) 5 | 6 | 0.10.3 / 2015-06-03 7 | =================== 8 | - Was an idiot. Forgot to re-build - see https://github.com/martyjs/marty-lib/issues/15 9 | 10 | 0.10.2 / 2015-06-03 11 | =================== 12 | 13 | - Fix stack overflow in Application.register [#14](https://github.com/martyjs/marty-lib/pull/14) 14 | - Support deeply nested stores in listenTo [#11](https://github.com/martyjs/marty-lib/pull/11) 15 | 16 | 0.10.1 / 2015-05-28 17 | =================== 18 | 19 | - Add a temporary fix for react-hot-loader issue (Will properly be fixed when https://github.com/gaearon/react-hot-api/pull/16 is merged) 20 | - Re-added htmlBody/htmlState, parseJSON and specification (Resolves [martyjs/marty-express#10](https://github.com/martyjs/marty-express/issues/10)) 21 | 22 | 0.10.0 / 2015-05-27 23 | =================== 24 | **New features** 25 | 26 | - [marty-native](https://github.com/martyjs/marty/issues/230) 27 | - [No singleton](https://github.com/martyjs/marty/issues/261) 28 | - [ES7 features](https://github.com/martyjs/marty-lib/pull/3) 29 | - [Improved testing](https://github.com/martyjs/marty/issues/19) 30 | - DevTools supports marty-native 31 | 32 | **Deprecations** 33 | 34 | - Due to API incompatibility issues `parseJSON` will be removed by default in future versions. Instead you should call `this.json()` from within your state source. 35 | 36 | **Removed** 37 | 38 | - `Marty.register()` is no longer needed, just return the type 39 | - You will no longer be able to pass in a store or an object hash of stores into `Marty.createStateMixin` 40 | - [Rollbacks](http://martyjs.org/v/0.9.0/api/stores/index.html#rollback) have been removed entirely 41 | - [Store handler values](http://martyjs.org/api/stores/#handlers) can only be either a string or an array of strings. You cannot do complex object queries any more. 42 | - `Marty.registry` is no longer supported. Use applications instead. 43 | - `Marty.createContext()` is no longer supported. Use applications instead. 44 | - `Marty.renderToString()` is no longer supported. Use `Application#renderToString()` instead 45 | - `Store#rollback()` is no longer supported. You should instead dispatch an error action. 46 | - `.for(this)` has been removed. Use applications instead. 47 | - You no longer to specify `id`'s in the type. Instead define the Id on the `Application#register()` 48 | - `require('marty').Dispatcher` is no longer supported. Create an application and access the [dispatcher](http://martyjs.org/api/application/index.html#dispatcher). 49 | - `require('marty/http/hooks')` is no longer supported. Use `require('marty').hooks` instead 50 | - `require('marty/environment')` is no longer supported. Use `require('marty').environment` 51 | - `require('marty/fetch')` is no longer supported. Use `require('marty').fetch` 52 | - `require('marty/when')` is no longer supported. Use `require('marty').when` 53 | - `require('marty/autoDispatch')` is no longer supported. Use `require('marty').autoDispatch` 54 | - `require('marty').Diagnostics` is no longer supported. Use `require('marty').diagnostics` 55 | 56 | 0.9.17 / 2015-05-26 57 | =================== 58 | - Return individual parts of html `htmlBody` and `htmlState` to solve [#288](https://github.com/martyjs/marty/issues/288) 59 | 60 | 0.9.16 / 2015-05-21 61 | =================== 62 | - Remove try/catch around when to improve stack traces [#313](https://github.com/martyjs/marty/issues/313). 63 | - Make HTTP methods upper case to conform to Fetch spec [#318](https://github.com/martyjs/marty/issues/318) 64 | 65 | 0.9.15 / 2015-05-17 66 | =================== 67 | - Update Marty.renderToString so that it will continue re-rendering until no new fetches are made. Resolves [#314](https://github.com/martyjs/marty/issues/314). 68 | 69 | 0.9.14 / 2015-05-14 70 | =================== 71 | - Updated isomorphic-fetch to v2.0.0 72 | 73 | 0.9.13 / 2015-05-11 74 | =================== 75 | - Add optional `throwError` hook that replicates Marty v0.8 behavior of throwing an error if http response is not OK 76 | 77 | 0.9.12 / 2015-05-04 78 | =================== 79 | - Pass finished fetches into a containers `pending` handler to resolve [#300](https://github.com/martyjs/marty/issues/276) 80 | 81 | 0.9.11 / 2015-04-16 82 | =================== 83 | - Allow you to extend contexts to resolve [#276](https://github.com/martyjs/marty/issues/276) 84 | 85 | 0.9.10 / 2015-04-11 86 | =================== 87 | - Update definition of React peer dependency 88 | 89 | 0.9.9 / 2015-04-08 90 | =================== 91 | - Fix for parsing JSON in Chrome 43 [#268](https://github.com/jhollingworth/marty/issues/268) 92 | 93 | 0.9.8 / 2015-04-07 94 | =================== 95 | 96 | - Do not setState in container unless component is mounted [#265](https://github.com/jhollingworth/marty/issues/265) 97 | - Generate lifecycle methods at container creation [#263](https://github.com/jhollingworth/marty/issues/263) 98 | - Make it easier to use with webpack [#259](https://github.com/jhollingworth/marty/issues/259) 99 | 100 | 0.9.7 / 2015-04-04 101 | =================== 102 | 103 | - Fix bug where single instance of observer per container [#248](https://github.com/jhollingworth/marty/issues/248) 104 | - Allow you to pass in component life style hooks (componentWillReceiveProps, componentWillUpdate, componentDidUpdate, componentDidMount, componentWillUnmount and componentWillMount) [#249](https://github.com/jhollingworth/marty/issues/249) 105 | 106 | 0.9.6 / 2015-03-30 107 | =================== 108 | 109 | - Use latest props when container is updating fetches [#244](https://github.com/jhollingworth/marty/issues/244) 110 | 111 | 0.9.5 / 2015-03-29 112 | =================== 113 | 114 | - Don't swallow errors when fetching `locally` or `remotely` [#238](https://github.com/jhollingworth/marty/issues/238) 115 | 116 | 0.9.4 / 2015-03-28 117 | =================== 118 | 119 | - Add `Container#getInnerComponent` 120 | 121 | 0.9.3 / 2015-03-28 122 | =================== 123 | 124 | - Allow you to specify your own contextTypes on the container #234 125 | - Allow you to extend containers with your own functions [#224](https://github.com/jhollingworth/marty/issues/224) 126 | 127 | 0.9.2 / 2015-03-27 128 | =================== 129 | 130 | - Correctly listening to `componentWillReceiveProps` and passing props [#229](https://github.com/jhollingworth/marty/issues/229) 131 | 132 | 0.9.1 / 2015-03-25 133 | =================== 134 | 135 | - Fix issue where Marty would not work with window object #219, [#216](https://github.com/jhollingworth/marty/issues/216) 136 | 137 | 0.9.0 / 2015-03-24 138 | =================== 139 | 140 | **New features** 141 | 142 | - Isomorphism [#13](https://github.com/jhollingworth/marty/issues/13) 143 | - CookieStateSource 144 | - LocationStateSource 145 | - ES6 Classes [#89](https://github.com/jhollingworth/marty/issues/89) 146 | - Add dataType option to http state source [#161](https://github.com/jhollingworth/marty/issues/161) 147 | - Lodash v3 instead of underscore [#136](https://github.com/jhollingworth/marty/issues/136) 148 | - Simplify action creators #163, [#93](https://github.com/jhollingworth/marty/issues/93) 149 | - replaceState and setState [#126](https://github.com/jhollingworth/marty/issues/126) 150 | - HttpStateSource hooks [#118](https://github.com/jhollingworth/marty/issues/118) 151 | - FetchResult#toPromise [#131](https://github.com/jhollingworth/marty/issues/131) 152 | - Clear fetch history in Store#clear [#149](https://github.com/jhollingworth/marty/issues/149) 153 | - Batch store change events [#112](https://github.com/jhollingworth/marty/issues/112) 154 | - Allow you to specify when function context [#76](https://github.com/jhollingworth/marty/issues/76) 155 | - Marty.createContainer [#204](https://github.com/jhollingworth/marty/issues/204) 156 | - Set request credentials to 'same-origin' [#209](https://github.com/jhollingworth/marty/issues/209) 157 | 158 | **Bugs** 159 | 160 | - dependsOn doesn't update when dependent store updates [#113](https://github.com/jhollingworth/marty/issues/113) 161 | - Don't auto set content-type if using FormData [#140](https://github.com/jhollingworth/marty/issues/140) 162 | - Fetch API compatibility [#133](https://github.com/jhollingworth/marty/issues/133) 163 | 164 | 165 | 0.8.15 / 2015-03-06 166 | =================== 167 | - Add reactify as a dependency so you dont have to explicitly add it to parent project 168 | 169 | 0.8.14 / 2015-03-05 170 | =================== 171 | - Remove dependency on Babel 172 | 173 | 0.8.13 / 2015-03-03 174 | =================== 175 | - Hotfix for fetch incomaptibility in Chrome Canary [#133](https://github.com/jhollingworth/marty/issues/133) 176 | 177 | 0.8.12 / 2015-02-14 178 | =================== 179 | - Added improved error logging [#129](https://github.com/jhollingworth/marty/issues/129) 180 | 181 | 0.8.11 / 2015-02-12 182 | =================== 183 | - Improve documentation 184 | - Fixes [#106](https://github.com/jhollingworth/marty/issues/106) 185 | - Fixes [#74](https://github.com/jhollingworth/marty/issues/74) 186 | - Fixes [#123](https://github.com/jhollingworth/marty/issues/123) 187 | 188 | 0.8.10 / 2015-02-03 189 | ================== 190 | - Fixes documentation typos 191 | - :green_heart: Fixes [#100](https://github.com/jhollingworth/marty/issues/100) 192 | - Fixes [#94](https://github.com/jhollingworth/marty/issues/94) 193 | - Fixes [#99](https://github.com/jhollingworth/marty/issues/99) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2015 James Hollingworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = ./node_modules/.bin 2 | 3 | .PHONY: bootstrap bootstrap-js bootstrap-ruby start docs release-docs build; 4 | 5 | bootstrap: bootstrap-js bootstrap-ruby 6 | 7 | bootstrap-js: package.json 8 | @npm install 9 | 10 | bootstrap-ruby: docs/Gemfile 11 | @which bundle > /dev/null || gem install bundler 12 | @cd docs && bundle install 13 | 14 | release: 15 | @inc=$(inc) sh ./build/release.sh 16 | 17 | build: 18 | @rm -rf dist && mkdir -p dist 19 | @$(BIN)/browserify --bare --transform babelify --plugin bundle-collapser/plugin --require ./marty.js --exclude react --standalone Marty > dist/marty.js 20 | @cat dist/marty.js | $(BIN)/uglifyjs -m -c "comparisons=false,keep_fargs=true,unsafe=true,unsafe_comps=true,warnings=false" -b "ascii_only=true,beautify=false" -o dist/marty.min.js 21 | @gzip --best dist/marty.min.js -c > dist/marty.min.js.gz 22 | 23 | docs: 24 | @cd docs && bundle exec jekyll serve -w 25 | 26 | release-docs: 27 | @sh ./build/release-docs.sh 28 | 29 | prerelease-docs: 30 | @sh ./build/prerelease-docs.sh 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Marty is no longer actively maintained. Use [Alt](http://alt.js.org) or [Redux](https://github.com/gaearon/redux) instead. [More info](http://martyjs.org/blog/2015/08/02/marty-last.html). 3 | 4 | 5 | [Marty](http://martyjs.org) is a Javascript library for state management in React applications. It is an implementation of the [Flux architecture](http://facebook.github.io/flux/docs/overview.html). 6 | 7 | [![Join the chat at https://gitter.im/martyjs/marty](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/martyjs/marty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/jhollingworth.svg)](https://saucelabs.com/u/jhollingworth) 10 | 11 | ## Quick start 12 | 13 | ``` 14 | make build # rebuild source 15 | make docs # show documentation on http://localhost:4000 16 | ``` 17 | 18 | ## Releasing 19 | 20 | ``` 21 | make release # inc's patch, builds, creates tag, pushes to github and then publishes to npm 22 | make release inc={inc} # specify what to version part to increment (major, premajor, minor, preminor, patch, prepatch, prerelease) 23 | make release-docs # builds documentation and copies into ../marty-gh-pages 24 | ``` 25 | 26 | ## TypeScript 27 | 28 | A TypeScript definition is available at `marty.d.ts`. Please note that it requires the React definition from [DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped/blob/master/react/react.d.ts). 29 | 30 | ## Git Commit Messages 31 | 32 | * Use the present tense ("Add feature" not "Added feature") 33 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 34 | * Limit the first line to 72 characters or less 35 | * Reference issues and pull requests liberally 36 | * Consider starting the commit message with an applicable emoji: 37 | * :lipstick: `:lipstick:` when improving the format/structure of the code 38 | * :racehorse: `:racehorse:` when improving performance 39 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks 40 | * :memo: `:memo:` when writing docs 41 | * :penguin: `:penguin:` when fixing something on Linux 42 | * :apple: `:apple:` when fixing something on Mac OS 43 | * :checkered_flag: `:checkered_flag:` when fixing something on Windows 44 | * :bug: `:bug:` when fixing a bug 45 | * :fire: `:fire:` when removing code or files 46 | * :green_heart: `:green_heart:` when fixing the CI build 47 | * :white_check_mark: `:white_check_mark:` when adding tests 48 | * :lock: `:lock:` when dealing with security 49 | * :arrow_up: `:arrow_up:` when upgrading dependencies 50 | * :arrow_down: `:arrow_down:` when downgrading dependencies 51 | 52 | (From [atom](https://atom.io/docs/latest/contributing#git-commit-messages)) 53 | 54 | ## Maintainers 55 | 56 | * [James Hollingworth](http://github.com/jhollingworth) 57 | 58 | ## License 59 | 60 | * [MIT](https://raw.github.com/martyjs/marty/master/LICENSE) 61 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.11.0 -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marty", 3 | "main": "dist/marty.js", 4 | "homepage": "martyjs.org", 5 | "authors": [ 6 | "jhollingworth " 7 | ], 8 | "description": "A react.js/flux application framework", 9 | "keywords": [ 10 | "react.js", 11 | "flux" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests" 20 | ], 21 | "dependencies": { 22 | "es6-promise": "^2.0.0", 23 | "flux": "^2.0.1", 24 | "cookies-js": "^1.2.1", 25 | "isomorphic-fetch": "1.6.0", 26 | "lodash": "^3.5.0" 27 | }, 28 | "version": "0.11.0" 29 | } -------------------------------------------------------------------------------- /build/prerelease-docs.sh: -------------------------------------------------------------------------------- 1 | version=$(cat VERSION) 2 | 3 | cd docs 4 | VERSION=true jekyll build -d ../../marty-gh-pages/v/$version 5 | 6 | cd .. -------------------------------------------------------------------------------- /build/release-docs.sh: -------------------------------------------------------------------------------- 1 | version=$(cat VERSION) 2 | 3 | mkdir -p tmp/doc-versions 4 | 5 | #Copy the old versions of documentation for now 6 | 7 | cd docs 8 | 9 | cp -r ../../marty-gh-pages/v/ ../tmp/doc-versions 10 | bundle exec jekyll build -d ../../marty-gh-pages 11 | 12 | # Copy the old versions back again 13 | mkdir ../../marty-gh-pages/v/ 14 | cp -r ../tmp/doc-versions/* ../../marty-gh-pages/v/ 15 | rm -rf ../tmp/doc-versions 16 | echo martyjs.org > ../../marty-gh-pages/CNAME 17 | 18 | VERSION=true bundle exec jekyll build -d ../../marty-gh-pages/v/$version 19 | 20 | cd .. -------------------------------------------------------------------------------- /build/release.sh: -------------------------------------------------------------------------------- 1 | if [[ -n $(git status --porcelain) ]]; 2 | then 3 | echo "repo is dirty. please commit before releasing"; 4 | exit 1; 5 | fi 6 | 7 | echo "updating version" 8 | version="$(node build/updateVersion.js)" 9 | 10 | echo "building v$version" 11 | make build 12 | 13 | echo "commiting source" 14 | git add -A 15 | git commit -m "v$version" 16 | 17 | echo "creating tag v$version" 18 | git tag v$version 19 | git push origin master 20 | git push origin --tags 21 | 22 | echo "publishing to NPM" 23 | npm publish -------------------------------------------------------------------------------- /build/updateVersion.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var util = require('util'); 3 | var semver = require('semver'); 4 | var join = require('path').join; 5 | var inc = process.env.inc || 'patch'; 6 | var version = semver.inc(require('../package.json').version, inc); 7 | 8 | console.log(version) 9 | 10 | updateDocs(); 11 | updateMarty(); 12 | updateVersion(); 13 | ['../package.json', '../bower.json'].forEach(updateConfig); 14 | 15 | function updateDocs() { 16 | var config = read('../docs/_config.yml'); 17 | config = config.replace(/current_version: .*/, util.format("current_version: %s", version)); 18 | write('../docs/_config.yml', config); 19 | } 20 | 21 | function updateMarty() { 22 | var marty = read('../marty.js'); 23 | marty = marty.replace(/new Marty\('.*'/, util.format("new Marty('%s'", version)); 24 | write('../marty.js', marty); 25 | } 26 | 27 | function updateConfig(path) { 28 | var config = JSON.parse(read(path)); 29 | config.version = version; 30 | write(path, JSON.stringify(config, null, 2)); 31 | } 32 | 33 | function updateVersion() { 34 | write('../VERSION', version); 35 | } 36 | 37 | function write(path, data) { 38 | fs.writeFileSync(join(__dirname, path), data); 39 | } 40 | 41 | function read(path) { 42 | return fs.readFileSync(join(__dirname, path), 'utf-8'); 43 | } -------------------------------------------------------------------------------- /dist/marty.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/dist/marty.min.js.gz -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jekyll', '~>2.0' 4 | gem 'json' 5 | gem 'rb-fsevent' 6 | gem 'sanitize', '~>2.0' 7 | gem 'rouge' -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | blankslate (2.1.2.4) 5 | celluloid (0.16.0) 6 | timers (~> 4.0.0) 7 | classifier-reborn (2.0.3) 8 | fast-stemmer (~> 1.0) 9 | coffee-script (2.4.1) 10 | coffee-script-source 11 | execjs 12 | coffee-script-source (1.9.1) 13 | colorator (0.1) 14 | execjs (2.5.2) 15 | fast-stemmer (1.0.2) 16 | ffi (1.9.8) 17 | hitimes (1.2.2) 18 | jekyll (2.5.3) 19 | classifier-reborn (~> 2.0) 20 | colorator (~> 0.1) 21 | jekyll-coffeescript (~> 1.0) 22 | jekyll-gist (~> 1.0) 23 | jekyll-paginate (~> 1.0) 24 | jekyll-sass-converter (~> 1.0) 25 | jekyll-watch (~> 1.1) 26 | kramdown (~> 1.3) 27 | liquid (~> 2.6.1) 28 | mercenary (~> 0.3.3) 29 | pygments.rb (~> 0.6.0) 30 | redcarpet (~> 3.1) 31 | safe_yaml (~> 1.0) 32 | toml (~> 0.1.0) 33 | jekyll-coffeescript (1.0.1) 34 | coffee-script (~> 2.2) 35 | jekyll-gist (1.2.1) 36 | jekyll-paginate (1.1.0) 37 | jekyll-sass-converter (1.3.0) 38 | sass (~> 3.2) 39 | jekyll-watch (1.2.1) 40 | listen (~> 2.7) 41 | json (1.8.2) 42 | kramdown (1.6.0) 43 | liquid (2.6.2) 44 | listen (2.10.0) 45 | celluloid (~> 0.16.0) 46 | rb-fsevent (>= 0.9.3) 47 | rb-inotify (>= 0.9) 48 | mercenary (0.3.5) 49 | mini_portile (0.6.2) 50 | nokogiri (1.6.6.2) 51 | mini_portile (~> 0.6.0) 52 | parslet (1.5.0) 53 | blankslate (~> 2.0) 54 | posix-spawn (0.3.11) 55 | pygments.rb (0.6.3) 56 | posix-spawn (~> 0.3.6) 57 | yajl-ruby (~> 1.2.0) 58 | rb-fsevent (0.9.4) 59 | rb-inotify (0.9.5) 60 | ffi (>= 0.5.0) 61 | redcarpet (3.2.3) 62 | rouge (1.8.0) 63 | safe_yaml (1.0.4) 64 | sanitize (2.1.0) 65 | nokogiri (>= 1.4.4) 66 | sass (3.4.13) 67 | timers (4.0.1) 68 | hitimes 69 | toml (0.1.2) 70 | parslet (~> 1.5.0) 71 | yajl-ruby (1.2.1) 72 | 73 | PLATFORMS 74 | ruby 75 | 76 | DEPENDENCIES 77 | jekyll (~> 2.0) 78 | json 79 | rb-fsevent 80 | rouge 81 | sanitize (~> 2.0) 82 | 83 | BUNDLED WITH 84 | 1.10.4 85 | -------------------------------------------------------------------------------- /docs/_api/action-creators/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Action Creators API 4 | id: api-action-creators 5 | section: Action Creators 6 | --- 7 | {% sample %} 8 | classic 9 | ======= 10 | var UserActionCreators = Marty.createActionCreators({ 11 | addUser: function (name, email) { 12 | this.dispatch(UserActions.ADD_USER, name, email); 13 | } 14 | }); 15 | 16 | es6 17 | === 18 | class UserActionCreators extends Marty.ActionCreators { 19 | addUser(name, email) { 20 | this.dispatch(UserActions.ADD_USER, name, email); 21 | } 22 | } 23 | {% endsample %} 24 | 25 | 26 |

dispatch(type, [...])

27 | 28 | Dispatches an action payload with the given type. Any [action handlers]({% url /api/stores/index.html#handleAction %}) will be invoked with the given action handlers. 29 | 30 |

app

31 | 32 | Returns the instance's [application]({% url /api/application/index.html %}). -------------------------------------------------------------------------------- /docs/_api/app-mixin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | id: api-app-mixin 4 | title: App Mixin API 5 | section: App Mixin 6 | --- 7 | 8 | `Marty.createAppMixin()` returns a mixin that makes the application instance available in the component at `this.app`. 9 | 10 | {% highlight js %} 11 | var User = React.createClass({ 12 | mixins: [Marty.createAppMixin()], 13 | saveFoo() { 14 | this.app.fooActions.saveFoo(); 15 | }, 16 | deleteBar() { 17 | this.app.barActions.deleteBar(); 18 | } 19 | }) 20 | {% endhighlight %} 21 | -------------------------------------------------------------------------------- /docs/_api/application/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Application API 4 | id: api-application 5 | section: Application 6 | --- 7 | 8 |

dispatcher

9 | 10 | The application will create an instance of [dispatcher]({% url /guides/dispatcher/index.html %}) when first created. 11 | 12 |

register(id, type)

13 | 14 | Creates an instance of the given type, passing the id and application into the types constructor. The instance will then be accessible on the application object with the given Id. 15 | 16 | {% highlight js %} 17 | function Foo(options) { 18 | console.log(options.id, options.app); // foo, Application(...) 19 | } 20 | 21 | var app = new Marty.Application(); 22 | 23 | app.register('foo', Foo); 24 | {% endhighlight %} 25 | 26 |

register(registrations)

27 | 28 | Same as [register](#register) except you pass in an object literal where the keys are the Ids and the value is the type. If the value is an object literal when the instance will be accessible on the app within a child object. 29 | 30 | {% highlight js %} 31 | var app = new Marty.Application(); 32 | 33 | app.register({ 34 | foo: Foo, 35 | bar: { 36 | baz: Baz 37 | } 38 | }); 39 | 40 | console.log(app.bar.baz); 41 | {% endhighlight %} 42 | 43 |

replaceState(stores)

44 | 45 | Replaces ([Store#replaceState]({% url /api/stores/index.html#replaceState %})) the state of all stores with the values passed in. The key within the ``stores`` object literal must be the Id of the store. 46 | 47 | {% highlight js %} 48 | var app = new Marty.Application(); 49 | 50 | app.register('fooStore', require('./stores/fooStore')); 51 | app.register('barStore', require('./stores/barStore')); 52 | 53 | app.replaceState({ 54 | fooStore: { 55 | state: { 56 | [123]: { id: 123, name: 'Foo' } 57 | } 58 | }, 59 | barStore: { 60 | state: { 61 | [456]: { id: 456, name: 'Foo' } 62 | } 63 | } 64 | }); 65 | 66 | app.fooStore.getFoo(123) // { id: 123, name: 'Foo' } 67 | {% endhighlight %} 68 | 69 |

clearState()

70 | 71 | Calls [Store#clear]({% url /api/stores/index.html#clear %}) on all registered stores. 72 | 73 |

dehydrate()

74 | 75 | Calls [Store#dehydrate]({% url /api/stores/index.html#dehydrate %}) if present or [Store#getState]({% url /api/stores/index.html#getState %}) on all registered stores. Returning all states as a serializable object literal where they key is the Id of the store. 76 | 77 |

rehydrate([states])

78 | 79 | Given some dehyrdated state, it will call [Store#rehydrate]({% url /api/stores/index.html#rehydrate %}) if present or [Store#replaceState]({% url /api/stores/index.html#replaceState %}) on all registered stores passing in the dehyrdated state. The key of the states must match the Id of the store. If you don't pass in states then it will look at the ``window.__marty.state``. 80 | 81 |

renderToString(Component, options)

82 | 83 | [Renders](http://facebook.github.io/react/docs/top-level-api.html#react.rendertostring) the given element to a string, waits for all fetches to complete and then re-renders element. Returns a promise which resolves once element is re-rendered. Result of render is an object containing the html body and the state as a script tag. ``timeout`` allows you to configure how long to wait for a fetch to finish before re-rendering the component (Default **1000ms**). 84 | 85 | {% highlight js %} 86 | var app = new Application(); 87 | var User = require('./views/user'); 88 | 89 | app.renderToString(, { timeout: 2000}).then(function (res) { 90 | console.log('HTML body', res.htmlBody); 91 | console.log('HTML state', res.htmlState); 92 | console.log('Diagnostics', res.diagnostics); 93 | }); 94 | {% endhighlight %} 95 | 96 |

renderToStaticMarkup(Component, options)

97 | 98 | Same as [renderToString](#renderToString) except using [React.renderToStaticMarkup](https://facebook.github.io/react/docs/top-level-api.html#react.rendertostaticmarkup). 99 | 100 |

getAll(type)

101 | 102 | Get all instances of the given type. Result is an object literal where the keys is the instance id and the value is the instance. 103 | 104 | {% highlight js %} 105 | app.register({ 106 | fooStore: FooStore, 107 | barStore: { store: BarStore } 108 | }) 109 | 110 | app.getAll('Store') // => { 'fooStore': ..., 'bar.store': ... } 111 | {% endhighlight %} 112 | -------------------------------------------------------------------------------- /docs/_api/constants/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Constants API 4 | id: api-constants 5 | section: Constants 6 | --- 7 | 8 |

Marty.createConstants(Array)

9 | 10 | {% highlight js %} 11 | var Constants = Marty.createConstants([ 12 | 'RECEIVE_USERS', 13 | 'DELETE_USER' 14 | ]); 15 | 16 | //returns 17 | { 18 | RECEIVE_USERS: 'RECEIVE_USERS', 19 | RECEIVE_USERS_STARTING: 'RECEIVE_USERS_STARTING', 20 | RECEIVE_USERS_DONE: 'RECEIVE_USERS_DONE', 21 | RECEIVE_USERS_FAILED: 'RECEIVE_USERS_FAILED', 22 | DELETE_USER: 'DELETE_USER', 23 | DELETE_USER_STARTING: 'DELETE_USER_STARTING', 24 | DELETE_USER_DONE: 'DELETE_USER_DONE', 25 | DELETE_USER_FAILED: 'DELETE_USER_FAILED', 26 | } 27 | {% endhighlight %} 28 | 29 | 30 |

Marty.createConstants(Object)

31 | 32 | {% highlight js %} 33 | var Constants = Marty.createConstants({ 34 | Users: ['RECEIVE_USERS', 'DELETE_USER'], 35 | Foos: { 36 | Bars: ['ADD_BAR'] 37 | } 38 | }); 39 | 40 | //returns 41 | { 42 | Users: { 43 | RECEIVE_USERS: 'RECEIVE_USERS', 44 | RECEIVE_USERS_STARTING: 'RECEIVE_USERS_STARTING', 45 | RECEIVE_USERS_DONE: 'RECEIVE_USERS_DONE', 46 | RECEIVE_USERS_FAILED: 'RECEIVE_USERS_FAILED', 47 | DELETE_USER: 'DELETE_USER', 48 | DELETE_USER_STARTING: 'DELETE_USER_STARTING', 49 | DELETE_USER_DONE: 'DELETE_USER_DONE', 50 | DELETE_USER_FAILED: 'DELETE_USER_FAILED', 51 | }, 52 | Foos: { 53 | Bars: { 54 | ADD_BAR: 'ADD_BAR', 55 | ADD_BAR_STARTING: 'ADD_BAR_STARTING', 56 | ADD_BAR_DONE: 'ADD_BAR_DONE', 57 | ADD_BAR_FAILED: 'ADD_BAR_FAILED', 58 | } 59 | } 60 | } 61 | {% endhighlight %} 62 | -------------------------------------------------------------------------------- /docs/_api/containers/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | id: api-container 4 | title: Container API 5 | section: Container 6 | --- 7 | {% highlight js %} 8 | class User extends React.Component { 9 | render() { 10 | return
{this.props.user}
; 11 | } 12 | } 13 | 14 | module.exports = Marty.createContainer(User, { 15 | listenTo: 'userStore', 16 | fetch: { 17 | user() { 18 | return this.app.userStore.getUser(this.props.id); 19 | }, 20 | friends() { 21 | return this.app.userStore.getFriends(this.props.id); 22 | } 23 | }, 24 | failed(errors) { 25 | return
{errors}
; 26 | }, 27 | pending(results) { 28 | _.defaults(results, { 29 | user: DEFAULT_USER, 30 | friends: [] 31 | }); 32 | 33 | return this.done(results); 34 | } 35 | }); 36 | {% endhighlight %} 37 | 38 |

39 | createContainer options 40 |

41 | 42 |

listenTo

43 | 44 | Must be either a [store]({% url /api/stores/index.html %}) or an array of [stores]({% url /api/stores/index.html %}). When the store changes then all state is re-fetched and passed to inner component. 45 | 46 |

fetch

47 | 48 | ``fetch`` is an object hash. The value is a function which is invoked and the result is passed to the inner component as a prop. The prop key is determined by the key in the hash. 49 | 50 | ``fetch`` can also be a function. If it is then you must return an object hash where the values are the values you want to pass to the inner component. 51 | 52 | {% highlight js %} 53 | module.exports = Marty.createContainer(User, { 54 | listenTo: ['userStore'], 55 | fetch() { 56 | return { 57 | user: this.app.userStore.getUser(this.props.id); 58 | } 59 | } 60 | }); 61 | {% endhighlight %} 62 | 63 | If any of the values within the object hash are [fetch results]({% url /api/stores/index.html#fetch-result %}) then Marty will wait for the fetches to complete before rendering the inner component. Marty will call the [pending](#pending) handler if any of the fetches are pending and the [failed](#failed) handler if any have failed. 64 | 65 |

done(props)

66 | 67 | Creates the inner components, passing through the result of the [fetch](#fetch) via props. Override if you want more control about how the inner component is created. You should ensure the component should have the ref ``innerComponent``. 68 | 69 |

pending(finishedFetches)

70 | 71 | Invoked when any of the fetches are pending. Default is to return an empty ``div``. Any fetches that are done are passed into the `pending` handler as an object hash. 72 | 73 | Any fetch results that have finished are passed in. 74 | 75 |

failed(errors)

76 | 77 | Invoked when any of the fetches have failed. An object hash is passed in where the key is name of the fetch and the value is the error. Default is to throw an error. 78 | 79 | {% highlight js %} 80 | module.exports = Marty.createContainer(User, { 81 | listenTo: 'userStore' 82 | fetch() { 83 | return { 84 | user: this.app.userStore.getUser(this.props.id); 85 | } 86 | }, 87 | failed(errors) { 88 | console.error('Failed to fetch user', errors.user.message); 89 | } 90 | }); 91 | {% endhighlight %} 92 | 93 |

Lifecycle methods, accessors, and statics

94 | 95 | Other options passed in to `createContainer` are passed down to `React.createClass`. These can be used to specify additional lifecycle hooks (which will be combined with the ones for listening to stores as needed), public accessor methods, and statics. 96 | 97 | {% highlight js %} 98 | module.exports = Marty.createContainer(UserView, { 99 | statics: { 100 | willTransitionTo: UserView.willTransitionTo 101 | }, 102 | 103 | componentWillMount() { 104 | this.app.userQueries.subscribe(this.props.userId); 105 | }, 106 | 107 | getCustomValue() { 108 | return this.getInnerComponent().getCustomValue(); 109 | } 110 | }); 111 | {% endhighlight %} 112 | 113 |

114 | Container properties 115 |

116 | 117 |

getInnerComponent()

118 | 119 | Returns the inner component. 120 | 121 |

app

122 | 123 | Returns the instances application. 124 | -------------------------------------------------------------------------------- /docs/_api/inject-app/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | id: api-inject-app 4 | title: Inject App API 5 | section: Inject App 6 | --- 7 | 8 | `Marty.injectApp(Component)` adds a property to the component prototype to make the application instance available at `this.app`. 9 | 10 | {% highlight js %} 11 | class User extends React.Component { 12 | saveFoo() { 13 | this.app.fooActions.saveFoo(); 14 | } 15 | 16 | deleteBar() { 17 | this.app.barActions.deleteBar(); 18 | } 19 | }) 20 | 21 | Marty.injectApp(User); 22 | {% endhighlight %} 23 | -------------------------------------------------------------------------------- /docs/_api/queries/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Queries API 4 | id: api-queries 5 | section: Queries 6 | --- 7 | {% sample %} 8 | classic 9 | ======= 10 | var UserQueries = Marty.createQueries({ 11 | getUser: function (id) { 12 | this.dispatch(UserActions.RECEIVE_USER_STARTING, id); 13 | this.app.userAPI.getUser(id).then(function (res) { 14 | if (res.status === 200) { 15 | this.dispatch(UserActions.RECEIVE_USER, res.body, id); 16 | } else { 17 | this.dispatch(UserActions.RECEIVE_USER_FAILED, id); 18 | } 19 | }.bind(this)).catch(function (err) { 20 | this.dispatch(UserActions.RECEIVE_USER_FAILED, id, err); 21 | }.bind(this)) 22 | } 23 | }); 24 | 25 | es6 26 | === 27 | class UserQueries extends Marty.Queries { 28 | getUser(id) { 29 | this.dispatch(UserActions.RECEIVE_USER_STARTING, id); 30 | this.app.userAPI.getUser(id).then((res) => { 31 | if (res.status === 200) { 32 | this.dispatch(UserActions.RECEIVE_USER, res.body, id); 33 | } else { 34 | this.dispatch(UserActions.RECEIVE_USER_FAILED, id); 35 | } 36 | }).catch((err) => this.dispatch(UserActions.RECEIVE_USER_FAILED, id, err)); 37 | } 38 | } 39 | {% endsample %} 40 | 41 |

dispatch(type, [...])

42 | 43 | Dispatches an action payload with the given type. Any [action handlers]({% url /api/stores/index.html#handleAction %}) will be invoked with the given action handlers. 44 | 45 |

app

46 | 47 | Returns the instance's [application]({% url /api/application/index.html %}). -------------------------------------------------------------------------------- /docs/_api/state-mixin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | id: api-state-mixin 4 | title: State Mixin API 5 | section: State Mixin 6 | --- 7 |

listenTo

8 | 9 | Expects either a [store Id]({% url /api/stores/index.html %}) or an array of [store Ids]({% url /api/stores/index.html %}). Just before the [initial render](http://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount), it will register a change listener with the specified store(s). 10 | 11 | When the element is about to be [unmounted from the DOM](http://facebook.github.io/react/docs/component-specs.html#unmounting-componentwillunmount) it will dispose of an change listeners. 12 | 13 | {% highlight js %} 14 | var UserState = Marty.createStateMixin({ 15 | listenTo: ['userStore', 'fooStore'] 16 | }); 17 | {% endhighlight %} 18 | 19 |

getState

20 | 21 | The result of getState will be [set as the state of the bound component](http://facebook.github.io/react/docs/component-api.html#setstate). getState will be called when 22 | 23 | * [getInitialState](http://facebook.github.io/react/docs/component-specs.html#getinitialstate) is called 24 | * The components [props change](http://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate) 25 | * Any of the stores change 26 | 27 | {% highlight js %} 28 | var UserState = Marty.createStateMixin({ 29 | getState: function () { 30 | return { 31 | user: this.app.userStore.getUser(this.props.id) 32 | } 33 | } 34 | }); 35 | {% endhighlight %} 36 | 37 |

app

38 | 39 | Returns the instance's [application]({% url /api/application/index.html %}). -------------------------------------------------------------------------------- /docs/_api/state-sources/cookie.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Cookie State Source 4 | id: cookie-source-json 5 | section: State Sources 6 | --- 7 | 8 | Provides a simple way of accessing your cookies. Useful when creating [isomorphic applications]({% url /guides/isomorphism/index.html %}). 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var UserCookies = Marty.createStateSource({ 14 | type: 'cookie', 15 | login: function (user) { 16 | this.set(user.id, true) 17 | }, 18 | logout: function(id) { 19 | this.expire(id); 20 | } 21 | isLoggedIn: function (id) { 22 | return !!this.get(id); 23 | } 24 | }); 25 | 26 | es6 27 | === 28 | class UserCookies extends Marty.CookieStateSource { 29 | constructor(options) { 30 | super(options); 31 | } 32 | login(user) { 33 | this.set(user.id, true) 34 | }, 35 | logout(id) { 36 | this.expire(id); 37 | } 38 | isLoggedIn(id) { 39 | return !!this.get(id); 40 | } 41 | } 42 | {% endsample %} 43 | 44 |

get(key)

45 | 46 | Gets the item from the cookie the given key. 47 | 48 |

set(key, value)

49 | 50 | Sets the value to the cookie with the given key. 51 | 52 |

expire(key)

53 | 54 | Removes the key from the cookie. -------------------------------------------------------------------------------- /docs/_api/state-sources/http.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: HTTP State Source 4 | id: state-source-http 5 | section: State Sources 6 | --- 7 | 8 | Provides a simple way of making HTTP requests. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var UsersAPI = Marty.createStateSource({ 14 | type: 'http', 15 | createUser: function (user) { 16 | return this.post({ url: '/users', body: user }).then(function (res) { 17 | if (res.ok) { 18 | return res.json(); 19 | } 20 | 21 | throw new Error('Failed to create user', res); 22 | }); 23 | } 24 | }); 25 | 26 | es6 27 | === 28 | class UsersAPI extends Marty.HttpStateSource { 29 | createUser(user) { 30 | return this.post({ url: '/users', body: user }).then(res => { 31 | if (res.ok) { 32 | return res.json(); 33 | } 34 | 35 | throw new Error('Failed to create user', res); 36 | }); 37 | } 38 | } 39 | {% endsample %} 40 | 41 |

baseUrl

42 | 43 | An (optional) base url to prepend to any urls. 44 | 45 |

request(options)

46 | 47 | Starts an HTTP request with the given method and options. We use the [fetch](https://github.com/github/fetch) polyfill however you can override ``request()`` with your own implementation. The only requirement is it returns a Promise. 48 | 49 | {% sample %} 50 | classic 51 | ======= 52 | var UsersAPI = Marty.createStateSource({ 53 | type: 'http', 54 | createUser: function (user) { 55 | return this.request({ 56 | url: '/users', 57 | method: 'POST', 58 | body: { name: 'foo' }, 59 | contentType: 'application/json' 60 | }); 61 | } 62 | }); 63 | 64 | es6 65 | === 66 | class UsersAPI extends Marty.HttpStateSource { 67 | createUser(user) { 68 | return this.request({ 69 | url: '/users', 70 | method: 'POST', 71 | body: { name: 'foo' } 72 | }); 73 | } 74 | } 75 | {% endsample %} 76 | 77 |

Options

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
Nametypedefaultdescription
urlstringUrl of resource
methodstringgethttp method
headersobject{}http headers
contentTypestringapplication/jsonContent type of request
dataTypestringjsonThe type of data that you're expecting back from the server. xml, json, script, or html
121 | 122 |

get(url)

123 | 124 | Same as request({ method: 'GET', url: url }) 125 | 126 |

get(options)

127 | 128 | Same as request(_.extend(options, { method: 'GET'}) 129 | 130 |

post(url)

131 | 132 | Same as request({ method: 'POST', url: url }) 133 | 134 |

post(options)

135 | 136 | Same as request(_.extend(options, { method: 'POST'}) 137 | 138 |

put(url)

139 | 140 | Same as request({ method: 'PUT', url: url }) 141 | 142 |

put(options)

143 | 144 | Same as request(_.extend(options, { method: 'PUT'}) 145 | 146 |

delete(url)

147 | 148 | Same as request({ method: 'DELETE', url: url }) 149 | 150 |

delete(options)

151 | 152 | Same as request(_.extend(options, { method: 'DELETE'}) 153 | 154 |

Hooks

155 | 156 | Hooks allows you to make changes to requests before they are sent and as well as when responses are received. This can be useful when you want to do things like automatically converting all JSON responses to immutable objects. 157 | 158 | Hooks are object literals which have 4 optional keys: ``id``, ``before``, ``after`` and ``priority``. ``id`` is required if you wish to have multiple hooks registered. If ``before`` is present then it will be called with the request as its argument. If ``after`` is present then it will be called after the response is received with the response as its argument. Setting a priority allows you to alter in what order the hook is executed (The smaller the number, the earlier it will be executed). 159 | 160 | {% highlight js %} 161 | var Marty = require('marty'); 162 | 163 | Marty.HttpStateSource.addHook({ 164 | id: 'SomeHook', 165 | priority: 1, 166 | before(req) { 167 | req.headers['Foo'] = 'bar'; 168 | }, 169 | after(res) { 170 | return res.json(); 171 | } 172 | }); 173 | {% endhighlight %} 174 | 175 |

addHook

176 | 177 | Registers the hook in the pipeline 178 | 179 | {% highlight js %} 180 | var Marty = require('marty'); 181 | 182 | Marty.HttpStateSource.addHook({ 183 | id: 'AnotherHook', 184 | priority: 1, 185 | before: function (req) { 186 | req.headers['Foo'] = 'bar'; 187 | }, 188 | after: function (res) { 189 | return res.json(); 190 | } 191 | }); 192 | {% endhighlight %} 193 | 194 |

removeHook

195 | 196 | Removes the hook from the pipline. 197 | 198 | {% highlight js %} 199 | var Marty = require('marty'); 200 | var ParseJSON = require('marty/http/hooks/parseJSON'); 201 | 202 | Marty.HttpStateSource.removeHook(ParseJSON); 203 | {% endhighlight %} 204 | -------------------------------------------------------------------------------- /docs/_api/state-sources/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: State Sources API 4 | id: api-state-sources 5 | section: State Sources 6 | --- 7 | 8 | {% sample %} 9 | classic 10 | ======= 11 | var UsersAPI = Marty.createStateSource({ 12 | createUser: function (user) { 13 | return $.get("/users"); 14 | } 15 | }); 16 | 17 | es6 18 | === 19 | class UsersAPI extends Marty.StateSource { 20 | createUser(user) { 21 | return $.get("/users"); 22 | } 23 | } 24 | {% endsample %} 25 | 26 |

type

27 | 28 | The type of the state source (e.g. 'http'). 29 | 30 |

mixins

31 | 32 | An (optional) array of mixins that can be passed in through the createStateSource method. 33 | 34 |

app

35 | 36 | Returns the instance's [application]({% url /api/application/index.html %}). -------------------------------------------------------------------------------- /docs/_api/state-sources/json-storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: JSON Storage State Source 4 | id: state-source-json 5 | section: State Sources 6 | --- 7 | 8 | Provides a simple way of storing JSON objects in local or session storage. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var UserStorage = Marty.createStateSource({ 14 | namespace: 'users', 15 | type: 'jsonStorage', 16 | createUser: function (user) { 17 | this.set(user.id, user) 18 | }, 19 | getUser: function (id) { 20 | return this.get(id); 21 | } 22 | }); 23 | 24 | es6 25 | === 26 | class UserStorage extends Marty.JSONStorageStateSource { 27 | constructor(options) { 28 | super(options); 29 | this.namespace = 'users'; 30 | } 31 | createUser(user) { 32 | this.set(user.id, user); 33 | } 34 | getUser(id) { 35 | return this.get(id); 36 | } 37 | } 38 | {% endsample %} 39 | 40 |

storage

41 | 42 | The web storage to use, can be [localStorage](https://developer.mozilla.org/en/docs/Web/Guide/API/DOM/Storage#localStorage) or [sessionStorage](https://developer.mozilla.org/en/docs/Web/Guide/API/DOM/Storage#sessionStorage). Default is [localStorage](https://developer.mozilla.org/en/docs/Web/Guide/API/DOM/Storage#localStorage). 43 | 44 |

namespace

45 | 46 | An (optional) prefix for keys. 47 | 48 |

get(key)

49 | 50 | Gets the item in the storage with the given key. If the item exists, it will deserialize it. 51 | 52 |

set(key, obj)

53 | 54 | Serializes the object to a JSON string before and then inserts into the storage with the given key. -------------------------------------------------------------------------------- /docs/_api/state-sources/local-storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Local Storage State Source 4 | id: state-source-local 5 | section: State Sources 6 | --- 7 | 8 | Provides a simple way of storing state in local storage. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var FooStorage = Marty.createStateSource({ 14 | namespace: 'foos', 15 | type: 'localStorage', 16 | saveFoo: function (foo) { 17 | this.set('bar', foo); 18 | }, 19 | getFoo: function (id) { 20 | return this.get(id); 21 | } 22 | }); 23 | 24 | es6 25 | === 26 | class FooStorage extends Marty.LocalStorageStateSource { 27 | constructor(options) { 28 | super(options); 29 | this.namespace = 'foos'; 30 | } 31 | saveFoo(foo) { 32 | this.set('bar', foo); 33 | } 34 | getFoo(id) { 35 | return this.get(id); 36 | } 37 | } 38 | {% endsample %} 39 | 40 |

namespace

41 | 42 | An (optional) prefix for keys. 43 | 44 |

get(key)

45 | 46 | Gets the item in the storage with the given key. If the item exists, it will deserialize it. 47 | 48 |

set(key, obj)

49 | 50 | Serializes the object to a JSON string before and then inserts into the storage with the given key. -------------------------------------------------------------------------------- /docs/_api/state-sources/location.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Location State Source 4 | id: location-state-source 5 | section: State Sources 6 | --- 7 | 8 | Provides a simple way of accessing your current location. Useful when creating [isomorphic applications]({% url /guides/isomorphism/index.html %}). 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var Location = Marty.createStateSource({ 14 | type: 'location' 15 | }); 16 | es6 17 | === 18 | class Location extends Marty.LocationStateSource { 19 | } 20 | {% endsample %} 21 | 22 |

getLocation()

23 | 24 | Returns information about the current location: 25 | 26 | * **url** e.g. http://foo.com/bar?baz=bam 27 | * **path** e.g. /bar 28 | * **hostname** e.g. foo.com 29 | * **query** e.g. ``{ baz: 'bam' }`` 30 | * **protocol** e.g. http -------------------------------------------------------------------------------- /docs/_api/state-sources/session-storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Session Storage State Source 4 | id: state-source-session 5 | section: State Sources 6 | --- 7 | 8 | Provides a simple way of storing state in session storage. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var FooStorage = Marty.createStateSource({ 14 | namespace: 'foos', 15 | type: 'sessionStorage', 16 | saveFoo: function (foo) { 17 | this.set('bar', foo); 18 | }, 19 | getFoo: function (id) { 20 | return this.get(id); 21 | } 22 | }); 23 | 24 | es6 25 | === 26 | class FooStorage extends Marty.SessionStorageStateSource { 27 | constructor(options) { 28 | super(options); 29 | this.namespace = 'foos'; 30 | } 31 | saveFoo(foo) { 32 | this.set('bar', foo); 33 | } 34 | getFoo(id) { 35 | return this.get(id); 36 | } 37 | } 38 | {% endsample %} 39 | 40 |

namespace

41 | 42 | An (optional) prefix for keys. 43 | 44 |

get(key)

45 | 46 | Gets the item in the storage with the given key. If the item exists, it will deserialize it. 47 | 48 |

set(key, obj)

49 | 50 | Serializes the object to a JSON string before and then inserts into the storage with the given key. -------------------------------------------------------------------------------- /docs/_api/test-utils/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Test Utils 4 | id: top-level 5 | section: Test Utils 6 | --- 7 | 8 | Marty's test utils are available by requiring `marty/test-utils`. For full examples see [marty-test-examples](https://github.com/martyjs/marty-test-examples). 9 | 10 | {% highlight js %} 11 | var TestUtils = require('marty/test-utils'); 12 | 13 | var app = TestUtils.createApplication(Application, { 14 | ... 15 | }); 16 | {% endhighlight %} 17 | 18 |

createApplication(ApplicationType, options)

19 | 20 | Creates an instance of the application with the given type while giving you the opportunity to control what actually gets instantiated. 21 | 22 |

Options

23 | 24 | {% highlight js %} 25 | var TestUtils = require('marty/test-utils'); 26 | 27 | var app = TestUtils.createApplication(Application, { 28 | include: ['foo', 'bar'], 29 | exclude: ['bar', 'baz'], 30 | stub: { 31 | fooAPI: { 32 | getFoo: sinon.stub().returns(Promise.done({ id: 123 })) 33 | } 34 | } 35 | }); 36 | {% endhighlight %} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Nametypedescription
stubobjectWhen registering, instead of creating an instance of the type it will use the stub instead
includestring arrayWill only create instances with Ids that are in this array
excludestring arrayWill only create instances with Ids that are not in this array
64 | 65 |

createStore(properties)

66 | 67 | Creates a mock store with the given properties. Useful when stubbing out a store. 68 | 69 | {% highlight js %} 70 | var app = TestUtils.createApplication(Application, { 71 | stub: { 72 | fooStore: TestUtils.createStore({ 73 | getFoo: sinon.stub() 74 | }) 75 | } 76 | }); 77 | {% endhighlight %} 78 | 79 |

dispatch(app, type, ...args)

80 | 81 | Dispatches an action with given type and arguments from the applications dispatcher. 82 | 83 | {% highlight js %} 84 | var app = new Application(); 85 | 86 | TestUtils.dispatch(app, "RECEIVE_USER", { id: 123 }); 87 | {% endhighlight %} 88 | 89 |

getDispatchedActionsWithType(app, type)

90 | 91 | Returns all actions that have been dispatched with that type. 92 | 93 | {% highlight js %} 94 | var actions = TestUtils.getDispatchedActionsWithType(app, "RECEIVE_USER"); 95 | {% endhighlight %} 96 | 97 |

hasDispatched(app, type, ...args)

98 | 99 | Returns true if an action with the given type and arguments has been dispatched. 100 | 101 | {% highlight js %} 102 | expect(TestUtils.hasDispatched(app, "RECEIVE_USER", { id: 123 })).to.be.true; 103 | {% endhighlight %} 104 | 105 | -------------------------------------------------------------------------------- /docs/_api/top-level-api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Top-Level API 4 | id: top-level 5 | section: Top-Level API 6 | --- 7 | 8 |

Marty.createApplication(constructor)

9 | 10 | Creates a [application]({% url /api/application/index.html %}) class with the given constructor. 11 | 12 |

Marty.createStore(properties)

13 | 14 | Creates a [store]({% url /api/stores/index.html %}) class with the given properties. 15 | 16 |

Marty.createQueries(properties)

17 | 18 | Register [queries]({% url /api/queries/index.html %}) class with the given properties. 19 | 20 |

Marty.createStateSource(properties)

21 | 22 | Register a [state source]({% url /api/state-source/index.html %}) class with the given properties. 23 | 24 |

Marty.createActionCreators(properties)

25 | 26 | Register [action creators]({% url /api/action-creators/index.html %}) class with the given properties. 27 | 28 |

Marty.createStateMixin(options)

29 | 30 | Creates a [state mixin]({% url /guides/state-mixin/index.html %}). 31 | 32 |

Marty.createConstants(options)

33 | 34 | Creates [constants]({% url /guides/constants/index.html %}). 35 | 36 |

Marty.createContainer(InnerComponent, options)

37 | 38 | Wraps the component with a [container component]({% url /guides/containers/index.html %}) that is responsible for fetching state from stores and passing it to the inner component. 39 | 40 |

Marty.isServer

41 | 42 | True if the current process is being executed within node.js or io.js. 43 | 44 |

Marty.isBrowser

45 | 46 | True if the current process is being executed within a browser. 47 | 48 |

Marty.warnings

49 | 50 | Configurable list of warnings that Marty emits. Setting the key to false will stop the warning from happening. We will warn you when a feature is being deprecated so disabling warnings can make upgrading difficult in the future. 51 | 52 | {% highlight js %} 53 | Marty.warnings.reservedFunction = false; 54 | {% endhighlight %} 55 | 56 |

Marty.warnings.without(warningsToDisable*, callback, [context])

57 | 58 | Disables a warning for the duration of the callback. 59 | 60 | {% highlight js %} 61 | Marty.warnings.without(['reservedFunction', 'superNotCalledWithOptions'], function () { 62 | // do something evil 63 | }); 64 | {% endhighlight %} 65 | 66 |

Marty.version

67 | 68 | The current version of Marty. 69 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Marty 3 | description: A React.js/Flux Framework 4 | url: http://martyjs.org 5 | baseurl: / 6 | permalink: "/blog/:year/:month/:day/:title.html" 7 | paginate_path: "/blog/page:num/" 8 | relative_permalinks: true 9 | paginate: 5 10 | timezone: America/Los_Angeles 11 | highlighter: rouge 12 | defaults: 13 | - scope: 14 | path: '' 15 | type: page 16 | values: 17 | layout: post 18 | - scope: 19 | path: docs 20 | type: page 21 | values: 22 | layout: docs 23 | exclude: 24 | - Gemfile 25 | - Gemfile.lock 26 | - README.md 27 | - Rakefile 28 | markdown: redcarpet 29 | redcarpet: 30 | extensions: 31 | - fenced_code_blocks 32 | sass: 33 | style: :compressed 34 | sass_dir: _css 35 | collections: 36 | guides: 37 | title: Guides 38 | output: true 39 | api: 40 | title: API 41 | output: true 42 | support: 43 | title: Support 44 | output: true 45 | devtools: 46 | title: Developer Tools 47 | output: true 48 | current_version: 0.11.0 -------------------------------------------------------------------------------- /docs/_data/api_section_order.yml: -------------------------------------------------------------------------------- 1 | - Top-Level API 2 | - Application 3 | - Stores 4 | - Action Creators 5 | - Queries 6 | - Containers 7 | - Inject App 8 | - State Mixin 9 | - App Mixin 10 | - State Sources 11 | - Constants 12 | - Test Utils 13 | -------------------------------------------------------------------------------- /docs/_data/guides_section_order.yml: -------------------------------------------------------------------------------- 1 | - Getting Started 2 | - Flux 3 | - Marty Native 4 | - Application 5 | - Stores 6 | - Fetching state 7 | - Action Creators 8 | - Queries 9 | - Containers 10 | - State Mixin 11 | - State Sources 12 | - Isomorphism 13 | - Developer Tools 14 | - Constants 15 | - Dispatcher 16 | - Upgrading 17 | - Further reading -------------------------------------------------------------------------------- /docs/_devtools/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | sectionid: devtools 4 | --- 5 | 6 |
7 |

Marty Developer Tools

8 |

9 | Marty Developer Tools is an extension to Chrome's developer tools. It shows you the current state of your stores, actions that have flowed through the application as well as allowing you to revert to an earlier state. For each action we will show: 10 |

11 |
    12 |
  • Which stores handled the action
  • 13 |
  • What arguments were passed to the stores action handler
  • 14 |
  • What components re-rendered as a result of the action (and how many)
  • 15 |
  • Which stores caused a component to re-render
  • 16 |
17 | 18 |

19 | Download from Chrome Web Store 20 |

21 | 22 | Marty Developer Tools 23 | 24 | For Marty Developer Tools to work, you add Marty to the window object (window.Marty = require('marty')). Once you've done that, open your app in Chrome and open Chrome Developer Tools. You should see a new 'Marty' tab which gives you a view into what your application is doing. 25 | 26 |
-------------------------------------------------------------------------------- /docs/_guides/action-creators/actions-vs-action-creators.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Actions vs. Action Creators 4 | id: actions-vs-action-creators 5 | section: Action Creators 6 | --- 7 | 8 | There has been some confusion around actions and action creators. In the [original blog announcing Flux](http://facebook.github.io/flux/docs/overview.html), [action creators were called actions](http://facebook.github.io/flux/docs/overview.html#actions). Since then, the creators of Flux have [decided to rename actions to action creators](https://groups.google.com/d/msg/reactjs/jBPHH4Q-8Sc/zwObiX9UT2EJ) because "they create actions but are not themselves actually the action itself". 9 | 10 | Ultimately this is just semantics, so actions and action creators are terms that can be used interchangeably. 11 | -------------------------------------------------------------------------------- /docs/_guides/action-creators/handling-errors.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Handling Errors 4 | id: handling-errors-action-creators 5 | section: Action Creators 6 | --- 7 | 8 | Sooner or later you're going to create an action that has the potential to fail. Whether theres a fault in the system or you have validation errors you will need a way to handle these errors. Thanks to the [HTTP state source]({% url /api/state-sources/http.html %}) returning promises this process is relatively straightforward. All you need to do is add a ``catch`` callback to the promise returned from the state source and then deal with the error in there. ``Marty.createConstants(...)`` will automatically create a ``{action type}_FAILED`` constant for you if you want to dispatch the error to the rest of the system. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var UserActionCreators = Marty.createActionCreators({ 14 | saveUser: function (user) { 15 | this.dispatch(UserConstants.SAVE_USER, user); 16 | 17 | this.app.userAPI.saveUser(user).then(function () { 18 | this.dispatch(UserConstants.SAVE_USER_DONE, user); 19 | }.bind(this)).catch(function (err) { 20 | this.dispatch(UserConstants.SAVE_USER_FAILED, user, err); 21 | }.bind(this)); 22 | } 23 | }); 24 | 25 | es6 26 | === 27 | class UserActionCreators extends Marty.ActionCreators { 28 | saveUser(user) { 29 | this.dispatch(UserConstants.SAVE_USER, user); 30 | 31 | this.app.userAPI.saveUser(user) 32 | .then(() => this.dispatch(UserConstants.SAVE_USER_DONE, user)) 33 | .catch((err) => this.dispatch(UserConstants.SAVE_USER_FAILED, user, err)); 34 | } 35 | } 36 | {% endsample %} 37 | -------------------------------------------------------------------------------- /docs/_guides/action-creators/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Action Creators 4 | id: action-creators 5 | section: Action Creators 6 | --- 7 | 8 | Action Creators are where any changes to your applications state start. Actions are functions that are responsible for coordinating changes to local and remote state. 9 | 10 | All actions have a type of string which gives a terse description of what the action does (e.g. "UPDATE\_USER_EMAIL"). Stores listen for new actions (using the [dispatcher]({% url /guides/dispatcher/index.html %})) and use [action's type to determine whether to do something with it]({% url /api/stores/index.html#handlers %}). Action types help us build loosely coupled applications that can grow without increasing complexity. 11 | 12 | To create an action, you should pass its type followed by any arguments to the [``dispatch``]({% url /api/actionCreators/index.html#dispatch %}) function. 13 | 14 | {% sample %} 15 | classic 16 | ======= 17 | var UserConstants = Marty.createConstants(["UPDATE_EMAIL"]); 18 | 19 | var UserActionCreators = Marty.createActionCreators({ 20 | updateEmail: function (userId, email) { 21 | this.dispatch(UserConstants.UPDATE_EMAIL, userId, email) 22 | } 23 | }); 24 | 25 | var app = new Marty.Application(); 26 | 27 | app.register('userActionCreators', UserActionCreators); 28 | app.dispatcher.register(function (action) { 29 | console.log(action.type) // => "UPDATE_EMAIL" 30 | console.log(action.arguments) // => [123, "foo@bar.com"]; 31 | }); 32 | 33 | app.userActionCreators.updateEmail(123, "foo@bar.com"); 34 | 35 | es6 36 | === 37 | var UserConstants = Marty.createConstants(["UPDATE_EMAIL"]); 38 | 39 | class UserActionCreators extends Marty.ActionCreators { 40 | updateEmail(userId, email) { 41 | this.dispatch(UserConstants.UPDATE_EMAIL, userId, email) 42 | } 43 | } 44 | 45 | var app = new Marty.Application(); 46 | 47 | app.register('userActionCreators', UserActionCreators); 48 | app.dispatcher.register(function (action) { 49 | console.log(action.type) // => "UPDATE_EMAIL" 50 | console.log(action.arguments) // => [123, "foo@bar.com"]; 51 | }); 52 | 53 | app.userActionCreators.updateEmail(123, "foo@bar.com"); 54 | 55 | {% endsample %} 56 | -------------------------------------------------------------------------------- /docs/_guides/action-creators/migrating-from-v8.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Migrating from Marty v0.8 4 | id: migrating-action-creators-0.8 5 | section: Action Creators 6 | --- 7 | 8 | In Marty v0.8 and below we had a different way of defining an action creators type which has now been deprecated: 9 | 10 | {% highlight js %} 11 | var UserActionCreators = Marty.createActionCreators({ 12 | updateEmail: UserConstants.UPDATE_EMAIL(function (userId, email) { 13 | this.dispatch(userId, email); 14 | }), 15 | deleteEmail: UserConstants.DELETE_EMAIL() 16 | }); 17 | {% endhighlight %} 18 | 19 | ##Why was it deprecated? 20 | 21 | There were 3 reasons: 22 | 1. Automatic error handling made debugging really difficult ([#127](https://github.com/martyjs/marty/issues/127)). 23 | 2. Automatically dispatched actions were confusing and not that helpful ([#157](https://github.com/martyjs/marty/issues/157), [#152](https://github.com/martyjs/marty/issues/152)). 24 | 3. It didn't play nicely with ES6 classes 25 | 26 | We decided this code wasn't adding any value so we should move towards a simpler more explicit approach. 27 | 28 | In v0.9 you will see warnings in your code to move to the new style and we will remove the code entirely in v0.10. 29 | 30 | ##How do I migrate my code to the new style? 31 | 32 | All you need to do is move the constant to being the first argument of the dispatch function. If you utilizing auto dispatch constants then you will need to use ``marty/autoDispatch``. 33 | 34 | {% highlight js %} 35 | var autoDispatch = require('marty/autoDispatch'); 36 | 37 | var UserActionCreators = Marty.createActionCreators({ 38 | updateEmail: function (userId, email) { 39 | this.dispatch(UserConstants.UPDATE_EMAIL, userId, email); 40 | }), 41 | deleteEmail: autoDispatch(UserConstants.DELETE_EMAIL) 42 | }); 43 | {% endhighlight %} -------------------------------------------------------------------------------- /docs/_guides/application/automatic-registration.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Automatically register dependencies 4 | id: automatically-register-dependencies 5 | section: Application 6 | --- 7 | 8 | It's easy to forget to register a type in the application after you create it. Fortunately if you're using [webpack](http://webpack.github.io/) or [browserify](http://browserify.org) we can automate the registration process. 9 | 10 |

browserify

11 | 12 | You first need to install [bulk-require](https://github.com/substack/bulk-require) and the [bulkify](https://github.com/substack/bulkify) transformation (Don't forget to add the transform to your browserify config). 13 | 14 | {% highlight bash %} 15 | npm install --save-dev bulk-require bulkify 16 | {% endhighlight %} 17 | 18 | Then all you need to do is call `bulk-require` from within your application 19 | 20 | {% highlight js %} 21 | let { each } = require('lodash'); 22 | let bulk = require('bulk-require'); 23 | 24 | class Application extends Marty.Application { 25 | constructor(options) { 26 | super(options); 27 | 28 | let dependencies = bulk(__dirname, [ 29 | 'stores/*.js', 30 | 'actions/*.js', 31 | 'queries/*.js', 32 | 'sources/*.js' 33 | ]); 34 | 35 | each(dependencies, dep => this.register(dep)); 36 | } 37 | {% endhighlight %} 38 | 39 |

webpack

40 | 41 | Thanks to [webpack's dynamic require](http://webpack.github.io/docs/context.html) you don't need to install any dependencies. You just need to do this: 42 | 43 | {% highlight js %} 44 | // Dynamically require in everything within the 'actions', 'queries', 'sources' and 'stores' folders 45 | let context = require.context("./", true, /(actions|queries|sources|stores)/); 46 | 47 | class Application extends Marty.Application { 48 | constructor(options) { 49 | super(options); 50 | 51 | // Iterate through all the JS files in those folders 52 | context.keys().forEach((key) => { 53 | if (!/\.js/.test(key)) { 54 | // Generate an Id based on directory structure. 55 | let id = key.replace('./', '').replace(/\//g, '.'); 56 | 57 | this.register(id, context(key)); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | module.exports = Application; 64 | {% endhighlight %} 65 | -------------------------------------------------------------------------------- /docs/_guides/application/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Application 4 | id: application 5 | section: Application 6 | --- 7 | 8 | The `Application` is responsible for knowing about and instantiating all Stores, Action Creators, Queries, and State sources within your application. It also plays a central role in building [isomorphic applications]({% url /guides/isomorphism/index.html %}). 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | // application.js 14 | var Application = Marty.createApplication(function () { 15 | this.register('userStore', require('./stores/userStore')); 16 | }); 17 | 18 | // views/user.js 19 | ... 20 | 21 | module.exports = Marty.createContainer(User, { 22 | listenTo: 'userStore', 23 | fetch: { 24 | user: function () { 25 | return this.app.userStore.getUser(); 26 | } 27 | } 28 | }); 29 | 30 | // main.js 31 | var app = new Application(); 32 | var ApplicationContainer = require('marty').ApplicationContainer; 33 | 34 | React.render(( 35 | 36 | 37 | 38 | ), document.body); 39 | es6 40 | === 41 | // application.js 42 | class Application extends Marty.Application { 43 | constructor(options) { 44 | super(options); 45 | 46 | this.register('userStore', require('./stores/userStore')); 47 | } 48 | } 49 | 50 | // views/user.js 51 | ... 52 | 53 | module.exports = Marty.createContainer(User, { 54 | listenTo: 'userStore', 55 | fetch: { 56 | user() { 57 | return this.app.userStore.getUser(); 58 | } 59 | } 60 | }); 61 | 62 | // main.js 63 | var app = new Application(); 64 | var { ApplicationContainer } = require('marty'); 65 | 66 | React.render(( 67 | 68 | 69 | 70 | ), document.body); 71 | {% endsample %} 72 | 73 | You use `register` to tell the application about all the different types in your application. The two things you must specify are an Id (string) and a type (e.g. a `Store` or an `ActionCreator`). Internally `register` will create an instance of that type, passing the application instance into the constructor and then making it accessible on the application object. 74 | 75 | You can also pass in an object literal to `register`, allowing you to register multiple types at once as well. If the object values are themselves objects then their Id's will be the path to the leaf node joined by `.`'s. 76 | 77 | Having to remember to register types when you create them can become a bit of a chore. If you're using [webpack](http://webpack.github.io/) or [browserify](http://browserify.org) you can [automate the registration process]({% url /guides/application/automatic-registration.html %})! 78 | 79 | {% sample %} 80 | classic 81 | ======= 82 | var Application = Marty.createApplication(function () { 83 | this.register({ 84 | foo: { 85 | bar: { 86 | baz: BazStore 87 | } 88 | } 89 | }); 90 | }); 91 | 92 | var app = new Application(); 93 | 94 | console.log(app.foo.bar.baz.id, 'foo.bar.baz'); 95 | 96 | es6 97 | === 98 | class Application extends Marty.Application { 99 | constructor(options) { 100 | super(options); 101 | 102 | this.register({ 103 | foo: { 104 | bar: { 105 | baz: BazStore 106 | } 107 | } 108 | }); 109 | } 110 | } 111 | 112 | var app = new Application(); 113 | 114 | console.log(app.foo.bar.baz.id, 'foo.bar.baz'); 115 | 116 | {% endsample %} 117 | 118 | Once you have an instance of an application you need to make accessible to your components. To do this you wrap your root component in an `ApplicationContainer` which you must pass the application instance to via the `app` prop. The `ApplicationContainer` will make the available to any children via [contexts](https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html)). 119 | 120 | You can access the application instance by calling `this.app` in Stores, Action Creators, Queries, and State sources. This is also true for components as long as its wrapped in a container or you're using the [state mixin]({% url /guides/state-mixin/index.html %})/[app mixin]({% url /api/app-mixin/index.html %}). 121 | 122 | {% sample %} 123 | classic 124 | ======= 125 | //views/user.js 126 | var User = React.createClass({ 127 | saveUser: function () { 128 | this.app.userActions.saveUser({ 129 | id: this.props.user.id 130 | }); 131 | } 132 | }) 133 | 134 | module.exports = Marty.createContainer(User); 135 | 136 | //main.js 137 | var app = new Application(); 138 | var ApplicationContainer = require('marty').ApplicationContainer; 139 | 140 | React.render(( 141 | 142 | 143 | 144 | ), document.body); 145 | es6 146 | === 147 | //views/user.js 148 | class User extends React.Component { 149 | saveUser() { 150 | this.app.userActions.saveUser({ 151 | id: this.props.user.id 152 | }); 153 | } 154 | } 155 | 156 | module.exports = Marty.createContainer(User); 157 | 158 | //main.js 159 | var app = new Application(); 160 | var { ApplicationContainer } = require('marty'); 161 | 162 | React.render(( 163 | 164 | 165 | 166 | ), document.body); 167 | {% endsample %} 168 | 169 | -------------------------------------------------------------------------------- /docs/_guides/constants/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Constants 4 | id: constants 5 | section: Constants 6 | --- 7 | 8 | [Actions]({% url /guides/action-creators/index.html %}) must have a type which is a terse description of what the action does (e.g. ``"DELETE_USER"``). The main purpose of action types is that they allow you to loosely couple your actions to your stores ([Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter)). 9 | 10 | As your application grows you might find that it becomes littered with these strings, making it difficult to refactor and understand at a high level what actions are available. Marty provides **Constants** as a solution to this problem. 11 | 12 | ``Marty.createConstants`` will create an object literal where the key is the action type. The value is also the action type. We also create a few extra constants for you so that you can dispatch the various states an action can be in (e.g. starting, done, failed). 13 | 14 | {% highlight js %} 15 | var UserConstants = Marty.createConstants([ 16 | "ADD_USER", 17 | "DELETE_USER", 18 | "UPDATE_USER_EMAIL" 19 | ]); 20 | 21 | // returns 22 | { 23 | ADD_USER: 'ADD_USER', 24 | ADD_USER_STARTING: 'ADD_USER_STARTING', 25 | ADD_USER_DONE: 'ADD_USER_DONE', 26 | ADD_USER_FAILED: 'ADD_USER_FAILED', 27 | 28 | DELETE_USER: 'DELETE_USER', 29 | DELETE_USER_STARTING: 'DELETE_USER_STARTING', 30 | DELETE_USER_DONE: 'DELETE_USER_DONE', 31 | DELETE_USER_FAILED: 'DELETE_USER_FAILED', 32 | 33 | UPDATE_USER_EMAIL: 'UPDATE_USER_EMAIL', 34 | UPDATE_USER_STARTING: 'UPDATE_USER_STARTING', 35 | UPDATE_USER_DONE: 'UPDATE_USER_DONE', 36 | UPDATE_USER_FAILED: 'UPDATE_USER_FAILED', 37 | } 38 | {% endhighlight %} 39 | -------------------------------------------------------------------------------- /docs/_guides/containers/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Containers 4 | id: container 5 | section: Containers 6 | --- 7 | 8 | We found that there was a lot of boilerplate code in React components for listening to [stores]({% url /guides/stores/index.html %}) and getting state from them. Containers provide a simpler way for your components to interact with stores. They do this by wrapping your component in another component (Called a **container component**) which is responsible for fetching state from the stores which it then passes to the inner component via props. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var User = React.createClass({ 14 | render: function() { 15 | return
{this.props.user}
; 16 | } 17 | }); 18 | 19 | module.exports = Marty.createContainer(User, { 20 | listenTo: 'userStore', 21 | fetch: { 22 | user: function() { 23 | return this.app.userStore.getUser(this.props.id); 24 | } 25 | }, 26 | failed(errors) { 27 | return
{errors}
; 28 | }, 29 | pending() { 30 | return this.done({ 31 | user: {} 32 | }); 33 | } 34 | }); 35 | 36 | es6 37 | === 38 | class User extends React.Component { 39 | render() { 40 | return
{this.props.user}
; 41 | } 42 | } 43 | 44 | module.exports = Marty.createContainer(User, { 45 | listenTo: 'userStore', 46 | fetch: { 47 | user() { 48 | return this.app.userStore.getUser(this.props.id); 49 | } 50 | }, 51 | failed(errors) { 52 | return
{errors}
; 53 | }, 54 | pending() { 55 | return this.done({ 56 | user: {} 57 | }); 58 | } 59 | }); 60 | {% endsample %} 61 | 62 | To create a container, pass your component to [``Marty.createContainer``]({% url /api/top-level-api/index.html#createContainer %}) with an object hash that contains the container configuration. [``Marty.createContainer``]({% url /api/top-level-api/index.html#createContainer %}) will return a new component which knows how to fetch state from stores as well as rendering it. The two most important configuration option are [``fetch``]({% url /api/containers/index.html#fetch %}) and [``listenTo``]({% url /api/containers/index.html#listenTo %}). 63 | 64 | [``fetch``]({% url /api/containers/index.html#fetch %}) is an object where the values are functions which are invoked and the result is passed to the inner component as a prop. The prop key is determined by the key. 65 | 66 | Stores might not immediately have all the data immediately and so we need to re-invoke the `fetches` whenever any store changes. [``listenTo``]({% url /api/containers/index.html#listenTo %}) allows you to specify either a single id or an array of Ids (This is the Id of the store in the application). 67 | 68 | {% highlight js %} 69 | listenTo: 'foos' 70 | // or 71 | listenTo: ['bars.baz', 'bam'] 72 | {% endhighlight %} 73 | 74 | If you're using the [fetch API]({% url /api/stores/index.html#fetch %}) then containers provide an easy way of dealing with the different states a fetch can be in. If any of your fetches are pending then the container will render whatever you return from the [``pending`` handler]({% url /api/containers/index.html#pending %}). The same will happen if any of the fetches have failed however the container will pass in an object containing all the errors to the [``failed`` handler]({% url /api/containers/index.html#failed %}). 75 | 76 | If you want to render the component before all fetches are done, you can call the `done` handler from the `pending` handler. Any fetches that are done are passed into the `pending` handler allowing you to provide defaults for the missing values: 77 | 78 | {% highlight js %} 79 | module.exports = Marty.createContainer(User, { 80 | listenTo: 'userStore', 81 | fetch: { 82 | user() { 83 | return this.app.userStore.getUser(this.props.id); 84 | }, 85 | friends() { 86 | return this.app.userStore.getFriends(this.props.id); 87 | } 88 | }, 89 | pending(fetches) { 90 | return this.done(_.defaults(fetches, { 91 | users: DEFAULT_USER, 92 | friends: [] 93 | }); 94 | } 95 | }); 96 | {% endhighlight %} 97 | 98 | ##Further reading 99 | 100 | * [Mixins Are Dead. Long Live Composition](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) 101 | * [Building The Facebook News Feed With Relay](http://facebook.github.io/react/blog/2015/03/19/building-the-facebook-news-feed-with-relay.html) 102 | 103 | -------------------------------------------------------------------------------- /docs/_guides/developer-tools/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Developer Tools 4 | id: devtools 5 | section: Dev tools 6 | --- 7 | 8 | [Marty Developer Tools](https://chrome.google.com/webstore/detail/marty-developer-tools/fifcikknnbggajppebgolpkaambnkpae) is an extension to Chrome's developer tools. It shows you the current state of your stores, actions that have flowed through the application as well as allowing you to revert to an earlier state. For each action we will show: 9 | 10 | * Which stores handled the action 11 | * What arguments were passed to the stores action handler 12 | * What components re-rendered as a result of the action (and how many) 13 | * Which stores caused a component to re-render 14 | 15 | Marty Developer Tools 16 | 17 | For Marty Developer Tools to work, you add Marty to the window object (window.Marty = require('marty')). Once you've done that, open your app in Chrome and open Chrome Developer Tools. You should see a new 'Marty' tab which gives you a view into what your application is doing. 18 | 19 |

Using DevTools with marty-native

20 | 21 | To use DevTools with `marty-native` (Or any other place outside of Chrome) you need to first install `marty-devtools` on the command line and then make sure its running. 22 | 23 | ```bash 24 | npm install -g marty-devtools 25 | marty-devtools 26 | ``` 27 | 28 | Next, install `marty-devtools-observer` in your application and call the observer at start up. 29 | 30 | ```js 31 | var Marty = require('marty'); 32 | var observe = require('marty-devtools-observer'); 33 | 34 | observe(Marty); 35 | ``` 36 | 37 | Finally, open Chrome and go to `http://localhost:7070/?port=5858`. You should then see whats happening in your application. 38 | 39 |

Serializers

40 | 41 | Marty Developer Tools uses [``window.postMessage``](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) to communicate with Marty. This means you need to serialize any state to simple objects otherwise the developer tools won't be able to receive it. We try our best (e.g. We automatically serialize [immutable.js](http://facebook.github.io/immutable-js/)) collections for you however there will be times when you want more control. For these cases you can register a new serializer 42 | 43 | {% highlight js %} 44 | window.MartyDevTools.registerSerializer({ 45 | id: 'foo', 46 | canSerialize(obj) { 47 | return obj instanceof Foo; 48 | }, 49 | serialize(obj) { 50 | return obj.serialize(); 51 | } 52 | }) 53 | {% endhighlight %} 54 | 55 | -------------------------------------------------------------------------------- /docs/_guides/dispatcher/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Dispatcher 4 | id: dispatcher 5 | section: Dispatcher 6 | --- 7 | 8 | The dispatcher is the central hub in which all application data flows throw. When a store is created it [registers](http://facebook.github.io/flux/docs/dispatcher.html#api) a callback with the dispatcher. Whenever an [action creator]({% url /guides/action-creators/index.html %}) creates an action and you call ``this.dispatch()``, the action will be passed to the dispatcher, which passes it to any registered stores. The dispatcher will call each registered callback synchronously. Any actions that are dispatched during this process will be queued until all callbacks are called. 9 | 10 | Marty uses the dispatcher internally. Whenever you create a new [application]({% url /guides/application/index.html %}) instance we will create a dispatcher (We use [facebook's dispatcher](https://github.com/facebook/flux/)) which is accessible at `Application#dispatcher` and passed down to all registered types. 11 | 12 | {% highlight js %} 13 | var Marty = require('marty'); 14 | 15 | var application = new Marty.Application(); 16 | 17 | var dispatchToken = application.dispatcher.register(function (action) { 18 | console.log(action); 19 | }); 20 | 21 | application.dispatcher.dispatchAction({ type: 'CREATE_FOO' }); 22 | {% endhighlight %} 23 | -------------------------------------------------------------------------------- /docs/_guides/fetching-state/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Fetching state 4 | id: fetch 5 | section: Fetching state 6 | --- 7 | 8 | From the views perspective, the store holds all the state it needs. In most cases it's unfeasible for you to hold all your applications data locally and so we need to fetch data from a remote source. Traditionally you might solve this problem by using callbacks or a promise however we've found they make your views complicated and difficult to reason about. It also goes against Flux's unidirectional data flow. Marty introduces the [fetch API]({% url /api/stores/#fetch %}) which is an alternative solution to the problem. 9 | 10 | Say your view wants to load a user from the ``UserStore``. Internally the store would call ``fetch`` which allows it to define how to get the user locally or, if not present, get it from a state source. ``fetch`` requires 3 things: 11 | 12 | 1. **id** A string that uniquely identifies the bit of state you are fetching. Marty ensures that only one fetch for a given Id can be in progress at any time. 13 | 2. **locally** A function which tries to find the required state in the stores state (if its present) 14 | 3. **remotely** A function which tries to get the required state from a state source (if not present) 15 | 16 | {% sample %} 17 | classic 18 | ======= 19 | var UserStore = Marty.createStore({ 20 | getUser: function (userId) { 21 | return this.fetch({ 22 | id: userId, 23 | locally: function () { 24 | return this.state[userId]; 25 | }, 26 | remotely: function () { 27 | return this.app.userQueries.getUser(userId) 28 | } 29 | }); 30 | } 31 | }); 32 | 33 | es6 34 | === 35 | class UserStore extends Marty.Store { 36 | getUser(userId) { 37 | return this.fetch({ 38 | id: userId, 39 | locally() { 40 | return this.state[userId]; 41 | }, 42 | remotely() { 43 | return this.app.userQueries.getUser(userId) 44 | } 45 | }); 46 | } 47 | } 48 | {% endsample %} 49 | 50 | When you call fetch, Marty will first try calling the ``locally`` function. It the state is present in the store then it's returned and the fetch will finish executing. If the store can't find the state locally it should return ``undefined``. This causes the fetch function to invoke ``remotely``. Once ``remotely`` has finished executing then fetch will then re-execute the ``locally`` function with the expectation that the state is now in the store. If it isn't then the fetch will fail with a "Not found" error. If the ``remotely`` function needs to get the state asynchronously you can return a promise. The fetch function will wait for the promise to be resolved before re-executing ``locally``. 51 | 52 | Using the example of getting a user, you would have a UserAPI (Which is an [HTTP State Source]({% url /api/state-sources/http.html %})), internally it would make the HTTP request which would be represented as a promise. Once the request completes, you should dispatch the state. You then return this promise chain to ``remotely``. 53 | 54 | {% sample %} 55 | classic 56 | ======= 57 | var UserConstants = Marty.createConstants([ 58 | 'RECEIVE_USER', 59 | 'USER_NOT_FOUND' 60 | ]); 61 | 62 | var UserAPI = Marty.createStateSource({ 63 | type: 'http', 64 | getUser: function (userId) { 65 | var url = 'http://jsonplaceholder.typicode.com/users/' + userId; 66 | 67 | return this.get(url).then(function (res) { 68 | if (res.ok) { 69 | return res; 70 | } 71 | 72 | throw res; 73 | }); 74 | } 75 | }); 76 | 77 | var UserQueries = Marty.createQueries({ 78 | getUser: function (userId) { 79 | this.dispatch(UserConstants.RECEIVE_USER_STARTING, userId); 80 | 81 | return this.app.userAPI.getUser(userId) 82 | .then(receiveUser.bind(this)) 83 | .catch(receiveUserFailed.bind(this)); 84 | 85 | function receiveUser(res) { 86 | this.dispatch(UserConstants.RECEIVE_USER, userId, res.body); 87 | } 88 | 89 | function receiveUserFailed(res) { 90 | this.dispatch(UserConstants.RECEIVE_USER_FAILED, userId, err); 91 | } 92 | } 93 | }); 94 | 95 | var UserStore = Marty.createStore({ 96 | id: 'UsersStore', 97 | handlers: { 98 | addUser: UserConstants.RECEIVE_USER 99 | }, 100 | getInitialState: function() { 101 | return {}; 102 | }, 103 | addUser: function (user) { 104 | this.state[user.id] = user; 105 | this.hasChanged(); 106 | }, 107 | getUser: function (userId) { 108 | return this.fetch({ 109 | id: userId, 110 | locally: function () { 111 | return this.state[userId]; 112 | }, 113 | remotely: function () { 114 | return this.app.userQueries.getUser(userId) 115 | } 116 | }); 117 | } 118 | }); 119 | 120 | es6 121 | === 122 | var UserConstants = Marty.createConstants([ 123 | 'RECEIVE_USER', 124 | 'USER_NOT_FOUND' 125 | ]); 126 | 127 | class UserAPI extends Marty.HttpStateSource { 128 | getUser(userId) { 129 | var url = 'http://jsonplaceholder.typicode.com/users/' + userId; 130 | 131 | return this.get(url).then(function (res) { 132 | if (res.ok) { 133 | return res; 134 | } 135 | 136 | throw res; 137 | }); 138 | } 139 | } 140 | 141 | class UserQueries extends Marty.Queries { 142 | getUser(userId) { 143 | this.dispatch(UserConstants.RECEIVE_USER_STARTING, userId); 144 | 145 | return this.app.userAPI.getUser(userId) 146 | .then(res => this.dispatch(UserConstants.RECEIVE_USER, userId, res.body)_ 147 | .catch(err => this.dispatch(UserConstants.RECEIVE_USER_FAILED, userId, err); 148 | } 149 | } 150 | 151 | class UserStore extends Marty.Store { 152 | constructor(options) { 153 | super(options); 154 | this.state = {}; 155 | this.handlers = { 156 | addUser: UserConstants.RECEIVE_USER 157 | }; 158 | } 159 | addUser(user) { 160 | this.state[user.id] = user; 161 | this.hasChanged(); 162 | } 163 | getUser(userId) { 164 | return this.fetch({ 165 | id: userId, 166 | locally: function () { 167 | return this.state[userId]; 168 | }, 169 | remotely: function () { 170 | return this.app.userQueries.getUser(userId) 171 | } 172 | }); 173 | } 174 | } 175 | {% endsample %} 176 | 177 | The result of the fetch function is a [fetch result]({% url /api/stores/index.html#fetch-result %}) which represents the current state of the fetch. A fetch can either be **PENDING**, **FAILED** or **DONE** (``fetch.status``). If a fetch has failed then the result will have the error (``fetch.error``) and if done it will have the result (``fetch.result``). The best way to use a fetch result within your views is with a [container]({% url /guides/containers/index.html %}) 178 | 179 | {% sample %} 180 | classic 181 | ======= 182 | var User = React.createClass({ 183 | render: function () { 184 | return ( 185 |
186 | {this.props.user.name} 187 | 188 |
189 | {this.props.friends.map(function (friend) { 190 | return ; 191 | })} 192 |
193 | 194 |
195 | ); 196 | } 197 | }); 198 | 199 | module.exports = Marty.createContainer(User, { 200 | listenTo: ['userStore', 'friendsStore'], 201 | fetch: { 202 | user: function() { 203 | return this.app.userStore.getUser(this.props.userId) 204 | }, 205 | friends: function () { 206 | return this.app.friendsStore.getFriends(this.props.userId); 207 | } 208 | }, 209 | pending: function (fetches) { 210 | return this.done(_.defaults(fetches, { 211 | user: DEFAULT_USER, 212 | friends: [] 213 | }); 214 | }, 215 | failed: function (errors) { 216 | return ; 217 | } 218 | }); 219 | 220 | es6 221 | === 222 | class User extends React.Component { 223 | render() { 224 | return ( 225 |
226 | {this.props.user.name} 227 | 228 |
229 | {this.props.friends.map(function (friend) { 230 | return ; 231 | })} 232 |
233 | 234 |
235 | ); 236 | } 237 | } 238 | 239 | module.exports = Marty.createContainer(User, { 240 | listenTo: ['userStore', 'friendsStore'], 241 | fetch: { 242 | user: function() { 243 | return this.app.userStore.getUser(this.props.userId) 244 | }, 245 | friends: function () { 246 | return this.app.friendsStore.getFriends(this.props.userId); 247 | } 248 | }, 249 | pending: function (fetches) { 250 | return this.done(_.defaults(fetches, { 251 | user: DEFAULT_USER, 252 | friends: [] 253 | }); 254 | }, 255 | failed: function (errors) { 256 | return ; 257 | } 258 | }); 259 | {% endsample %} 260 | -------------------------------------------------------------------------------- /docs/_guides/flux/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Flux 4 | id: flux 5 | section: Flux 6 | --- 7 | 8 | State is a big problem in the UI. Most JS applications have few restrictions on how state is changed. This makes it difficult to understand why something happens. Anyone who's spent late nights trying to understand why their application suddenly stopped working after removing an innocuous line should understand this. 9 | 10 | Flux is an answer to that problem. At its most basic level it's a set of rules about how to manage your applications state. Specifically who can change it, where they can change it and in what direction those changes should be propagated through your application. 11 | 12 | There are 4 things you will need to understand: How to tell the application to change its state ([Action creators]({% url /guides/action-creators/index.html %})), How to change the applications state ([Stores]({% url /guides/stores/index.html %})), how to tell the view that the state has changed ([State mixins]({% url /guides/state-mixin/index.html %})) and how to tie them all together ([Constants]({% url /guides/constants/index.html %})). 13 | 14 | Action Creators are where any changes to your applications state starts. Actions are functions that are responsible for coordinating changes to local and remote state. Actions have a type which is a string describing the action (e.g. "UPDATE\_USER_EMAIL"). 15 | 16 | We want to be explicit about the action types in your application so we define them as ([Constants]({% url /guides/constants/index.html %})). Constants allow you to loosely couple your application as well as documenting what actions are available (Useful for understanding what your application can do). Constants are also responsible for creating action creators. 17 | 18 | {% sample %} 19 | classic 20 | ======= 21 | var UserConstants = Marty.createConstants(["UPDATE_USER_EMAIL"]); 22 | 23 | var UserActionCreators = Marty.createActionCreators({ 24 | updateUserEmail: function (userId, email) { 25 | this.dispatch(UserConstants.UPDATE_USER_EMAIL, userId, email); 26 | } 27 | }); 28 | 29 | var app = new Marty.Application(); 30 | 31 | app.register('userActionCreators', UserActionCreators); 32 | app.userActionCreators.updateUserEmail(122, "foo@bar.com"); 33 | 34 | es6 35 | === 36 | var UserConstants = Marty.createConstants(["UPDATE_USER_EMAIL"]); 37 | 38 | class UserActionCreators extends Marty.ActionCreators { 39 | updateUserEmail(userId, email) { 40 | this.dispatch(UserConstants.UPDATE_USER_EMAIL, userId, email); 41 | } 42 | } 43 | 44 | var app = new Marty.Application(); 45 | 46 | app.register('userActionCreators', UserActionCreators); 47 | app.userActionCreators.updateUserEmail(122, "foo@bar.com"); 48 | 49 | {% endsample %} 50 | 51 | In the above scenario, ``UserConstants.UPDATE_USER_EMAIL`` creates an action creator which, when invoked, will create an action with type `UPDATE_USER_EMAIL`. 52 | 53 | If an action is making a change to your local state then it can pass its type data along to something called a dispatcher. The dispatcher is a just a big registry of callbacks (similar to an event emitter). Anyone interested can register to be notified when an action is dispatched. 54 | 55 | {% highlight js %} 56 | var app = new Marty.Application(); 57 | 58 | app.dispatcher.register(function (action) { 59 | console.log('action with type', action.type, 'has been dispatched') ; 60 | }); 61 | 62 | app.dispatcher.dispatchAction({ 63 | type: 'foo' 64 | }); 65 | {% endhighlight %} 66 | 67 | Normally you don't manually register callbacks with the dispatcher, instead you create stores which do this for you. Stores hold information about a domain. That domain could be a collection of entities (Like a [Backbone Collection](http://backbonejs.org/#Collection)) or it could be some information about something specific (Like a [Backbone Model](http://backbonejs.org/#Model)). 68 | 69 | {% sample %} 70 | classic 71 | ======= 72 | var UserStore = Marty.createStore({ 73 | handlers: { 74 | updateEmail: UserConstants.UPDATE_USER_EMAIL 75 | }, 76 | getInitialState: function () { 77 | return {}; 78 | }, 79 | getUser: function (userId) { 80 | return this.state[userId]; 81 | }, 82 | updateEmail: function (userId, email) { 83 | this.state[userId].email = email; 84 | this.hasChanged(); 85 | } 86 | }); 87 | 88 | es6 89 | === 90 | class UserStore extends Marty.Store { 91 | constructor(options) { 92 | super(options); 93 | this.state = {}; 94 | this.handlers = { 95 | updateEmail: UserConstants.UPDATE_USER_EMAIL 96 | }; 97 | } 98 | getUser(userId) { 99 | return this.state[userId]; 100 | } 101 | updateEmail(userId, email) { 102 | this.state[userId].email = email; 103 | this.hasChanged(); 104 | } 105 | } 106 | {% endsample %} 107 | 108 | When your application starts, each store automatically starts listening to the dispatcher. When an action is dispatched, each store checks its [``handlers`` hash]({% url /api/stores/index.html#handlers %}) to see if the store has a handler for the actions type. If it does it will call that handler, passing in the actions data. The action handler then updates its internal state (all stored in ``this.state``). 109 | 110 | The next (and final) step is to notify views about the new data. Like the dispatcher, you can register to be notified of any changes to a store. 111 | 112 | {% highlight js %} 113 | this.app.userStore.addChangeListener(function (state) { 114 | console.log('User store has changed', state); 115 | }); 116 | {% endhighlight %} 117 | 118 | If you have a view that's interested in a domain, it can ask the store to notify it of any changes. When the store updates, your view just rerenders itself with the new state. You might ask, "what if the store changed something your view isn't interested in (e.g. a different entity)?". Thanks to React's virtual DOM it doesn't really matter, if the state is the same then the view just returns the same DOM tree and React does nothing. This makes your views *significantly simpler* since you just render whatever the store tells you to render. 119 | 120 | {% sample %} 121 | classic 122 | ======= 123 | var User = React.createClass({ 124 | contextTypes: Marty.contextTypes, 125 | render: function () { 126 | return ( 127 |
128 | 131 |
132 | ); 133 | }, 134 | updateEmail: function (e) { 135 | var email = e.target.value; 136 | var userActions = this.context.app.userActionCreators; 137 | 138 | userActions.updateUserEmail(this.props.userId, email); 139 | } 140 | getInitialState: function () { 141 | var userStore = this.context.app.userStore; 142 | 143 | return { 144 | user: userStore.getUser(this.props.userId) 145 | }; 146 | }, 147 | componentDidMount: function () { 148 | var userStore = this.context.app.userStore; 149 | 150 | this.userStoreListener = userStore.addChangeListener(this.onUserStoreChanged); 151 | }, 152 | componentWillUnmount: function (nextProps) { 153 | this.userStoreListener.dispose(); 154 | }, 155 | onUserStoreChanged: function () { 156 | var userStore = this.context.app.userStore; 157 | 158 | this.setState({ 159 | user: userStore.getUser(this.props.userId) 160 | }); 161 | } 162 | }); 163 | 164 | es6 165 | === 166 | class User extends React.Component { 167 | render() { 168 | return ( 169 |
170 | 173 |
174 | ); 175 | } 176 | updateEmail(e) { 177 | var email = e.target.value; 178 | var userActions = this.context.app.userActionCreators; 179 | 180 | userActions.updateUserEmail(this.props.userId, email); 181 | } 182 | getInitialState() { 183 | var userStore = this.context.app.userStore; 184 | 185 | return { 186 | user: userStore.getUser(this.props.userId) 187 | }; 188 | } 189 | componentDidMount() { 190 | var userStore = this.context.app.userStore; 191 | 192 | this.userStoreListener = userStore.addChangeListener(this.onUserStoreChanged); 193 | } 194 | componentWillUnmount(nextProps) { 195 | this.userStoreListener.dispose(); 196 | } 197 | onUserStoreChanged() { 198 | var userStore = this.context.app.userStore; 199 | 200 | this.setState({ 201 | user: userStore.getUser(this.props.userId) 202 | }); 203 | } 204 | } 205 | {% endsample %} 206 | 207 | As your application grows you start to find that there is a lot of boilerplate code to get views to listen to stores. [Containers]({% url /guides/containers/index.html %}) are our solution to this problem. Containers manage listening to stores for you as well as providing a simpler API to implement: 208 | 209 | {% sample %} 210 | classic 211 | ======= 212 | var User = React.createClass({ 213 | render: function () { 214 | return ( 215 |
216 | 219 |
220 | ); 221 | }, 222 | updateEmail: function (e) { 223 | var email = e.target.value; 224 | 225 | this.app.userActionCreators.updateUserEmail(this.props.userId, email); 226 | } 227 | }); 228 | 229 | module.exports = Marty.createContainer(User, { 230 | listenTo: 'userStore', 231 | fetch: { 232 | user() { 233 | return this.app.userStore.getUser(this.props.userId) 234 | } 235 | } 236 | }); 237 | es6 238 | === 239 | class User extends React.Component { 240 | render() { 241 | return ( 242 |
243 | 246 |
247 | ); 248 | } 249 | updateEmail(e) { 250 | var email = e.target.value; 251 | 252 | this.app.userActionCreators.updateUserEmail(this.props.userId, email); 253 | } 254 | } 255 | 256 | module.exports = Marty.createContainer(User, { 257 | listenTo: 'userStore', 258 | fetch: { 259 | user() { 260 | return this.app.userStore.getUser(this.props.userId) 261 | } 262 | } 263 | }); 264 | {% endsample %} 265 | 266 | Whenever you want to change a value within your application, your data must follow this flow of [Action creator]({% url /guides/action-creators/index.html %}) **->** [Dispatcher]({% url /guides/dispatcher/index.html %}) **->** [Store]({% url /guides/stores/index.html %}) **->** [State mixin]({% url /guides/state-mixin/index.html %}) **->** View. This is known as a **unidirectional data flow**. 267 | 268 |
269 | Data flow 270 |
271 | 272 | While this seems superfluous at first, it turns out to have some great benefits. First and foremost, it's really easy to debug. There's only one place your application state can change so you don't have to dig into all the views to work out where a value was changed (it's even easier if you're using [immutable data collections]({% url /guides/stores/immutable-data-collections.html %})). Thanks to action types being strings you have a loosely coupled [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter) architecture which is easy to grow without increasing the complexity of the code base. 273 | -------------------------------------------------------------------------------- /docs/_guides/further-reading/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Further reading 4 | id: further-reading 5 | section: Further reading 6 | --- 7 | 8 | * [Actions and Action Creators](http://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html#actions-and-actioncreators) 9 | * [Discussion on actions & action creators](https://groups.google.com/forum/#!topic/reactjs/jBPHH4Q-8Sc) 10 | * [Original article about Flux](http://facebook.github.io/flux/docs/overview.html) 11 | * [Dispatcher documentation](http://facebook.github.io/flux/docs/dispatcher.html) -------------------------------------------------------------------------------- /docs/_guides/getting-started/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Getting Started 4 | id: getting-started 5 | section: Getting Started 6 | --- 7 | 8 | Welcome to Marty! This guide will help you learn how to use it. If you've never heard of Flux before I suggest you read an [overview of Flux]({% url /guides/flux %}). Or if you prefer you can look at some examples: 9 | 10 | * [Marty Todo MVC](https://github.com/martyjs/marty-todomvc) 11 | * [Marty Chat Example](https://github.com/martyjs/marty-chat-example) 12 | 13 | The quickest way to start writing some code is to use our [yeoman generator](https://github.com/jhollingworth/generator-marty). First you will need to install yeoman and the marty generator 14 | 15 | {% highlight bash %} 16 | npm install -g yo generator-marty 17 | {% endhighlight %} 18 | 19 | To use the generator, create a directory and then cd into it. Next run yo marty which will create an empty marty project and install all dependencies. To start the application run npm start, you can view it in a browser at [http://localhost:5000](http://localhost:5000). 20 | 21 | {% highlight bash %} 22 | mkdir example && cd example 23 | yo marty 24 | npm start 25 | open http://localhost:5000 26 | {% endhighlight %} 27 | 28 | Initially it only generates the basic folder structure. You can use yo marty:domain {domain name} to automatically create an action creator, store, constants, state source and component for the given domain. 29 | 30 | If you'd prefer to do your own thing you can download the [latest version from Github](https://github.com/martyjs/marty/releases) or you can get it from NPM or Bower 31 | 32 | {% highlight bash %} 33 | npm install --save marty 34 | bower install --save marty 35 | {% endhighlight %} 36 | 37 | Marty is built using [UMD](https://github.com/umdjs/umd). This means you can use it from [node.js](https://nodejs.org) or [Browserify](http://browserify.org) 38 | 39 | {% highlight js %} 40 | var Marty = require('marty'); 41 | 42 | module.exports = Marty.createStore({ 43 | ... 44 | }) 45 | {% endhighlight %} 46 | 47 | Or [require.js](http://requirejs.org) ([Working example](https://github.com/martyjs/marty/tree/master/examples/requirejs)) 48 | 49 | {% highlight js %} 50 | require(['marty'], function (Marty) { 51 | return Marty.createStore({ 52 | ... 53 | }) 54 | }); 55 | {% endhighlight %} 56 | 57 | Or you can access it from the window object ([Working example](https://github.com/martyjs/marty/tree/master/examples/window)) 58 | 59 | {% highlight js %} 60 | window.Marty.createStore({ 61 | ... 62 | }) 63 | {% endhighlight %} 64 | -------------------------------------------------------------------------------- /docs/_guides/isomorphism/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Isomorphism 4 | id: isomorphism 5 | section: Isomorphism 6 | --- 7 | 8 | When the web started your web page had to be constructed on the server. Then AJAX came along and developers realized you could build more responsive applications in the browser using just JavaScript. However moving rendering your application in the browser had some drawbacks. 9 | 10 | * It took a long time before a user saw anything because they were waiting for the JS to load and API calls to respond. 11 | * You wouldn't see anything at all if you didn't have JavaScript turned on. 12 | * You have to have two separate code bases for the client and server. 13 | * Search engines aren't terribly good at indexing JS applications and so SEO was impacted. 14 | 15 | A [number](http://blog.nodejitsu.com/scaling-isomorphic-javascript-code/) [of](https://asana.com/luna) [companies](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/) have found you can solve these problems by doing the initial render of your client on the server using [node.js](nodejs.org). They called this approach [isomorphic JavaScript](http://isomorphic.net/). 16 | 17 | Thanks to [React.renderToString](http://facebook.github.io/react/docs/top-level-api.html#react.rendertostring) rendering individual React components on the server is trivial. However that is only one of many challenges you must solve to have a fully working isomorphic JavaScript application. 18 | 19 | So how does Marty help? 20 | 21 |

Fetching state

22 | 23 | One of the first questions you will probably ask is how to get the right state into your components. 24 | 25 | A naive approach would be for you to put all the required state into the stores before rendering the component. We've found having an imperative data fetching strategy isn't a scalable solution. A better approach is to have the components tell us what state it needs using the same APIs they would in the browser and we will handle satisfying those requests on server. 26 | 27 | [``Application#renderToString``]({% url /api/application/index.html#renderToString %}) is a smarter version of [``React.renderToString``](http://facebook.github.io/react/docs/top-level-api.html#react.rendertostring) which knows about what state. It will render your component and then see what [fetches it makes]({% url /api/stores/index.html#fetch %}). It will wait until all of those fetches are complete (or have failed) and then it will then re-render the component. The result of [``Application#renderToString``]({% url /api/application/index.html#renderToString %}) is a promise which resolves to the rendered component. 28 | 29 | {% highlight js %} 30 | // stores/userStore.js 31 | var UserStore = Marty.createStore({ 32 | handlers: { 33 | addUser: UserConstants.RECEIVE_USER 34 | }, 35 | addUser(user) { 36 | this.state[user.id] = user; 37 | this.hasChanged(); 38 | }, 39 | getUser(id) { 40 | return this.fetch({ 41 | id: id, 42 | locally() { 43 | return this.state[id]; 44 | }, 45 | remotely() { 46 | return this.app.userQueries.getUser(id); 47 | } 48 | }); 49 | } 50 | }); 51 | 52 | // components/user.js 53 | var User = React.createClass({ 54 | render() { 55 | return
{this.props.user.name}
; 56 | } 57 | }); 58 | 59 | module.exports = Marty.createContainer(User, { 60 | listenTo: 'userStore', 61 | fetch: { 62 | user() { 63 | return this.app.userStore.getUser(this.props.id) 64 | } 65 | }, 66 | failed(errors) { 67 | return ; 68 | } 69 | }) 70 | 71 | // renderToString.js 72 | 73 | app.renderToString() 74 | .then(render => res.send(render.html).end()); 75 | 76 | {% endhighlight %} 77 | 78 | Rendering the HTML is only half the battle, we need a way of synchronizing the state of the stores between the server and browser. To solve this, Marty introduces the concept of dehydrating and rehydrating your application. When you call [``Application#dehydrate()``]({% url /api/application/index.html#dehydrate %}), it will iterate through all the stores, serializing their state to a JSON object (Use [``Store#dehyrdate``]({% url /api/stores/index.html#dehydrate %}) to control how a store is dehydrated). [``Application#renderToString``]({% url /api/application/index.html#renderToString %}) automatically does this for you, adding the dehydrated state to the window object (``window.__marty``). When the application loads in the browser you should call [``Application#rehydrate()``]({% url /api/application/index.html#rehydrate %}) which will use the dehydrated state to return the stores to its state on the server (Use [``Store#rehydrate``]({% url /api/stores/index.html#rehydrate %}) to control how a store is rehydrated). 79 | 80 | We've found its useful to know what fetches have been happening on the server (e.g. to identify fetches that are failing or taking too long) so [``Application#renderToString()``]({% url /api/application/index.html#renderToString %}) will also return diagnostic information about what fetches were made when rendering the component. 81 | 82 |

Single code base

83 | 84 | Having a single code base that can be run in server and browser is going to improve productivity and reduce the number of defects you have. While things like [browserify](http://browserify.org/) help there are still many challenges to overcome. 85 | 86 | You cannot make the same HTTP requests on the server as you do in the browser. The reason being there is a lot of implicit state in the browser we often forget about. For example when you make a request to `/bar`, your browser will automatically fully qualify the URL for you (e.g. http://foo.com/bar) and add in cookies and other HTTP headers. We'd like to keep using the same data fetching logic on the server and so we need a way of replicating all of this implicit state on the server. 87 | 88 | There are many other inconsistencies between APIs on the server and in the browser. For example if you want to modify a cookie in the browser you would do ``document.cookie = "foo=bar"`` whereas on the server (using express.js) you would do ``res.cookie('foo', 'bar')``. Routing is another example which you need to define with two incompatible APIs. 89 | 90 | [marty-express]({% url /guides/isomorphism/marty-express.html %}) is an [express.js](http://expressjs.com) middleware which aims to resolve these differences allowing you to have a single code base. It will do a number of things for your you: 91 | 92 | * Consumes [react-router](https://github.com/rackt/react-router) routes and automatically renders them on the server. It will also manage ``Application#renderToString()`` for you. 93 | * Modifies any requests made through [HTTP state source]({% url /api/state-sources/http.html %}), fully qualifying relative URLs and injecting headers from the original request. 94 | * Modifies [CookieStateSource]({% url /api/state-sources/cookie.html %}) and [LocationStateSource]({% url /api/state-sources/location.html %}) so they are using the using the express.js request and response (e.g. ``UserCookies.set('foo', 'bar')`` will add a ``Set-Cookie`` response header). -------------------------------------------------------------------------------- /docs/_guides/isomorphism/marty-express.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: marty-express 4 | id: marty-express 5 | section: Isomorphism 6 | --- 7 | 8 | [marty-express](http://github.com/martyjs/marty-express) is an [express.js](http://expressjs.com/) middleware which makes it easier to build isomorphic applications using express and [react-router](https://github.com/rackt/react-router). 9 | 10 | marty-express will take your react-router routes and automatically serve them as if they were in the browser meaning you have a single place to define your routes. If a user requests one of these routes, it will use [``app.renderToString``]({% url /api/application/index.html#renderToString %}) to render the component on the server and then pass the result to the view for rendering. 11 | 12 | If you make any requests through the HTTP state source then requests are automatically fully qualified and any HTTP headers in the original request are automatically added. It also modifies [LocationStateSource](/api/state-sources/location.html) and the [CookieStateSource](/api/state-sources/cookie.html) so that they use the ``req`` and ``res`` from the HTTP request. 13 | 14 | {% highlight js %} 15 | var Marty = require('marty'); 16 | var Router = require('react-router'); 17 | 18 | var routes = [ 19 | , 20 | 21 | ]; 22 | 23 | class IsomorphicApplication extends Marty.Application { 24 | constructor(options) { 25 | super(options); 26 | 27 | this.register('userStore', require('./stores/userStore')); 28 | 29 | ... 30 | } 31 | } 32 | 33 | var app = express(); 34 | 35 | app.use(require('marty-express')({ 36 | routes: routes, 37 | application: IsomorphicApplication, 38 | rendered: function (diagnostics) { 39 | console.log('Page rendered', diagnostics); 40 | } 41 | })); 42 | {% endhighlight %} 43 | -------------------------------------------------------------------------------- /docs/_guides/marty-native/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Marty Native 4 | id: marty-native 5 | section: Marty Native 6 | --- 7 | 8 | [React Native](http://facebook.github.io/react-native/) allows you to build native applications using familiar tools like JavaScript and React. Fortunately Marty code will happily run in a native environment. The only change you need to make is call `require('marty-native')` instead of `require('marty')`. This is because Marty uses React internally and React and React Native are completely separate node modules. 9 | 10 | {% highlight js %} 11 | var Marty = require('marty-native'); 12 | var React = require('react-native'); 13 | var User = require('./views/user'); 14 | var Application = require('./application'); 15 | 16 | var { AppRegistry } = React; 17 | var { ApplicationContainer } = Marty; 18 | 19 | // See /guides/developer-tools/index.html#marty-native 20 | require('marty-devtools-observer')(Marty); 21 | 22 | var Main = React.createClass({ 23 | getInitialState() { 24 | return { 25 | app: new Application() 26 | }; 27 | }, 28 | render() { 29 | return ( 30 | 31 | 32 | 33 | ); 34 | } 35 | }); 36 | 37 | React.AppRegistry.registerComponent('App', () => Main); 38 | {% endhighlight %} -------------------------------------------------------------------------------- /docs/_guides/queries/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Queries 4 | id: queries 5 | section: Queries 6 | --- 7 | 8 | Queries are responsible for coordinating getting new state from outside of the application. They are identical to action creators, the difference being you use queries for **reads** and action creators for **writes**. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var UserQueries = Marty.createQueries({ 14 | getUser: function (id) { 15 | this.dispatch(UserConstants.RECEIVE_USER_STARTING, id); 16 | this.app.userAPI.getUser(id).then( 17 | function (res) { 18 | if (res.status === 200) { 19 | this.dispatch(UserConstants.RECEIVE_USER, res.body, id); 20 | } else { 21 | this.dispatch(UserConstants.RECEIVE_USER_FAILED, id); 22 | } 23 | }.bind(this), 24 | function (err) { 25 | this.dispatch(UserConstants.RECEIVE_USER_FAILED, id, err); 26 | }.bind(this) 27 | ); 28 | } 29 | }); 30 | 31 | es6 32 | === 33 | class UserQueries extends Marty.Queries { 34 | getUser(id) { 35 | this.dispatch(UserConstants.RECEIVE_USER_STARTING, id); 36 | this.app.userAPI.getUser(id).then( 37 | res => { 38 | if (res.status === 200) { 39 | this.dispatch(UserConstants.RECEIVE_USER, res.body, id); 40 | } else { 41 | this.dispatch(UserConstants.RECEIVE_USER_FAILED, id); 42 | } 43 | }, 44 | err => this.dispatch(UserConstants.RECEIVE_USER_FAILED, id, err) 45 | ); 46 | } 47 | } 48 | 49 | module.exports = UserQueries; 50 | {% endsample %} 51 | 52 | ##Why have Queries at all? 53 | 54 | One practical reason for queries is that you get a circular dependency if your store tries to call an action creator from inside itself. Splitting reads from writes was an easy way of resolving this situation. You could just as easily use action creators, but we've found having separate types for them makes the code base easier to navigate and understand. 55 | 56 | -------------------------------------------------------------------------------- /docs/_guides/state-mixin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: State Mixin 4 | id: state-mixin 5 | section: State Mixin 6 | --- 7 | 8 |
9 | The State Mixin will be deprecated, we recommend you use containers instead. 10 |
11 | 12 | We found that there was a lot of boilerplate code in React components to start listening to [stores]({% url /guides/stores/index.html %}) and get their states. The State mixin helps to reduce the amount of code you have to write. 13 | 14 | Firstly, it automatically [adds change listeners]({% url /api/stores/index.html#addChangeListener %}) to [stores you want to listen to]({% url /api/state-mixin/index.html#listenTo %}), as well as disposing of those listeners when the component unmounts. 15 | 16 | It also introduces a new function [getState](#getState), which returns the state of the component. It will be called just before the initial render of the component and whenever a store updates. 17 | 18 | {% highlight js %} 19 | var UserState = Marty.createStateMixin({ 20 | listenTo: ['userStore', 'friendsStore'], 21 | getState: function () { 22 | return { 23 | users: this.app.userStore.getUser(this.props.userId), 24 | friends: this.app.friendsStores.getFriends(this.props.userId) 25 | }; 26 | } 27 | }); 28 | 29 | var Users = React.createClass({ 30 | mixins: [UserState], 31 | render: function () { 32 | return (
    33 | {this.state.users.map(function (user) { 34 | return
  • {user.name}
  • ; 35 | })} 36 |
); 37 | } 38 | }); 39 | {% endhighlight %} 40 | -------------------------------------------------------------------------------- /docs/_guides/state-sources/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: State Sources 4 | id: state-sources 5 | section: State Sources 6 | --- 7 | 8 | State sources are how you get state into and out of your application. State can come from many different places (e.g. API's, Web sockets, Local Storage), State sources encapsulate a lot of complexities in connecting to these sources and provides a uniform, easy to test interface for the rest of your application to use. 9 | 10 | {% sample %} 11 | classic 12 | ======= 13 | var UserAPI = Marty.createStateSource({ 14 | type: 'http', 15 | baseUrl: 'http://foo.com', 16 | getUsers: function () { 17 | return this.get('/users').then(function (res) { 18 | if (res.ok) { 19 | return res.json(); 20 | } 21 | 22 | throw new Error('Failed to get user', res); 23 | }); 24 | }, 25 | createUser: function (user) { 26 | return this.post('/users', { body: user }).then(function (res) { 27 | if (res.ok) { 28 | return res.json(); 29 | } 30 | 31 | throw new Error('Failed to create user', res); 32 | }); 33 | } 34 | }); 35 | es6 36 | === 37 | class UserAPI extends Marty.HttpStateSource { 38 | constructor(options) { 39 | super(options); 40 | this.baseUrl = 'http://foo.com'; 41 | } 42 | getUsers() { 43 | return this.get('/users').then(res => { 44 | if (res.ok) { 45 | return res.json(); 46 | } 47 | 48 | throw new Error('Failed to get user', res); 49 | }); 50 | } 51 | createUser(user) { 52 | return this.post('/users', { body: user }).then(res => { 53 | if (res.ok) { 54 | return res.json(); 55 | } 56 | 57 | throw new Error('Failed to create user', res); 58 | }); 59 | } 60 | } 61 | {% endsample %} 62 | 63 | Marty comes with a number of state sources out of the box: 64 | 65 | * [HTTP](/api/state-sources/http.html) 66 | * [JSON storage](/api/state-sources/json-storage.html) 67 | * [Local storage](/api/state-sources/local-storage.html) 68 | * [Session storage](/api/state-sources/session-storage.html) 69 | * [Location](/api/state-sources/location.html) 70 | * [Cookie](/api/state-sources/cookie.html) 71 | -------------------------------------------------------------------------------- /docs/_guides/stores/immutable-data-collections.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Immutable data collections 4 | id: immutable-data-collections 5 | section: Stores 6 | --- 7 | 8 | Within a Flux application you want the stores to be the **only** place you can change state. This can be very difficult to achieve using the in built Javascript collections since they are mutable. This can make it very difficult to debug issues since any piece of code that touches that collection could be the cause. 9 | 10 | The solution is to use immutable data collections like [immutable.js](http://facebook.github.io/immutable-js/) or [mori](http://swannodette.github.io/mori/). Operations on immutable data structures do not mutate the instance itself but rather return a new instance which is the result of the mutation. 11 | 12 | {% highlight js %} 13 | var users = Immutable.List.of("foo"); 14 | var users2 = users.push("bar"); 15 | 16 | console.log(users) // ["foo"] 17 | console.log(users2) // ["foo", "bar"] 18 | {% endhighlight %} 19 | 20 | Using immutable data collections help you sleep soundly with the knowledge that nothing outside of stores will be able to change its state. 21 | 22 | Whilst immutable data collections are not required, we try to make it as easy to use as possible. For example, you can simply set ``this.state`` with the mutated collection. If the state has changed it will call ``this.hasChanged()`` for you. 23 | 24 | {% sample %} 25 | classic 26 | ======= 27 | var UsersStore = Marty.createStore({ 28 | handlers: { 29 | addUser: Constants.RECEIVE_USER 30 | }, 31 | getInitialState: function () { 32 | return Immutable.List(); 33 | }, 34 | addUser: function (user) { 35 | this.state = this.state.push(user); 36 | } 37 | }); 38 | 39 | es6 40 | === 41 | class UsersStore extends Marty.Store { 42 | constructor(options) { 43 | super(options); 44 | this.state = Immutable.List(); 45 | this.handlers = { 46 | addUser: Constants.RECEIVE_USER 47 | }; 48 | } 49 | addUser(user) { 50 | this.state = this.state.push(user); 51 | } 52 | } 53 | {% endsample %} 54 | -------------------------------------------------------------------------------- /docs/_guides/stores/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Stores 4 | id: stores 5 | section: Stores 6 | --- 7 | 8 | The store is where your state should live. It is also the only place that your state should **change**. Actions come in from the dispatcher and the store decides whether to handle them or not. If the store does choose to handle an action and subsequently changes its state then it will notify any listeners that it has changed. Your views can then listen to those changes (via the [State Mixin]({% url /guides/state-mixin %})) and then re-render themselves. 9 | 10 | All of a store's state should live within ``Store#state``. If you want to update the state you should use ``Store#setState`` which will update ``this.state`` and then notify any listeners that the store has changed. Or if you prefer you can update ``this.state`` and then manually call ``this.hasChanged()`` 11 | 12 | When you create a store it will automatically start listening to the dispatcher. To determine which actions to handle you define the [handlers]({% url /api/stores/#handlers %}) hash. The keys in handlers are the functions you wish to call when an action comes in and the value is either a constant or an array of constants which match the actions type. If your store does handle the action, then the arguments you passed to [ActionCreator#dispatch]({% url /api/action-creators/index.html#dispatch %}) are the arguments for the handler. 13 | 14 | {% sample %} 15 | classic 16 | ======= 17 | var UsersStore = Marty.createStore({ 18 | handlers: { 19 | addUser: Constants.RECEIVE_USER 20 | }, 21 | getInitialState: function () { 22 | return {}; 23 | }, 24 | addUser: function (user) { 25 | this.state[user.id] = user; 26 | this.hasChanged(); 27 | } 28 | }); 29 | 30 | var app = new Marty.Application(); 31 | 32 | app.register('userStore', UserStore); 33 | 34 | var listener = app.userStore.addChangeListener(function () { 35 | console.log('Users store changed'); 36 | listener.dispose(); 37 | }); 38 | 39 | es6 40 | === 41 | class UsersStore extends Marty.Store { 42 | constructor(options) { 43 | super(options); 44 | this.state = {}; 45 | this.handlers = { 46 | addUser: Constants.RECEIVE_USER 47 | }; 48 | } 49 | addUser(user) { 50 | this.state[user.id] = user; 51 | this.hasChanged(); 52 | } 53 | } 54 | 55 | var app = new Marty.Application(); 56 | 57 | app.register('userStore', UserStore); 58 | 59 | var listener = app.userStore.addChangeListener(function () { 60 | console.log('Users store changed'); 61 | listener.dispose(); 62 | }); 63 | 64 | {% endsample %} 65 | -------------------------------------------------------------------------------- /docs/_guides/upgrading/08_09.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Upgrading from Marty 0.8 to 0.9 4 | id: upgrading 5 | section: Upgrading 6 | --- 7 | 8 | ##Ids 9 | 10 | Marty now requires every store, action creator, query and state source to have a unique Id (e.g. "UserStore"). If you've been populating ``displayName`` then the easiest thing to do is rename ``displayName`` to ``id`` and your application will behave the same. 11 | 12 | ##replaceState & setState 13 | 14 | ``Store#setState`` has been renamed to [``Store#replaceState``]({% url /api/stores/index.html#replaceState %}). [``Store#setState``]({% url /api/stores/index.html#setState %}) will now merge the existing state with the new state. This is to more closely follow the React API. 15 | 16 | ##HttpStateSource no longer throws an error if Status Code >= 400 17 | 18 | If the status code of an HTTP response is >= 400, we will no longer throw an error. If you wish to retain this behavior then you can add an [HTTP hook]({% url /api/state-sources/http.html#hooks %}) 19 | 20 | {% highlight js %} 21 | Marty.HttpStateSource.addHook({ 22 | id: 'throw-error', 23 | after(res) { 24 | if (!res.ok) { 25 | throw res; 26 | } 27 | } 28 | }); 29 | {% endhighlight %} 30 | 31 | ##Defining action types 32 | 33 | In Marty v0.8 and below we had a different way of defining an action creators type which has now been deprecated: 34 | 35 | {% highlight js %} 36 | var UserActionCreators = Marty.createActionCreators({ 37 | updateEmail: UserConstants.UPDATE_EMAIL(function (userId, email) { 38 | this.dispatch(userId, email); 39 | }), 40 | deleteEmail: UserConstants.DELETE_EMAIL() 41 | }); 42 | {% endhighlight %} 43 | 44 | ###Why was it deprecated? 45 | 46 | There were 3 reasons: 47 | 1. Automatic error handling made debugging really difficult ([#127](https://github.com/martyjs/marty/issues/127)). 48 | 2. Automatically dispatched actions were confusing and not that helpful ([#157](https://github.com/martyjs/marty/issues/157), [#152](https://github.com/martyjs/marty/issues/152)). 49 | 3. It didn't play nicely with ES6 classes 50 | 51 | We decided this code wasn't adding any value so we should move towards a simpler more explicit approach. 52 | 53 | In v0.9 you will see warnings in your code to move to the new style and we will remove the code entirely in v0.10. 54 | 55 | ###How do I migrate my code to the new style? 56 | 57 | All you need to do is move the constant to being the first argument of the dispatch function. If you utilizing auto dispatch constants then you will need to use ``marty/autoDispatch``. 58 | 59 | {% highlight js %} 60 | var autoDispatch = require('marty/autoDispatch'); 61 | 62 | var UserActionCreators = Marty.createActionCreators({ 63 | updateEmail: function (userId, email) { 64 | this.dispatch(UserConstants.UPDATE_EMAIL, userId, email); 65 | }), 66 | deleteEmail: autoDispatch(UserConstants.DELETE_EMAIL) 67 | }); 68 | {% endhighlight %} -------------------------------------------------------------------------------- /docs/_guides/upgrading/09_10.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Upgrading from Marty 0.9 to 0.10 4 | id: upgrading 5 | section: Upgrading 6 | --- 7 | 8 | If you've got any questions about upgrading you can ask the team in [Marty's Gitter chatroom](https://gitter.im/martyjs/marty). 9 | 10 |

Singleton to Application

11 | 12 | Firstly, if you're using ES6 you will need to remove all references to ``Marty.register(...)`` and just return the original type. 13 | 14 | {% highlight js %} 15 | // Instead of 16 | export default Marty.register(UserStore); 17 | 18 | // You just export the type 19 | export default UserStore; 20 | 21 | {% endhighlight %} 22 | 23 | Next you need to create a new Application instance and tell it about all of your types. If you're using webpack or browserify this process is significantly easier if you implement automatic registration. 24 | 25 | {% sample %} 26 | classic 27 | ======= 28 | var Application = Marty.createApplication(function () { 29 | this.register('userStore', require('./stores/userStore')); 30 | ... 31 | }); 32 | 33 | module.exports = Application; 34 | es6 35 | === 36 | class Application extends Marty.Application { 37 | constructor(options) { 38 | super(options); 39 | this.register('userStore', require('./stores/userStore')); 40 | ... 41 | } 42 | } 43 | 44 | export default Application; 45 | {% endsample %} 46 | 47 | Next, when calling `React.render` you must wrap the element you are about to render with an `ApplicationContainer`, passing an instance of your application in via the props. 48 | 49 | {% highlight js %} 50 | var app = new Application(); 51 | var { ApplicationContainer } = require('marty'); 52 | 53 | React.render(( 54 | 55 | 56 | 57 | ), document.body); 58 | {% endhighlight %} 59 | 60 | Now that we're returning types instead of instances we need to update all references within our application. This largely means that, instead of require'ing your dependency, you must instead call `this.app.{dependencyId}`. e.g. 61 | 62 | {% highlight js %} 63 | // Instead of 64 | var UserAPI = require('../sources/userAPI'); 65 | 66 | class UserActions extends Marty.ActionCreators { 67 | deleteUser(id) { 68 | UserAPI.for(this).deleteUser(id); 69 | } 70 | } 71 | 72 | // Assuming you had registered UserAPI with the id 'userAPI' 73 | class UserActions extends Marty.ActionCreators { 74 | deleteUser(id) { 75 | this.app.userAPI.deleteUser(id); 76 | } 77 | } 78 | {% endhighlight %} 79 | 80 | This is largely the same from with React components. The only requirement is that the component is either wrapped in a [container]({% url /guides/containers/index.html %}) or is using the [state mixin]({% url /guides/state-mixin/index.html %}). There are times when neither are appropriate so we've introduced the [app mixin]({% url /api/app-mixin/index.html %}) which simply injects `app` into your component. 81 | 82 | {% highlight js %} 83 | class User extends React.Component { 84 | saveUser() { 85 | this.app.userActionCreators.saveUser(this.props.id); 86 | } 87 | } 88 | 89 | export default Marty.createContainer(User); 90 | {% endhighlight %} 91 | 92 | For the `listenTo` property in [containers]({% url /api/containers/index.html#listenTo %}) and [state mixins]({% url /api/state-mixin/index.html#listenTo %}) you need to replace type references with their dependency Id's. The `listenTo` behavior is identical otherwise. 93 | 94 | {% highlight js %} 95 | export default Marty.createContainer(User, { 96 | // instead of 97 | listenTo: [ 98 | UserStore, 99 | FriendsStore 100 | ], 101 | 102 | // Use strings 103 | listenTo: [ 104 | 'userStore', 105 | 'friends.store' // To access app.friends.store 106 | ], 107 | 108 | fetch { 109 | user() { 110 | return this.app.userStore.getUser(123); 111 | } 112 | } 113 | }); 114 | {% endhighlight %} 115 | 116 | To update your tests, we recommend you look at our [test examples](https://github.com/martyjs/marty-test-examples) and [test utils]({% url /api/test-utils/index.html %}). 117 | 118 |

Isomorphism

119 | 120 | [Contexts](http://martyjs.org/v/0.9.16/api/context/index.html) have been completely removed and most functions that were previously on `Marty`, e.g. [`Marty.replaceState`](http://martyjs.org/v/0.9.16/api/top-level-api/#replaceState), [`Marty.clearState`](http://martyjs.org/v/0.9.16/api/top-level-api/#clearState), [`Marty.dehydrate`](http://martyjs.org/v/0.9.16/api/top-level-api/#dehydrate) and [`Marty.rehydrate`](http://martyjs.org/v/0.9.16/api/top-level-api/#rehydrate) have been moved to to the [application]({% url /api/application/index.html %}). 121 | 122 | {% highlight js %} 123 | // Instead of 124 | var context = Marty.createContext(); 125 | 126 | Marty.replaceState({ ... }, context); 127 | Marty.clearState(context); 128 | Marty.dehyrdate(context); 129 | Marty.rehydrate({ ... }, context); 130 | 131 | // You do 132 | var app = new Application(); 133 | 134 | app.replaceState({ ... }); 135 | app.clearState(); 136 | app.dehyrdate(); 137 | app.rehydrate({ ... }); 138 | {% endhighlight %} 139 | 140 | [`Marty.renderToString`](http://martyjs.org/v/0.9.16/api/top-level-api/#renderToString) has also been moved to the application and has simpler signature. 141 | 142 | {% highlight js %} 143 | // Instead of 144 | var context = Marty.createContext(); 145 | var options = { 146 | type: Foo, 147 | timeout: 2000, 148 | context: context, 149 | props: { bar: 'baz' } 150 | }; 151 | 152 | Marty.renderToString(options).then(res => { 153 | console.log('Rendered html', res.html); 154 | console.log('Diagnostics', res.diagnostics); 155 | }); 156 | 157 | // You do 158 | var app = new Application(); 159 | 160 | app.renderToString(, { timeout: 2000 }).then(res => { 161 | // We've had to split the html body and html state variables out to resolve https://github.com/martyjs/marty/issues/288. 162 | console.log('Rendered html body', res.htmlBody); 163 | console.log('Rendered html state', res.htmlState); 164 | console.log('Diagnostics', res.diagnostics); 165 | }); 166 | {% endhighlight %} 167 | 168 |

parseJSON

169 | 170 | The HttpStateSource internally uses [`fetch`](https://fetch.spec.whatwg.org/) to make HTTP calls for you. If the response contains JSON you need to [call `res.json()`](https://github.com/github/fetch#json) to get the actual JSON. We thought we'd be helpful and do this for you automatically, assigning the JSON to `res.body`. Unfortunately we did this before we knew `fetch` was also going to use the `body` property for other purposes which has [caused issues](https://github.com/martyjs/marty/issues/268). We therefore need to depreciate this feature. For v0.10 you will get warnings and in v0.11 we will turn feature off by default. 171 | 172 | To get rid of the warnings you first need to remove the HTTP hook 173 | 174 | {% highlight js %} 175 | require('marty').HttpStateSource.removeHook('parseJSON'); 176 | {% endhighlight %} 177 | 178 | Next you will need to update all references to `res.body`. We recommend putting all of this logic in your HTTP state source 179 | 180 | {% highlight js %} 181 | class UserAPI extends Marty.HttpStateSource { 182 | getUser(id) { 183 | return this.get(`/users/${id}`).then(res => { 184 | if (res.ok) { 185 | return res.json(); 186 | } 187 | 188 | throw new Error('Failed to get user'); 189 | }); 190 | } 191 | } 192 | {% endhighlight %} 193 | 194 |

State mixin's

195 | 196 | We warned when we released v0.9 we would be depreciating the state mixin. We've decided to keep them around for v0.10 however we've removed the ability to [create a state mixin by passing in a store](http://martyjs.org/v/0.9.16/api/state-mixin/index.html#stores) or [by passing in an object hash of stores](http://martyjs.org/v/0.9.16/api/state-mixin/index.html#stores). 197 | 198 | {% highlight js %} 199 | // No longer possible 200 | Marty.createStateMixin(UserStore) 201 | Marty.createStateMixin({ 202 | users: UserStore, 203 | friends: FriendsStore 204 | }); 205 | {% endhighlight %} 206 | 207 |

Rollbacks

208 | 209 | [Rollbacks](http://martyjs.org/v/0.9.16/api/stores/index.html#rollback) are no longer supported. We recommend you look at our [suggested approach to handling errors]({% url /guides/action-creators/handling-errors.html %}). 210 | 211 |

Other Depreciations

212 | 213 | * You no longer need to declare Id's or displayName's on the type. An instances Id will be populated from from `Application#register(id, type)`. 214 | * `Marty.registry` no longer exists. If you want to get all instances of a given type you can call [`Application#getAll(type)`]({% url /api/application/index.html#getAll %}) 215 | - `require('marty').Dispatcher` is no longer supported. Create an application and access the [dispatcher](http://martyjs.org/api/application/index.html#dispatcher). 216 | - `require('marty/http/hooks')` is no longer supported. Use `require('marty').hooks` instead 217 | - `require('marty/environment')` is no longer supported. Use `require('marty').environment` 218 | - `require('marty/fetch')` is no longer supported. Use `require('marty').fetch` 219 | - `require('marty/when')` is no longer supported. Use `require('marty').when` 220 | - `require('marty/autoDispatch')` is no longer supported. Use `require('marty').autoDispatch` 221 | - `require('marty').Diagnostics` is no longer supported. Use `require('marty').diagnostics` -------------------------------------------------------------------------------- /docs/_guides/upgrading/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Upgrading 4 | id: upgrading 5 | section: Upgrading 6 | --- 7 | 8 | * [Upgrading from Marty 0.9 to 0.10]({% url /guides/upgrading/09_10.html %}) 9 | * [Upgrading from Marty 0.8 to 0.9]({% url /guides/upgrading/08_09.html %}) -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ page.title }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% capture version %}{% version %}{% endcapture %} 23 | 89 | {{ content }} 90 |
91 |
92 |

Code licensed under MIT, documentation under CC BY 3.0.

93 | 102 |
103 |
104 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/_layouts/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | sectionid: home 4 | --- 5 | 6 | {{content}} -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | sectionid: page 4 | --- 5 | 6 | {% capture version %}{% version %}{% endcapture %} 7 | 8 |
9 | 47 |
48 | {% if page.title != null %} 49 |

{{page.title}}

50 | {% endif %} 51 |
52 | {{content}} 53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /docs/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | sectionid: blog 4 | --- 5 | 6 | {% capture version %}{% version %}{% endcapture %} 7 | 8 |
9 | 18 |
19 |

20 | {{page.title}} 21 | 22 | 23 | by {{ page.author }} 24 |

25 |
26 | {{content}} 27 |
28 | 29 |
30 | 41 | 42 |
43 |
44 | 45 | 57 | -------------------------------------------------------------------------------- /docs/_plugins/link.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Jekyll 4 | class MartyUrl < Liquid::Tag 5 | 6 | VARIABLE_SYNTAX = /(?[^{]*\{\{\s*(?[\w\-\.]+)\s*(\|.*)?\}\}[^\s}]*)(?.*)/ 7 | 8 | def initialize(tag_name, text, tokens) 9 | super 10 | matched = text.strip.match(VARIABLE_SYNTAX) 11 | if matched 12 | @text = matched['variable'].strip 13 | else 14 | @text = text; 15 | end 16 | 17 | config_path = File.expand_path(File.dirname(__FILE__) + "/../_config.yml") 18 | @version = YAML.load_file(config_path)['current_version'] 19 | end 20 | 21 | def render(context) 22 | 23 | if @text.match(VARIABLE_SYNTAX) 24 | var = @text.gsub(/{{/, '').gsub(/}}/, '') 25 | @text = context[var] || '' 26 | end 27 | 28 | if ENV['VERSION'] == 'true' 29 | "/v/#{@version}#{@text.strip}".strip 30 | else 31 | @text.strip 32 | end 33 | end 34 | end 35 | end 36 | 37 | Liquid::Template.register_tag('url', Jekyll::MartyUrl) -------------------------------------------------------------------------------- /docs/_plugins/sample.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module Tags 3 | class SampleBlock < Liquid::Block 4 | include Liquid::StandardFilters 5 | 6 | def initialize(tag_name, markup, tokens) 7 | super 8 | end 9 | 10 | def render(context) 11 | code = super.to_s 12 | 13 | lines = code.split("\n") 14 | styles = {} 15 | current_style = nil 16 | 17 | lines.each_with_index do |line, index| 18 | if line =~ /^==/ 19 | if current_style 20 | styles[current_style].pop 21 | end 22 | 23 | current_style = lines[index -1] 24 | styles[current_style] = [] 25 | elsif current_style 26 | styles[current_style] << line 27 | end 28 | end 29 | 30 | output = "
" 31 | 32 | output += "\t
" 33 | 34 | styles.each do |style, lines| 35 | output += "\t" 36 | end 37 | 38 | output += "\t
" 39 | 40 | styles.each do |style, lines| 41 | code = lines.join("\n").strip 42 | 43 | output += add_code_tag(render_rouge(code), style) 44 | end 45 | 46 | output += "\n
" 47 | end 48 | 49 | 50 | def render_rouge(code) 51 | require('rouge') 52 | formatter = Rouge::Formatters::HTML.new(line_numbers: false, wrap: false) 53 | lexer = Rouge::Lexer.find_fancy('js', code) 54 | formatter.format(lexer.lex(code)) 55 | end 56 | 57 | def add_code_tag(code, style) 58 | code_attributes = [ 59 | "data-lang=\"js\"", 60 | "class=\"language-js\"" 61 | ].join(" ") 62 | "
#{code.chomp}
" 63 | end 64 | end 65 | end 66 | end 67 | 68 | Liquid::Template.register_tag('sample', Jekyll::Tags::SampleBlock) -------------------------------------------------------------------------------- /docs/_plugins/version.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Jekyll 4 | class Version < Liquid::Tag 5 | 6 | def initialize(tag_name, collection_name, tokens) 7 | super 8 | end 9 | 10 | def render(context) 11 | config_path = File.expand_path(File.dirname(__FILE__) + "/../_config.yml") 12 | version = YAML.load_file(config_path)['current_version'] 13 | 14 | if ENV['VERSION'] == 'true' 15 | "/v/#{version}".strip 16 | end 17 | end 18 | end 19 | end 20 | 21 | Liquid::Template.register_tag('version', Jekyll::Version) -------------------------------------------------------------------------------- /docs/_posts/2015-03-24-marty-v0.9.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: marty.js v0.9 3 | layout: post 4 | author: James Hollingworth 5 | author_url: http://github.com/jhollingworth 6 | --- 7 | 8 | It's been 2 months since we first publicly announced Marty. Since then we've been working hard building new features and refining our API. Today I'm happy to announce Marty v0.9! 9 | 10 | So what's new in this release? 11 | 12 |

Isomorphisim

13 | 14 | Being able to run your JS application on the server and in the browser has been one of the major reasons people have started using React. We've found it's still challenging to build isomorphic applications and so we've built a set of APIs that make it easier. 15 | 16 | [``Marty.renderToString``]({% url /api/top-level-api/index.html#renderToString %}) is a smarter version of [``React.renderToString``](http://facebook.github.io/react/docs/top-level-api.html#react.rendertostring) which knows what state each component needs, fetches it for you and then renders the component. It also gives you lots of diagnostic information to understand what Marty is doing on the server: 17 | 18 | Marty Developer Tools 19 | 20 | For synchronizing state between the server and browser we have [``Marty.dehydrate()``]({% url /api/top-level-api/#dehydrate %}) and [``Marty.rehydrate()``]({% url /api/top-level-api/#rehydrate %}). [``Marty.dehydrate()``]({% url /api/top-level-api/#dehydrate %}) serializes the contents of your stores to a string which is sent down with the rendered HTML. When your application first loads in the browser you just need to call [``Marty.rehydrate()``]({% url /api/top-level-api/#rehydrate %}) to have your stores return back to their state when on the server. 21 | 22 | We've also created [marty-express]({% url /guides/isomorphism/marty-express.html %}), a middleware for [express.js](expressjs.com) which consumes [react-router](https://github.com/rackt/react-router) routes so you don't have to have to duplicate your routing on the client and the server. It also modifies the [HTTP state source]({% url /api/state-sources/http.html %}) so HTTP requests made on the server will behave the same as they would in the browser. 23 | 24 | If you'd like to learn more, we've written a [guide on how to get started]({% url /guides/isomorphism/index.html %}). Our [example chat app](https://github.com/martyjs/marty-chat-example) has also been updated to be isomorphic. 25 | 26 |

ES6 Classes

27 | 28 | To complement React ability to define components using ES6 classes we now support defining all types using classes as well. You can read our guide on [ES6]({% url /guides/es6/index.html %}) to find out how to make the switch. We have also created a new version of the [example chat app which uses ES6 classes](https://github.com/martyjs/marty-chat-example-es6). 29 | 30 | {% sample %} 31 | classic 32 | ======= 33 | var UsersStore = Marty.createStore({ 34 | id: 'UsersStore', 35 | handlers: { 36 | addUser: Constants.RECEIVE_USER 37 | }, 38 | getInitialState: function () { 39 | return []; 40 | }, 41 | addUser: function (user) { 42 | this.state.push(user); 43 | this.hasChanged(); 44 | } 45 | }); 46 | 47 | es6 48 | === 49 | class UsersStore extends Marty.Store { 50 | constructor(options) { 51 | super(options); 52 | this.state = []; 53 | this.handlers = { 54 | addUser: Constants.RECEIVE_USER 55 | }; 56 | } 57 | addUser(user) { 58 | this.state.push(user); 59 | this.hasChanged(); 60 | } 61 | } 62 | 63 | {% endsample %} 64 | 65 |

Containers

66 | 67 | When defining components using ES6 classes you can no longer use mixins which means you won't be able to use the [state mixin]({% url /guides/state-mixin/index.html %}). A new pattern emerging to solve this problem is [to wrap your components in "container" components](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750). We're a big fan of this pattern and so we've introduced [``Marty.createContainer``]({% url /api/top-level-api/index.html#createContainer %}) which creates a container component that does the same job as the [state mixin]({% url /guides/state-mixin/index.html %}). You can checkout our [containers guide]({% url /guides/containers/index.html %}) to learn more about them. 68 | 69 | {% highlight js %} 70 | class User extends React.Component { 71 | render() { 72 | return
{this.props.user}
; 73 | } 74 | } 75 | 76 | module.exports = Marty.createContainer(User, { 77 | listenTo: UserStore, 78 | fetch: { 79 | user() { 80 | return UserStore.for(this).getUser(this.props.id); 81 | } 82 | }, 83 | failed(errors) { 84 | return
{errors}
; 85 | }, 86 | pending() { 87 | return this.done({ 88 | user: {} 89 | }); 90 | } 91 | }); 92 | {% endhighlight %} 93 | 94 |

Queries

95 | 96 |

97 | Queries are a new type we're introducing that is responsible for coordinating getting new state from outside of the application. They should help reduce some confusion about how to dispatch actions when fetching state. You can read our guide to learn why they've been introduced. 98 |

99 | 100 | {% sample %} 101 | classic 102 | ======= 103 | var UserQueries = Marty.createQueries({ 104 | id: 'UserQueries', 105 | getUser: function (id) { 106 | this.dispatch(UserActions.RECEIVE_USER_STARTING, id); 107 | UserAPI.getUser(id).then(function (res) { 108 | if (res.status === 200) { 109 | this.dispatch(UserActions.RECEIVE_USER, res.body, id); 110 | } else { 111 | this.dispatch(UserActions.RECEIVE_USER_FAILED, id); 112 | } 113 | }.bind(this)).catch(function (err) { 114 | this.dispatch(UserActions.RECEIVE_USER_FAILED, id, err); 115 | }.bind(this)) 116 | } 117 | }); 118 | 119 | es6 120 | === 121 | class UserQueries extends Marty.Queries { 122 | getUser(id) { 123 | this.dispatch(UserActions.RECEIVE_USER_STARTING, id); 124 | UserAPI.getUser(id).then((res) => { 125 | if (res.status === 200) { 126 | this.dispatch(UserActions.RECEIVE_USER, res.body, id); 127 | } else { 128 | this.dispatch(UserActions.RECEIVE_USER_FAILED, id); 129 | } 130 | }).catch((err) => this.dispatch(UserActions.RECEIVE_USER_FAILED, id, err)); 131 | } 132 | } 133 | {% endsample %} 134 | 135 |

Action creators

136 | 137 | Our approach to defining [action creators](http://martyjs.org/v/0.8.15/guides/action-creators/index.html) was the source of lots of confusion and many bugs. We've got a new, simplified approach which should resolve these issues. 138 | 139 | {% sample %} 140 | classic 141 | ======= 142 | var UserConstants = Marty.createConstants(["UPDATE_EMAIL"]); 143 | 144 | var UserActionCreators = Marty.createActionCreators({ 145 | id: 'UserActionCreators', 146 | updateEmail: function (userId, email) { 147 | this.dispatch(UserConstants.UPDATE_EMAIL, userId, email) 148 | } 149 | }); 150 | es6 151 | === 152 | var UserConstants = Marty.createConstants(["UPDATE_EMAIL"]); 153 | 154 | class UserActionCreators extends Marty.ActionCreators { 155 | updateEmail(userId, email) { 156 | this.dispatch(UserConstants.UPDATE_EMAIL, userId, email) 157 | } 158 | } 159 | {% endsample %} 160 | 161 |

Components only re-rendered once per action

162 | 163 | There was an [interesting discussion in the Flux panel at React Conf about batching store updates to improve performance](https://www.youtube.com/watch?v=LTj4O7WJJ98&list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr#t=473). Marty now does this automatically for you so no matter how many times your store changes your components will only be re-rendered once per action. 164 | 165 |

Developer Tools

166 | 167 | [Marty Developer Tools]({% url /guides/developer-tools/index.html %}) has been given an overhaul. It can now tell you things like what components re-rendered as a result of the action and which stores caused a component to re-render. It also gives you the ability to reset the applications state to a specific action (think ``get reset --hard actionId``). 168 | 169 | Marty Developer Tools 170 | 171 |

lodash

172 | 173 | Thanks to [@jdalton](https://github.com/jdalton) we've moved from underscore.js to lodash (This is an internal change so wont effect you if you're using underscore in your application). This has resulted in a **6KB drop** in our gzipped file size. Unfortunately Marty has also grown by 6KB so Marty v0.9 is the same size as Marty v0.8 :( 174 | 175 |

Changelog

176 | 177 | For those currently using Marty v0.8, we've written a [guide for upgrading to v0.9]({% url /guides/upgrading/08_09.html %}). [@dariocravero](https://github.com/dariocravero) also has an [excellent guide on making the move to ES6](https://gist.github.com/dariocravero/2ea4308946c15f2da1af). 178 | 179 | ###Breaking changes 180 | 181 | ``Store#setState`` has been renamed to [``Store#replaceState``]({% url /api/stores/index.html#replaceState %}). [``Store#setState``]({% url /api/stores/index.html#setState %}) will now merge the existing state with the new state. This is to more closely follow the React API. 182 | 183 | ###New features 184 | 185 | - Isomorphism ([#13](http://github.com/martyjs/marty/issues/13)) 186 | - CookieStateSource & LocationStateSource ([#205](http://github.com/martyjs/marty/issues/205)) 187 | - ES6 Classes ([#89](http://github.com/martyjs/marty/issues/89)) 188 | - Add dataType option to http state source ([#176](http://github.com/martyjs/marty/issues/176)) 189 | - Lodash v3 instead of underscore ([#170](http://github.com/martyjs/marty/issues/170)) 190 | - HttpStateSource hooks ([#118](http://github.com/martyjs/marty/issues/118)) 191 | - FetchResult#toPromise ([#180](http://github.com/martyjs/marty/issues/180)) 192 | - Clear fetch history in Store#clear ([#149](http://github.com/martyjs/marty/issues/149)) 193 | - Batch store change events ([#183](http://github.com/martyjs/marty/issues/183)) 194 | - Allow you to specify when function context ([#184](http://github.com/martyjs/marty/issues/184)) 195 | - Marty.createContainer ([#206](http://github.com/martyjs/marty/issues/206)) 196 | - Set request credentials to 'same-origin' ([#209](http://github.com/martyjs/marty/issues/209)) 197 | 198 | **Bugs** 199 | 200 | - dependsOn doesn't update when dependent store updates ([#113](http://github.com/martyjs/marty/issues/113)) 201 | - Don't auto set content-type if using FormData ([#140](http://github.com/martyjs/marty/issues/140)) 202 | - Fetch API compatibility ([#133](http://github.com/martyjs/marty/issues/133)) 203 | 204 | ###Deprecations 205 | 206 | - [Invokable action creators]({% url /guides/action-creators/migrating-from-v8.html %}) 207 | - [State Mixin]({% url /guides/state-mixin/index.html %}), use [containers]({% url /guides/containers/index.html %}) instead. -------------------------------------------------------------------------------- /docs/_posts/2015-05-27-marty-v0.10.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: marty.js v0.10 3 | layout: post 4 | author: James Hollingworth 5 | author_url: http://github.com/jhollingworth 6 | --- 7 | 8 | Today I'm pleased to announce Marty v0.10! So whats new? 9 | 10 |

marty-native

11 | 12 | To complement React Natives arrival we now have [marty-native]({% url /guides/marty-native/index.html %}). Internally marty and marty-native are actually the same code, the only difference being we need to require in `react-native` instead of `react`. This opens up some interesting code-sharing abilities we will discuss soon. You can check out our [native chat example](https://github.com/martyjs/MartyNativeChatExample) to see how to use Marty with React Native. 13 | 14 | We've also updated Marty Developer Tools to allow you to debug native applications (as well as any app outside of Chrome, e.g. [electron](https://github.com/atom/electron)). [See our guide for more details]({% url /guides/developer-tools/index.html#marty-native %}). 15 | 16 | 17 | 18 |

No more singletons

19 | 20 | In previous versions of Marty everything was a singleton. This made it very simple to write your app, you just require in whatever you want and start using it. Unfortunately in exchange for simplicity we made some very important things like isomorphism and testing much harder. We introduced some changes in v0.9 (e.g. `for`) which helped improve the situation but never truly resolved it and so in v0.10 we decided to solve it once and for all. 21 | 22 | In v0.10 there are no more singletons. Functions like `Marty.createStore` will now return a type rather than an instance, `Marty.register` is no longer needed and there is no global dispatcher. Instead we introduce a new [`Application`]({% url /guides/application/index.html %}) type which is responsible for knowing about everything about your application. 23 | 24 | {% sample %} 25 | classic 26 | ======= 27 | var Marty = require('marty'); 28 | var ApplicationContainer = Marty.ApplicationContainer; 29 | 30 | var ExampleApp = Marty.createApplication(function () { 31 | this.register({ 32 | user: { 33 | store: require('./stores/userStore'), 34 | queries: require('./queries/userQueries'), 35 | actionCreators: require('./actions/userActionCreators'), 36 | } 37 | }); 38 | }); 39 | 40 | var app = new ExampleApp(); 41 | 42 | app.user.store.getUser(1); 43 | app.dispatcher.register(console.log.bind(console)); 44 | 45 | React.render(( 46 | 47 | 48 | 49 | ), document.body); 50 | es6 51 | === 52 | var { 53 | Application, 54 | ApplicationContainer 55 | } = require('marty'); 56 | 57 | // application.js 58 | class ExampleApp extends Application { 59 | constructor(options) { 60 | super(options); 61 | 62 | this.register({ 63 | user: { 64 | store: require('./stores/userStore'), 65 | queries: require('./queries/userQueries'), 66 | actionCreators: require('./actions/userActionCreators'), 67 | } 68 | }); 69 | } 70 | } 71 | 72 | var app = new ExampleApp(); 73 | 74 | app.user.store.getUser(1); 75 | app.dispatcher.register(console.log.bind(console)); 76 | 77 | React.render(( 78 | 79 | 80 | 81 | ), document.body); 82 | 83 | {% endsample %} 84 | 85 | `Application`s give you more fine grained control over how you build your application, allowing you to do things like run multiple independent applications on a single page. 86 | 87 | Testing your application becomes much simpler now that you can test components in isolation. We have a new set of [test utils]({% url /api/test-utils/index.html %}) that make testing Marty applications much easier. To accompany these utils we have a some [examples about how to test the various bits of a Marty application](https://github.com/martyjs/marty-test-examples). 88 | 89 | This will be a fairly large change for those upgrading from v0.9. Our [upgrade guide]({% url /guides/upgrading/09_10.html %}) should cover everything you need to know. We've also updated our [chat example](https://github.com/martyjs/marty-chat-example-es6/). If you need any further help you can chat to us on [gitter.im/martyjs/marty](https://gitter.im/martyjs/marty) 90 | 91 |

ES7 features

92 | 93 | While ES7 is still a while away, thanks to [babel](https://babeljs.io/docs/usage/experimental/) we can start playing with it now. We've added experimental support for [class properties](https://gist.github.com/jeffmo/054df782c05639da2adb) and [decorators](https://github.com/wycats/javascript-decorators). To enable make sure stage 0 is enabled in your [babel options](https://babeljs.io/docs/usage/options/). 94 | 95 | {% highlight js %} 96 | let { handles } = require('marty'); 97 | 98 | class UserStore extends Marty.Store { 99 | // Equivalent to getInitialState() 100 | state = { }; 101 | 102 | @handles(UserConstants.RECEIVE_USER) 103 | addUser(user) { 104 | ... 105 | } 106 | } 107 | {% endhighlight %} 108 | 109 |

Marty's gone on a diet

110 | 111 | We've shave 2.5KB off Marty's gzipped file size (25KB vs 27.5KB). This was mostly down to us removing a lot of old, unused features which we're making Marty bloated and complicated to understand. 112 | 113 |

Changelog

114 | 115 | For those currently using Marty v0.9, we've written a [guide for upgrading to v0.10]({% url /guides/upgrading/09_10.html %}). 116 | 117 | ### New features 118 | 119 | - [marty-native](https://github.com/martyjs/marty/issues/230) 120 | - [No singleton](https://github.com/martyjs/marty/issues/261) 121 | - [ES7 features](https://github.com/martyjs/marty-lib/pull/3) 122 | - [Improved testing](https://github.com/martyjs/marty/issues/19) 123 | - DevTools supports marty-native 124 | 125 | ### Deprecations 126 | 127 | - Due to API incompatibility issues `parseJSON` will be removed by default in future versions. Instead you should call `this.json()` from within your state source. 128 | 129 | ### Removed 130 | 131 | - `Marty.register()` is no longer needed, just return the type 132 | - You will no longer be able to pass in a store or an object hash of stores into `Marty.createStateMixin` 133 | - [Rollbacks](http://martyjs.org/v/0.9.0/api/stores/index.html#rollback) have been removed entirely 134 | - [Store handler values](http://martyjs.org/api/stores/#handlers) can only be either a string or an array of strings. You cannot do complex object queries any more. 135 | - `Marty.registry` is no longer supported. Use applications instead. 136 | - `Marty.createContext()` is no longer supported. Use applications instead. 137 | - `Marty.renderToString()` is no longer supported. Use `Application#renderToString()` instead 138 | - `Store#rollback()` is no longer supported. You should instead dispatch an error action. 139 | - `.for(this)` has been removed. Use applications instead. 140 | - You no longer to specify `id`'s in the type. Instead define the Id on the `Application#register()` 141 | - `require('marty').Dispatcher` is no longer supported. Create an application and access the [dispatcher](http://martyjs.org/api/application/index.html#dispatcher). 142 | - `require('marty/http/hooks')` is no longer supported. Use `require('marty').hooks` instead 143 | - `require('marty/environment')` is no longer supported. Use `require('marty').environment` 144 | - `require('marty/fetch')` is no longer supported. Use `require('marty').fetch` 145 | - `require('marty/when')` is no longer supported. Use `require('marty').when` 146 | - `require('marty/autoDispatch')` is no longer supported. Use `require('marty').autoDispatch` 147 | - `require('marty').Diagnostics` is no longer supported. Use `require('marty').diagnostics` -------------------------------------------------------------------------------- /docs/_posts/2015-08-02-marty-last.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Marty v0.10 will be the last major release 3 | layout: post 4 | author: James Hollingworth 5 | author_url: http://github.com/jhollingworth 6 | --- 7 | 8 | I started working on Marty last year in response to my dissatisfaction with the existing Flux libraries out there. 9 months later I no longer feel the same way and so I'm sad to say Marty v0.10 will be the last major release. Truth be told I've [done a flummox](https://github.com/acdlite/flummox#40-will-likely-be-the-last-major-release-use-redux-instead-its-really-great) and started using [redux](https://github.com/gaearon/redux). I recommend you check it out, it really is a better way of thinking about flux. 9 | 10 | For those who have built apps on top of Marty, I'm sorry for the inconvinience. I recommend you look at migrating to [Alt](http://alt.js.org/), the differences between Marty & Alt are mostly superficial now. There are a few features missing however [@goatslacker](https://github.com/goatslacker) and [@taion](https://github.com/taion) are working on [migrating them over](https://github.com/goatslacker/alt/issues/337). In the interim we will continue to accept pull requests and there will likely be the occasional patch. 11 | 12 | I would like to say a big thank you to everyone who has helped work on Marty and improve the code base. Especially to [@taion](https://github.com/taion) who has been a constant source of help and pull requests. -------------------------------------------------------------------------------- /docs/_support/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | sectionid: support 4 | --- 5 | 6 |
7 |

Support

8 | 9 |

Stack Overflow

10 | 11 | Stackoverflow is a great way to ask a question. We follow the martyjs tag so be sure to add it to your question. 12 | 13 |

Want to chat?

14 | We have a Gitter chatroom where you can chat about Marty. We also have a slack channel on reactiflux.slack.com. 15 | 16 | 17 |

Mailing list

18 | We have a new mailing list for any questions you have. 19 | 20 |

Github issues

21 | 22 | If you've got found a bug in Marty, then please create an issue on Github. 23 | 24 |

Twitter

25 | 26 | You can keep up to date on marty.js using the #martyjs hash tag on Twitter. 27 | 28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /docs/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | white-space: nowrap; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | -moz-box-sizing: content-box; 40 | box-sizing: content-box; 41 | } 42 | 43 | .CodeMirror-guttermarker { color: black; } 44 | .CodeMirror-guttermarker-subtle { color: #999; } 45 | 46 | /* CURSOR */ 47 | 48 | .CodeMirror div.CodeMirror-cursor { 49 | border-left: 1px solid black; 50 | } 51 | /* Shown when moving in bi-directional text */ 52 | .CodeMirror div.CodeMirror-secondarycursor { 53 | border-left: 1px solid silver; 54 | } 55 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 56 | width: auto; 57 | border: 0; 58 | background: #7e7; 59 | } 60 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursors { 61 | z-index: 1; 62 | } 63 | 64 | .cm-animate-fat-cursor { 65 | width: auto; 66 | border: 0; 67 | -webkit-animation: blink 1.06s steps(1) infinite; 68 | -moz-animation: blink 1.06s steps(1) infinite; 69 | animation: blink 1.06s steps(1) infinite; 70 | } 71 | @-moz-keyframes blink { 72 | 0% { background: #7e7; } 73 | 50% { background: none; } 74 | 100% { background: #7e7; } 75 | } 76 | @-webkit-keyframes blink { 77 | 0% { background: #7e7; } 78 | 50% { background: none; } 79 | 100% { background: #7e7; } 80 | } 81 | @keyframes blink { 82 | 0% { background: #7e7; } 83 | 50% { background: none; } 84 | 100% { background: #7e7; } 85 | } 86 | 87 | /* Can style cursor different in overwrite (non-insert) mode */ 88 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 89 | 90 | .cm-tab { display: inline-block; text-decoration: inherit; } 91 | 92 | .CodeMirror-ruler { 93 | border-left: 1px solid #ccc; 94 | position: absolute; 95 | } 96 | 97 | /* DEFAULT THEME */ 98 | 99 | .cm-s-default .cm-keyword {color: #708;} 100 | .cm-s-default .cm-atom {color: #219;} 101 | .cm-s-default .cm-number {color: #164;} 102 | .cm-s-default .cm-def {color: #00f;} 103 | .cm-s-default .cm-variable, 104 | .cm-s-default .cm-punctuation, 105 | .cm-s-default .cm-property, 106 | .cm-s-default .cm-operator {} 107 | .cm-s-default .cm-variable-2 {color: #05a;} 108 | .cm-s-default .cm-variable-3 {color: #085;} 109 | .cm-s-default .cm-comment {color: #a50;} 110 | .cm-s-default .cm-string {color: #a11;} 111 | .cm-s-default .cm-string-2 {color: #f50;} 112 | .cm-s-default .cm-meta {color: #555;} 113 | .cm-s-default .cm-qualifier {color: #555;} 114 | .cm-s-default .cm-builtin {color: #30a;} 115 | .cm-s-default .cm-bracket {color: #997;} 116 | .cm-s-default .cm-tag {color: #170;} 117 | .cm-s-default .cm-attribute {color: #00c;} 118 | .cm-s-default .cm-header {color: blue;} 119 | .cm-s-default .cm-quote {color: #090;} 120 | .cm-s-default .cm-hr {color: #999;} 121 | .cm-s-default .cm-link {color: #00c;} 122 | 123 | .cm-negative {color: #d44;} 124 | .cm-positive {color: #292;} 125 | .cm-header, .cm-strong {font-weight: bold;} 126 | .cm-em {font-style: italic;} 127 | .cm-link {text-decoration: underline;} 128 | 129 | .cm-s-default .cm-error {color: #f00;} 130 | .cm-invalidchar {color: #f00;} 131 | 132 | /* Default styles for common addons */ 133 | 134 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 135 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 136 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 137 | .CodeMirror-activeline-background {background: #e8f2ff;} 138 | 139 | /* STOP */ 140 | 141 | /* The rest of this file contains styles related to the mechanics of 142 | the editor. You probably shouldn't touch them. */ 143 | 144 | .CodeMirror { 145 | line-height: 1; 146 | position: relative; 147 | overflow: hidden; 148 | background: white; 149 | color: black; 150 | } 151 | 152 | .CodeMirror-scroll { 153 | /* 30px is the magic margin used to hide the element's real scrollbars */ 154 | /* See overflow: hidden in .CodeMirror */ 155 | margin-bottom: -30px; margin-right: -30px; 156 | padding-bottom: 30px; 157 | height: 100%; 158 | outline: none; /* Prevent dragging from highlighting the element */ 159 | position: relative; 160 | -moz-box-sizing: content-box; 161 | box-sizing: content-box; 162 | } 163 | .CodeMirror-sizer { 164 | position: relative; 165 | border-right: 30px solid transparent; 166 | -moz-box-sizing: content-box; 167 | box-sizing: content-box; 168 | } 169 | 170 | /* The fake, visible scrollbars. Used to force redraw during scrolling 171 | before actuall scrolling happens, thus preventing shaking and 172 | flickering artifacts. */ 173 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 174 | position: absolute; 175 | z-index: 6; 176 | display: none; 177 | } 178 | .CodeMirror-vscrollbar { 179 | right: 0; top: 0; 180 | overflow-x: hidden; 181 | overflow-y: scroll; 182 | } 183 | .CodeMirror-hscrollbar { 184 | bottom: 0; left: 0; 185 | overflow-y: hidden; 186 | overflow-x: scroll; 187 | } 188 | .CodeMirror-scrollbar-filler { 189 | right: 0; bottom: 0; 190 | } 191 | .CodeMirror-gutter-filler { 192 | left: 0; bottom: 0; 193 | } 194 | 195 | .CodeMirror-gutters { 196 | position: absolute; left: 0; top: 0; 197 | padding-bottom: 30px; 198 | z-index: 3; 199 | } 200 | .CodeMirror-gutter { 201 | white-space: normal; 202 | height: 100%; 203 | -moz-box-sizing: content-box; 204 | box-sizing: content-box; 205 | padding-bottom: 30px; 206 | margin-bottom: -32px; 207 | display: inline-block; 208 | /* Hack to make IE7 behave */ 209 | *zoom:1; 210 | *display:inline; 211 | } 212 | .CodeMirror-gutter-elt { 213 | position: absolute; 214 | cursor: default; 215 | z-index: 4; 216 | } 217 | 218 | .CodeMirror-lines { 219 | cursor: text; 220 | min-height: 1px; /* prevents collapsing before first draw */ 221 | } 222 | .CodeMirror pre { 223 | /* Reset some styles that the rest of the page might have set */ 224 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 225 | border-width: 0; 226 | background: transparent; 227 | font-family: inherit; 228 | font-size: inherit; 229 | margin: 0; 230 | white-space: pre; 231 | word-wrap: normal; 232 | line-height: inherit; 233 | color: inherit; 234 | z-index: 2; 235 | position: relative; 236 | overflow: visible; 237 | } 238 | .CodeMirror-wrap pre { 239 | word-wrap: break-word; 240 | white-space: pre-wrap; 241 | word-break: normal; 242 | } 243 | 244 | .CodeMirror-linebackground { 245 | position: absolute; 246 | left: 0; right: 0; top: 0; bottom: 0; 247 | z-index: 0; 248 | } 249 | 250 | .CodeMirror-linewidget { 251 | position: relative; 252 | z-index: 2; 253 | overflow: auto; 254 | } 255 | 256 | .CodeMirror-widget {} 257 | 258 | .CodeMirror-wrap .CodeMirror-scroll { 259 | overflow-x: hidden; 260 | } 261 | 262 | .CodeMirror-measure { 263 | position: absolute; 264 | width: 100%; 265 | height: 0; 266 | overflow: hidden; 267 | visibility: hidden; 268 | } 269 | .CodeMirror-measure pre { position: static; } 270 | 271 | .CodeMirror div.CodeMirror-cursor { 272 | position: absolute; 273 | border-right: none; 274 | width: 0; 275 | } 276 | 277 | div.CodeMirror-cursors { 278 | visibility: hidden; 279 | position: relative; 280 | z-index: 3; 281 | } 282 | .CodeMirror-focused div.CodeMirror-cursors { 283 | visibility: visible; 284 | } 285 | 286 | .CodeMirror-selected { background: #d9d9d9; } 287 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 288 | .CodeMirror-crosshair { cursor: crosshair; } 289 | 290 | .cm-searching { 291 | background: #ffa; 292 | background: rgba(255, 255, 0, .4); 293 | } 294 | 295 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 296 | .CodeMirror span { *vertical-align: text-bottom; } 297 | 298 | /* Used to force a border model for a node */ 299 | .cm-force-border { padding-right: .1px; } 300 | 301 | @media print { 302 | /* Hide the cursor when printing */ 303 | .CodeMirror div.CodeMirror-cursors { 304 | visibility: hidden; 305 | } 306 | } 307 | 308 | /* Help users use markselection to safely style text background */ 309 | span.CodeMirror-selectedtext { background: none; } 310 | -------------------------------------------------------------------------------- /docs/css/syntax.css: -------------------------------------------------------------------------------- 1 | .highlight pre code { 2 | color: #637c84; 3 | } 4 | 5 | .highlight { 6 | color: #333333; 7 | background: #f8f5ec; 8 | } 9 | 10 | .highlight .c { 11 | color: #93a1a1; 12 | } 13 | 14 | 15 | .highlight .g { 16 | color: #637c84; 17 | } 18 | 19 | /* Generic */ 20 | 21 | .highlight .k { 22 | color: #859900; 23 | } 24 | 25 | /* Keyword */ 26 | 27 | .highlight .l { 28 | color: #637c84; 29 | } 30 | 31 | /* Literal */ 32 | 33 | .highlight .n { 34 | color: #637c84; 35 | } 36 | 37 | /* Name */ 38 | 39 | .highlight .o { 40 | color: #859900; 41 | } 42 | 43 | /* Operator */ 44 | 45 | .highlight .x { 46 | color: #cc7a6f; 47 | } 48 | 49 | /* Other */ 50 | 51 | .highlight .p { 52 | color: #637c84; 53 | } 54 | 55 | /* Punctuation */ 56 | 57 | .highlight .cm { 58 | color: #93a1a1; 59 | } 60 | 61 | /* Comment.Multiline */ 62 | 63 | .highlight .cp { 64 | color: #859900; 65 | } 66 | 67 | /* Comment.Preproc */ 68 | 69 | .highlight .c1 { 70 | color: #93a1a1; 71 | } 72 | 73 | /* Comment.Single */ 74 | 75 | .highlight .cs { 76 | color: #859900; 77 | } 78 | 79 | /* Comment.Special */ 80 | 81 | .highlight .gd { 82 | color: #36958e; 83 | } 84 | 85 | /* Generic.Deleted */ 86 | 87 | .highlight .ge { 88 | font-style: italic; 89 | color: #637c84; 90 | } 91 | 92 | /* Generic.Emph */ 93 | 94 | .highlight .gr { 95 | color: #dc322f; 96 | } 97 | 98 | /* Generic.Error */ 99 | 100 | .highlight .gh { 101 | color: #cc7a6f; 102 | } 103 | 104 | /* Generic.Heading */ 105 | 106 | .highlight .gi { 107 | color: #859900; 108 | } 109 | 110 | /* Generic.Inserted */ 111 | 112 | .highlight .go { 113 | color: #637c84; 114 | } 115 | 116 | /* Generic.Output */ 117 | 118 | .highlight .gp { 119 | color: #637c84; 120 | } 121 | 122 | /* Generic.Prompt */ 123 | 124 | .highlight .gs { 125 | font-weight: bold; 126 | color: #637c84; 127 | } 128 | 129 | /* Generic.Strong */ 130 | 131 | .highlight .gu { 132 | color: #cc7a6f; 133 | } 134 | 135 | /* Generic.Subheading */ 136 | 137 | .highlight .gt { 138 | color: #637c84; 139 | } 140 | 141 | /* Generic.Traceback */ 142 | 143 | .highlight .kc { 144 | color: #cc7a6f; 145 | } 146 | 147 | /* Keyword.Constant */ 148 | 149 | .highlight .kd { 150 | color: #268bd2; 151 | } 152 | 153 | /* Keyword.Declaration */ 154 | 155 | .highlight .kn { 156 | color: #859900; 157 | } 158 | 159 | /* Keyword.Namespace */ 160 | 161 | .highlight .kp { 162 | color: #859900; 163 | } 164 | 165 | /* Keyword.Pseudo */ 166 | 167 | .highlight .kr { 168 | color: #268bd2; 169 | } 170 | 171 | /* Keyword.Reserved */ 172 | 173 | .highlight .kt { 174 | color: #dc322f; 175 | } 176 | 177 | /* Keyword.Type */ 178 | 179 | .highlight .ld { 180 | color: #637c84; 181 | } 182 | 183 | /* Literal.Date */ 184 | 185 | .highlight .m { 186 | color: #36958e; 187 | } 188 | 189 | /* Literal.Number */ 190 | 191 | .highlight .s { 192 | color: #36958e; 193 | } 194 | 195 | /* Literal.String */ 196 | 197 | .highlight .na { 198 | color: #637c84; 199 | } 200 | 201 | /* Name.Attribute */ 202 | 203 | .highlight .nb { 204 | color: #b58900; 205 | } 206 | 207 | /* Name.Builtin */ 208 | 209 | .highlight .nc { 210 | color: #268bd2; 211 | } 212 | 213 | /* Name.Class */ 214 | 215 | .highlight .no { 216 | color: #cc7a6f; 217 | } 218 | 219 | /* Name.Constant */ 220 | 221 | .highlight .nd { 222 | color: #268bd2; 223 | } 224 | 225 | /* Name.Decorator */ 226 | 227 | .highlight .ni { 228 | color: #cc7a6f; 229 | } 230 | 231 | /* Name.Entity */ 232 | 233 | .highlight .ne { 234 | color: #cc7a6f; 235 | } 236 | 237 | /* Name.Exception */ 238 | 239 | .highlight .nf { 240 | color: #268bd2; 241 | } 242 | 243 | /* Name.Function */ 244 | 245 | .highlight .nl { 246 | color: #637c84; 247 | } 248 | 249 | /* Name.Label */ 250 | 251 | .highlight .nn { 252 | color: #637c84; 253 | } 254 | 255 | /* Name.Namespace */ 256 | 257 | .highlight .nx { 258 | color: #637c84; 259 | } 260 | 261 | /* Name.Other */ 262 | 263 | .highlight .py { 264 | color: #637c84; 265 | } 266 | 267 | /* Name.Property */ 268 | 269 | .highlight .nt { 270 | color: #268bd2; 271 | } 272 | 273 | /* Name.Tag */ 274 | 275 | .highlight .nv { 276 | color: #268bd2; 277 | } 278 | 279 | /* Name.Variable */ 280 | 281 | .highlight .ow { 282 | color: #859900; 283 | } 284 | 285 | /* Operator.Word */ 286 | 287 | .highlight .w { 288 | color: #637c84; 289 | } 290 | 291 | /* Text.Whitespace */ 292 | 293 | .highlight .mf { 294 | color: #36958e; 295 | } 296 | 297 | /* Literal.Number.Float */ 298 | 299 | .highlight .mh { 300 | color: #36958e; 301 | } 302 | 303 | /* Literal.Number.Hex */ 304 | 305 | .highlight .mi { 306 | color: #36958e; 307 | } 308 | 309 | /* Literal.Number.Integer */ 310 | 311 | .highlight .mo { 312 | color: #36958e; 313 | } 314 | 315 | /* Literal.Number.Oct */ 316 | 317 | .highlight .sb { 318 | color: #93a1a1; 319 | } 320 | 321 | /* Literal.String.Backtick */ 322 | 323 | .highlight .sc { 324 | color: #36958e; 325 | } 326 | 327 | /* Literal.String.Char */ 328 | 329 | .highlight .sd { 330 | color: #637c84; 331 | } 332 | 333 | /* Literal.String.Doc */ 334 | 335 | .highlight .s2 { 336 | color: #36958e; 337 | } 338 | 339 | /* Literal.String.Double */ 340 | 341 | .highlight .se { 342 | color: #cc7a6f; 343 | } 344 | 345 | /* Literal.String.Escape */ 346 | 347 | .highlight .sh { 348 | color: #637c84; 349 | } 350 | 351 | /* Literal.String.Heredoc */ 352 | 353 | .highlight .si { 354 | color: #36958e; 355 | } 356 | 357 | /* Literal.String.Interpol */ 358 | 359 | .highlight .sx { 360 | color: #36958e; 361 | } 362 | 363 | /* Literal.String.Other */ 364 | 365 | .highlight .sr { 366 | color: #dc322f; 367 | } 368 | 369 | /* Literal.String.Regex */ 370 | 371 | .highlight .s1 { 372 | color: #36958e; 373 | } 374 | 375 | /* Literal.String.Single */ 376 | 377 | .highlight .ss { 378 | color: #36958e; 379 | } 380 | 381 | /* Literal.String.Symbol */ 382 | 383 | .highlight .bp { 384 | color: #268bd2; 385 | } 386 | 387 | /* Name.Builtin.Pseudo */ 388 | 389 | .highlight .vc { 390 | color: #268bd2; 391 | } 392 | 393 | /* Name.Variable.Class */ 394 | 395 | .highlight .vg { 396 | color: #268bd2; 397 | } 398 | 399 | /* Name.Variable.Global */ 400 | 401 | .highlight .vi { 402 | color: #268bd2; 403 | } 404 | 405 | /* Name.Variable.Instance */ 406 | 407 | .highlight .il { 408 | color: #36958e; 409 | } -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/favicon.ico -------------------------------------------------------------------------------- /docs/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/img/data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/img/data-flow.png -------------------------------------------------------------------------------- /docs/img/devtools-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/img/devtools-action.png -------------------------------------------------------------------------------- /docs/img/devtools-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/img/devtools-data-flow.png -------------------------------------------------------------------------------- /docs/img/qubit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/img/qubit.png -------------------------------------------------------------------------------- /docs/img/renderToString-diagnostics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martyjs/marty/2a82b57ed49700a5c114839672c1affe2dbb4a4d/docs/img/renderToString-diagnostics.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: marty.js 4 | id: home 5 | --- 6 |
7 |
8 |

marty.js

9 |

10 | A JavaScript library for state management in React applications 11 |

12 |

13 | Get started now 14 |

15 |
16 |
17 |
18 |
19 |
20 |

Flux

21 |

22 | Marty is an implementation of the Flux architecture. It defines a set of rules about how to manage your applications state. Facebook invented Flux to help them build client-side web applications. 23 |

24 |
25 |
26 |

Declarative data fetching

27 |

28 | Marty has a unique fetch API that allows you to declaratively define your components state requirements. It also helps you define what to do when waiting for that state or when an error occurs. 29 |

30 |
31 |
32 |

Isomorphic

33 |

34 | Marty makes it easy to build JavaScript applications that run as happily on the server as they do in the browser. Read our guide on isomorphism to learn more. 35 |

36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /docs/js/headings.js: -------------------------------------------------------------------------------- 1 | $("h2[id],h3[id],h4[id]").each(function () { 2 | var $heading = $(this); 3 | var $link = $(""); 4 | 5 | $heading.append($link); 6 | 7 | $heading.hover(function () { 8 | $link.show(); 9 | }, function () { 10 | $link.hide(); 11 | }); 12 | }); -------------------------------------------------------------------------------- /docs/js/style.js: -------------------------------------------------------------------------------- 1 | setStyle(localStorage.getItem('style') || 'es6') 2 | 3 | function setStyle(style) { 4 | $(".sample .btn." + style).addClass("active"); 5 | $(".sample :not(.btn." + style + ")").removeClass("active"); 6 | 7 | $(".sample .highlight[data-style='" + style + "']").show(); 8 | $(".sample .highlight:not([data-style='" + style + "'])").hide(); 9 | localStorage.setItem('style', style); 10 | } 11 | 12 | $('.sample .btn').click(function () { 13 | setStyle($(this).data().style); 14 | }); -------------------------------------------------------------------------------- /examples/requirejs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | require.js example 5 | 6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /examples/requirejs/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | paths: { 3 | marty: '../../dist/marty', 4 | react: '../../node_modules/react/dist/react' 5 | }, 6 | shim: { 7 | marty: { 8 | deps: ['react'] 9 | } 10 | } 11 | }); 12 | 13 | require(['react', 'marty'], function (React, Marty) { 14 | var FooStore = Marty.createStore({ 15 | getInitialState: function () { 16 | return { 17 | 1: { displayName: 'Foo' }, 18 | 2: { displayName: 'Bar' } 19 | }; 20 | }, 21 | getFoo: function (id) { 22 | return this.state[id]; 23 | } 24 | }); 25 | 26 | var Application = Marty.createApplication(function () { 27 | this.register('fooStore', FooStore); 28 | }); 29 | 30 | var Foo = React.createClass({ 31 | render: function () { 32 | return React.createElement("div", null, this.props.foo.displayName); 33 | } 34 | }); 35 | 36 | var FooContainer = Marty.createContainer(Foo, { 37 | listenTo: 'fooStore', 38 | fetch: { 39 | foo: function () { 40 | return this.app.fooStore.getFoo(this.props.id); 41 | } 42 | } 43 | }) 44 | 45 | var element = React.createElement(FooContainer, { 46 | id: 1, 47 | app: new Application() 48 | }); 49 | 50 | React.render(element, document.getElementById('app')); 51 | }); -------------------------------------------------------------------------------- /examples/window/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.Marty example 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 51 | 52 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var _ = require('lodash'); 3 | var yaml = require('js-yaml'); 4 | 5 | module.exports = function (config) { 6 | process.env.NODE_ENV = 'test'; 7 | 8 | switch (process.env.ENV) { 9 | case 'CI': 10 | _.extend(process.env, saucelabsVariables()); 11 | config.set(saucelabs()); 12 | break; 13 | case 'IE': 14 | _.extend(process.env, saucelabsVariables()); 15 | 16 | config.set(_.extend(saucelabs(), { 17 | customLaunchers: { 18 | sl_ie_11: { 19 | base: 'SauceLabs', 20 | browserName: 'internet explorer', 21 | platform: 'Windows 8.1', 22 | version: '11' 23 | } 24 | }, 25 | browsers: ['sl_ie_11'] 26 | })); 27 | break; 28 | default: 29 | config.set(local()); 30 | break; 31 | } 32 | 33 | function saucelabs() { 34 | var customLaunchers = { 35 | sl_chrome: { 36 | base: 'SauceLabs', 37 | browserName: 'chrome', 38 | platform: 'Windows 7', 39 | version: '38' 40 | }, 41 | sl_firefox: { 42 | base: 'SauceLabs', 43 | browserName: 'firefox', 44 | version: '33' 45 | }, 46 | sl_safari: { 47 | base: 'SauceLabs', 48 | browserName: 'safari', 49 | version: '5' 50 | }, 51 | sl_ie_9: { 52 | base: 'SauceLabs', 53 | browserName: 'internet explorer', 54 | platform: 'Windows 7', 55 | version: '9' 56 | }, 57 | sl_ie_10: { 58 | base: 'SauceLabs', 59 | browserName: 'internet explorer', 60 | platform: 'Windows 7', 61 | version: '10' 62 | }, 63 | sl_ie_11: { 64 | base: 'SauceLabs', 65 | browserName: 'internet explorer', 66 | platform: 'Windows 8.1', 67 | version: '11' 68 | } 69 | }; 70 | 71 | return _.extend(base(), { 72 | sauceLabs: { 73 | testName: 'Marty Tests' 74 | }, 75 | browserDisconnectTimeout : 10000, 76 | browserDisconnectTolerance : 1, 77 | browserNoActivityTimeout : 4 * 60 * 1000, 78 | captureTimeout : 4 * 60 * 1000, 79 | customLaunchers: customLaunchers, 80 | browsers: Object.keys(customLaunchers), 81 | reporters: ['dots', 'saucelabs'], 82 | singleRun: true 83 | }); 84 | } 85 | 86 | function local() { 87 | return _.extend(base(), { 88 | reporters: ['spec'], 89 | browsers: ['Chrome'], 90 | autoWatch: true, 91 | singleRun: false, 92 | colors: true 93 | }); 94 | } 95 | 96 | function base() { 97 | return { 98 | basePath: '', 99 | frameworks: ['mocha', 'browserify'], 100 | browserify: { 101 | transform: ['babelify'], 102 | debug: true 103 | }, 104 | files: [ 105 | 'marty.js', 106 | 'lib/*.js', 107 | 'test/browser/**/*.js' 108 | ], 109 | preprocessors: { 110 | 'lib/*': ['browserify'], 111 | 'marty.js': ['browserify'], 112 | 'test/browser/**/*.js': ['browserify'] 113 | }, 114 | port: 9876, 115 | logLevel: config.LOG_INFO, 116 | }; 117 | } 118 | 119 | function saucelabsVariables() { 120 | return _.pick(travisGlobalVariables(), 'SAUCE_USERNAME', 'SAUCE_ACCESS_KEY'); 121 | 122 | function travisGlobalVariables() { 123 | var config = {}; 124 | var travis = yaml.safeLoad(fs.readFileSync('./.travis.yml', 'utf-8')); 125 | 126 | travis.env.global.forEach(function (variable) { 127 | var parts = /(.*)="(.*)"/.exec(variable); 128 | 129 | config[parts[1]] = parts[2]; 130 | }); 131 | 132 | return config; 133 | } 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /marty.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'marty' { 2 | import React = require('react'); 3 | 4 | module Marty { 5 | interface Map { 6 | [name: string]: T 7 | } 8 | 9 | interface FetchOptions { 10 | id: string; 11 | locally?: () => T; 12 | remotely?: () => Promise; 13 | dependsOn?: FetchResult | Array>; 14 | cacheError?: boolean; 15 | } 16 | 17 | interface WhenHandlers { 18 | done(result: T): any; 19 | failed(error: any): any; 20 | pending(): any; 21 | } 22 | 23 | interface When { 24 | (handlers: WhenHandlers, context?: any): void; 25 | all(fetchResult: FetchResult[], handlers: WhenHandlers>, context?: any): void; 26 | toPromise(): Promise; 27 | } 28 | 29 | interface FetchResult { 30 | status: string; 31 | failed: boolean; 32 | error?: any; 33 | result?: T; 34 | done: boolean; 35 | when: When; 36 | toPromise: () => Promise; 37 | } 38 | 39 | interface Fetch { 40 | (options: FetchOptions): FetchResult; 41 | (id: string, local: () => T, remote?: () => Promise): FetchResult; 42 | 43 | done(result: T, id: string, store: Store): FetchResult; 44 | pending(id: string, store: Store): FetchResult; 45 | failed(error: any, id: string, store: Store): FetchResult; 46 | notFound(id: string, store: Store): FetchResult; 47 | } 48 | 49 | type MartyInstance = Store | ActionCreators | Queries | StateSource; 50 | 51 | type MartyType = { new (...args: any[]): MartyInstance }; 52 | 53 | interface RegisterMap { 54 | [id: string]: RegisterMap | MartyType; 55 | } 56 | 57 | class Application { 58 | register(map: RegisterMap): void; 59 | register(id: string, instance: MartyType): void; 60 | } 61 | 62 | class ApplicationContainer extends React.Component<{ app: Application }, {}> {} 63 | 64 | class Store { 65 | state: S; 66 | handlers: any; 67 | dispatchToken: string; 68 | 69 | app: any; 70 | 71 | constructor(options: any); 72 | setState(nextState: S): void; 73 | replaceState(nextState: S): void; 74 | addChangeListener(callback: (state: S, store: Store) => any, context: any): void; 75 | hasChanged(): void; 76 | fetch: Fetch; 77 | hasAlreadyFetched(id: string): boolean; 78 | waitFor(stores: Array>): void; 79 | } 80 | 81 | class ActionCreators { 82 | id: string; 83 | app: Marty; 84 | dispatch(type: string, ...data: any[]): void; 85 | } 86 | 87 | class Queries extends DispatchCoordinator { 88 | id: string; 89 | app: Marty; 90 | dispatch(type: string, ...data: any[]): void; 91 | } 92 | 93 | type ContainerConfigFetch = Map | (() => Map); 94 | 95 | interface ContainerConfig { 96 | listenTo: string | Array; 97 | fetch?: ContainerConfigFetch; 98 | done?: (props: any) => React.ReactElement; 99 | pending?: () => React.ReactElement; 100 | failed?: (errors: any[]) => React.ReactElement; 101 | } 102 | 103 | function createContainer(component: React.ComponentClass, config?: ContainerConfig): React.ClassicComponentClass<{}>; 104 | 105 | class StateSource { 106 | id: string; 107 | type: string; 108 | mixins: Array; 109 | 110 | app: any; 111 | } 112 | 113 | class CookieStateSource extends StateSource { 114 | get(key: string): any; 115 | set(key: string, value: any): boolean; 116 | expire(key: string): boolean; 117 | } 118 | 119 | interface RequestOptions { 120 | url: string; 121 | method?: string; 122 | headers?: Map; 123 | body?: string | Object; 124 | contentType?: string; 125 | dataType?: string; 126 | } 127 | 128 | interface HttpFetch { 129 | text(): Promise; 130 | json(): Promise; 131 | headers: { 132 | get(key: string): string; 133 | } 134 | status: number; 135 | statusText: string; 136 | } 137 | 138 | interface HookOptions { 139 | id: string; 140 | priority?: number; 141 | before?: (req: any) => any; 142 | after?: (res: any) => any; 143 | } 144 | 145 | class HttpStateSource extends StateSource { 146 | baseUrl: string; 147 | request(options: RequestOptions): Promise; 148 | get(url: string): Promise; 149 | get(options: RequestOptions): Promise; 150 | post(url: string): Promise; 151 | post(options: RequestOptions): Promise; 152 | put(url: string): Promise; 153 | put(options: RequestOptions): Promise; 154 | delete(url: string): Promise; 155 | delete(options: RequestOptions): Promise; 156 | 157 | static addHook(options: HookOptions): void; 158 | static removeHook(options: HookOptions): void; 159 | } 160 | 161 | class JSONStorageStateSource extends StateSource { 162 | storage: any; 163 | namespace: string; 164 | 165 | get(key: string): any; 166 | set(key: string, value: any): void; 167 | } 168 | 169 | class LocalStorageStateSource extends JSONStorageStateSource {} 170 | 171 | class SessionStorageStateSource extends JSONStorageStateSource {} 172 | 173 | interface LocationInformation { 174 | url: string; 175 | path: string; 176 | hostname: string; 177 | query: Map; 178 | protocol: string; 179 | } 180 | 181 | class LocationStateSource extends StateSource { 182 | getLocation(): LocationInformation 183 | } 184 | 185 | function get(type: string, id: string): any; 186 | 187 | function getAll(type: string): any[]; 188 | 189 | function getDefault(type: string, id: string): any; 190 | 191 | function getAllDefaults(type: string): any[]; 192 | 193 | function resolve(type: string, id: string, options?: Object): any; 194 | 195 | interface Dispatcher { 196 | id: string; 197 | isDefault: boolean; 198 | dispatchAction(options: Object): any; 199 | onActionDispatched(callback: (action: any) => any, context?: any): void; 200 | } 201 | 202 | var dispatcher: Dispatcher; 203 | 204 | interface ConstantsOption { 205 | [key: string]: string[] | ConstantsOption; 206 | } 207 | 208 | function createConstants(constants: string[] | ConstantsOption): any; 209 | 210 | interface Warnings { 211 | without(warningsToDisable: string[], callback: () => any, context?: any): void; 212 | invokeConstant: boolean; 213 | reservedFunction: boolean; 214 | cannotFindContext: boolean; 215 | classDoesNotHaveAnId: boolean; 216 | stateIsNullOrUndefined: boolean; 217 | callingResolverOnServer: boolean; 218 | stateSourceAlreadyExists: boolean; 219 | superNotCalledWithOptions: boolean; 220 | promiseNotReturnedFromRemotely: boolean; 221 | contextNotPassedInToConstructor: boolean; 222 | } 223 | 224 | var warnings: Warnings; 225 | 226 | function createInstance(): typeof Marty; 227 | 228 | function dispose(): void; 229 | 230 | var version: string; 231 | 232 | var isServer: boolean; 233 | 234 | var isBrowser: boolean; 235 | } 236 | 237 | import M = Marty; 238 | 239 | export = M; 240 | } 241 | -------------------------------------------------------------------------------- /marty.js: -------------------------------------------------------------------------------- 1 | var windowDefined = typeof window !== "undefined"; 2 | 3 | if (typeof global === "undefined" && windowDefined) { 4 | window.global = window; 5 | } 6 | 7 | require('es6-promise').polyfill(); 8 | require('isomorphic-fetch'); 9 | 10 | var Marty = require('marty-lib/modules/core/marty'); 11 | var marty = new Marty('0.11.0', react(), reactDomServer()); 12 | 13 | marty.use(require('marty-lib/modules/core')); 14 | marty.use(require('marty-lib/modules/constants')); 15 | marty.use(require('marty-lib/modules/application')); 16 | marty.use(require('marty-lib/modules/store')); 17 | marty.use(require('marty-lib/modules/action-creators')); 18 | marty.use(require('marty-lib/modules/queries')); 19 | marty.use(require('marty-lib/modules/state-mixin')); 20 | marty.use(require('marty-lib/modules/app-mixin')); 21 | marty.use(require('marty-lib/modules/container')); 22 | marty.use(require('marty-lib/modules/http-state-source')); 23 | marty.use(require('marty-lib/modules/cookie-state-source')); 24 | marty.use(require('marty-lib/modules/location-state-source')); 25 | marty.use(require('marty-lib/modules/session-storage-state-source')); 26 | marty.use(require('marty-lib/modules/json-storage-state-source')); 27 | marty.use(require('marty-lib/modules/local-storage-state-source')); 28 | 29 | module.exports = marty; 30 | 31 | function react() { 32 | try { 33 | return module.parent.require("react"); 34 | } catch (e) { 35 | try { 36 | return require("react"); 37 | } catch (e) { 38 | if (windowDefined && window.React) { 39 | return window.React; 40 | } 41 | } 42 | } 43 | 44 | throw new Error('Could not find React'); 45 | } 46 | 47 | function reactDomServer() { 48 | try { 49 | return module.parent.require("react-dom/server"); 50 | } catch (e) { 51 | try { 52 | return require("react-dom/server"); 53 | } catch (e) { 54 | if (windowDefined) { 55 | if (!window.ReactDOMServer) { 56 | // Don't require ReactDOMServer in browser. 57 | return {}; 58 | } 59 | 60 | return window.ReactDOMServer; 61 | } 62 | } 63 | } 64 | 65 | throw new Error('Could not find ReactDOMServer'); 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marty", 3 | "version": "0.11.0", 4 | "description": "A Javascript library for state management in React applications", 5 | "main": "marty.js", 6 | "directories": { 7 | "doc": "./doc", 8 | "lib": "./lib" 9 | }, 10 | "scripts": { 11 | "test": "make test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:martyjs/marty.git" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "flux" 20 | ], 21 | "dependencies": { 22 | "es6-promise": "^2.0.0", 23 | "isomorphic-fetch": "^2.1.0", 24 | "marty-lib": "^0.11.0" 25 | }, 26 | "devDependencies": { 27 | "babel": "^5.0.0", 28 | "babelify": "^6.0.0", 29 | "browserify": "^10.0.0", 30 | "bundle-collapser": "^1.2.0", 31 | "react": "^0.14.0", 32 | "react-dom": "^0.14.0", 33 | "semver": "^4.2.0", 34 | "uglify-js": "^2.4.23" 35 | }, 36 | "peerDependencies": { 37 | "react": "0.14.x", 38 | "react-dom": "0.14.x" 39 | }, 40 | "author": "James Hollingworth", 41 | "license": "MIT", 42 | "licenses": [ 43 | { 44 | "type": "MIT", 45 | "url": "https://raw.github.com/martyjs/marty/master/LICENSE" 46 | } 47 | ], 48 | "bugs": { 49 | "url": "https://github.com/martyjs/marty/issues" 50 | }, 51 | "browserify": { 52 | "transform": [ 53 | "babelify" 54 | ] 55 | }, 56 | "homepage": "https://martyjs.org", 57 | "typescript": { 58 | "definition": "marty.d.ts" 59 | } 60 | } -------------------------------------------------------------------------------- /test-utils.js: -------------------------------------------------------------------------------- 1 | module.exports = require('marty-lib/modules/test-utils'); --------------------------------------------------------------------------------