├── .gitignore ├── LICENSE ├── README.md ├── app └── main.js ├── build └── index.html ├── index.js ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christian Alfoni 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cerebral-react-baobab 2 | A Cerebral package with React and Baobab 3 | 4 | ## DEPRECATED 5 | 6 | Please head over to [Cerebral website](http://www.cerebraljs.com) 7 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Controller from 'controller'; 3 | 4 | const Decorator = Controller.Decorator; 5 | 6 | const controller = Controller({ 7 | list: ['foo'], 8 | $list2: { 9 | cursors: { 10 | list: ['list'] 11 | }, 12 | get({list}) { 13 | return list.map((item) => item + ' hihihi'); 14 | } 15 | }, 16 | foo: 'bar' 17 | }); 18 | 19 | controller.signal('test', function AddBar (args, state) { 20 | state.push('list', 'bar'); 21 | }); 22 | 23 | @Decorator({ 24 | foo: ['foo'] 25 | }) 26 | class App extends React.Component { 27 | render() { 28 | console.log('Rendering app'); 29 | return ( 30 |
31 |

Hello world!

32 | 33 | 34 |
35 | ); 36 | } 37 | } 38 | 39 | @Decorator({ 40 | list: ['list'], 41 | list2: ['$list2'] 42 | }) 43 | class List extends React.Component { 44 | render() { 45 | return ; 46 | } 47 | } 48 | 49 | React.render(controller.injectInto(App), document.body); 50 | -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var cerebral = require('cerebral'); 2 | var React = require('react'); 3 | var Baobab = require('baobab'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var Value = cerebral.Value; 7 | var updateTree = function (tree, state, path) { 8 | Object.keys(state).forEach(function (key) { 9 | if (key[0] === '$') { 10 | return; 11 | } 12 | path.push(key); 13 | if (!Array.isArray(state[key]) && typeof state[key] === 'object' && state[key] !== null) { 14 | updateTree(tree, state[key], path) 15 | } else { 16 | tree.set(path, state[key]) 17 | } 18 | path.pop(); 19 | }); 20 | }; 21 | 22 | var Factory = function (initialState, defaultArgs, options) { 23 | 24 | options = options || {}; 25 | options.syncwrite = true; 26 | 27 | var eventEmitter = new EventEmitter(); 28 | var tree = new Baobab(initialState, options); 29 | initialState = tree.get(); 30 | 31 | var controller = cerebral.Controller({ 32 | defaultArgs: defaultArgs, 33 | onReset: function () { 34 | updateTree(tree, initialState, []) 35 | }, 36 | onGetRecordingState: function () { 37 | 38 | }, 39 | onSeek: function (seek, isPlaying, currentRecording) { 40 | 41 | }, 42 | onUpdate: function () { 43 | eventEmitter.emit('change', tree.get()); 44 | }, 45 | onRemember: function () { 46 | eventEmitter.emit('remember', tree.get()); 47 | }, 48 | onGet: function (path) { 49 | return tree.get(path); 50 | }, 51 | onSet: function (path, value) { 52 | tree.set(path, value); 53 | }, 54 | onUnset: function (path, key) { 55 | tree.unset(path.concat(key)); 56 | }, 57 | onPush: function (path, value) { 58 | tree.push(path, value); 59 | }, 60 | onSplice: function () { 61 | tree.splice.apply(tree, arguments); 62 | }, 63 | onMerge: function (path, value) { 64 | tree.merge(path, value); 65 | }, 66 | onConcat: function (path, value) { 67 | tree.apply(path, function (existingValue) { 68 | return existingValue.concat(value); 69 | }); 70 | }, 71 | onPop: function (path) { 72 | tree.apply(path, function (existingValue) { 73 | existingValue.pop(); 74 | return existingValue; 75 | }); 76 | }, 77 | onShift: function (path) { 78 | tree.apply(path, function (existingValue) { 79 | existingValue.shift(); 80 | return existingValue; 81 | }); 82 | }, 83 | onUnshift: function (path, value) { 84 | tree.unshift(path, value); 85 | } 86 | }); 87 | 88 | controller.injectInto = function (AppComponent) { 89 | return React.createElement(React.createClass({ 90 | displayName: 'CerebralContainer', 91 | childContextTypes: { 92 | controller: React.PropTypes.object.isRequired 93 | }, 94 | getChildContext: function () { 95 | return { 96 | controller: controller 97 | } 98 | }, 99 | render: function () { 100 | return React.createElement(AppComponent); 101 | } 102 | })); 103 | }; 104 | 105 | controller.eventEmitter = eventEmitter; 106 | 107 | return controller; 108 | 109 | }; 110 | 111 | Factory.Mixin = { 112 | contextTypes: { 113 | controller: React.PropTypes.object 114 | }, 115 | componentWillMount: function () { 116 | this.signals = this.context.controller.signals; 117 | this.recorder = this.context.controller.recorder; 118 | this.get = this.context.controller.get; 119 | this.context.controller.eventEmitter.on('change', this._update); 120 | this.context.controller.eventEmitter.on('remember', this._update); 121 | this._update(this.context.controller.get([])); 122 | }, 123 | componentWillUnmount: function () { 124 | this._isUmounting = true; 125 | this.context.controller.eventEmitter.removeListener('change', this._update); 126 | this.context.controller.eventEmitter.removeListener('remember', this._update); 127 | }, 128 | shouldComponentUpdate: function (nextProps, nextState) { 129 | var propKeys = Object.keys(nextProps); 130 | var stateKeys = Object.keys(nextState); 131 | 132 | // props 133 | for (var x = 0; x < propKeys.length; x++) { 134 | var key = propKeys[x]; 135 | if (this.props[key] !== nextProps[key]) { 136 | return true; 137 | } 138 | } 139 | 140 | // State 141 | for (var x = 0; x < stateKeys.length; x++) { 142 | var key = stateKeys[x]; 143 | if (this.state[key] !== nextState[key]) { 144 | return true; 145 | } 146 | } 147 | 148 | return false; 149 | }, 150 | _update: function (state) { 151 | if (this._isUmounting || !this.getStatePaths) { 152 | return; 153 | } 154 | var statePaths = this.getStatePaths(); 155 | var newState = Object.keys(statePaths).reduce(function (newState, key) { 156 | newState[key] = Value(statePaths[key], state); 157 | return newState; 158 | }, {}); 159 | this.setState(newState); 160 | } 161 | }; 162 | 163 | var Render = function (Component) { 164 | return function () { 165 | var state = this.state || {}; 166 | var props = this.props || {}; 167 | 168 | var propsToPass = Object.keys(state).reduce(function (props, key) { 169 | props[key] = state[key]; 170 | return props; 171 | }, {}); 172 | 173 | propsToPass = Object.keys(props).reduce(function (propsToPass, key) { 174 | propsToPass[key] = props[key]; 175 | return propsToPass; 176 | }, propsToPass); 177 | 178 | propsToPass.signals = this.signals; 179 | propsToPass.recorder = this.recorder; 180 | propsToPass.get = this.get; 181 | 182 | return React.createElement(Component, propsToPass); 183 | }; 184 | }; 185 | 186 | Factory.Decorator = function (paths) { 187 | return function (Component) { 188 | return React.createClass({ 189 | displayName: Component.name + 'Container', 190 | mixins: [Factory.Mixin], 191 | getStatePaths: function () { 192 | return paths || {}; 193 | }, 194 | render: Render(Component) 195 | }); 196 | }; 197 | }; 198 | 199 | Factory.HOC = function (Component, paths) { 200 | return React.createClass({ 201 | displayName: Component.name + 'Container', 202 | mixins: [Factory.Mixin], 203 | getStatePaths: function () { 204 | return paths || {}; 205 | }, 206 | render: Render(Component) 207 | }); 208 | }; 209 | 210 | 211 | module.exports = Factory; 212 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cerebral-react-baobab", 3 | "version": "0.4.0", 4 | "description": "A Cerebral package for React and Baobab", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --devtool eval --progress --colors --content-base build", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/christianalfoni/cerebral-react-baobab.git" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "immutable", 17 | "cerebral" 18 | ], 19 | "author": "Christian Alfoni", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/christianalfoni/cerebral-react-baobab/issues" 23 | }, 24 | "homepage": "https://github.com/christianalfoni/cerebral-react-baobab", 25 | "peerDependencies": { 26 | "react": "^0.13.3", 27 | "baobab": "^2.0.0-dev13" 28 | }, 29 | "dependencies": { 30 | "cerebral": "^0.15.0" 31 | }, 32 | "devDependencies": { 33 | "babel-core": "^5.8.20", 34 | "babel-loader": "^5.3.2", 35 | "node-libs-browser": "^0.5.2", 36 | "webpack": "^1.10.1", 37 | "webpack-dev-server": "^1.10.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var node_modules = path.resolve(__dirname, 'node_modules'); 3 | 4 | var config = { 5 | entry: path.resolve(__dirname, 'app/main.js'), 6 | devtool: 'eval-source-map', 7 | output: { 8 | filename: 'bundle.js' 9 | }, 10 | resolve: { 11 | alias: { 12 | 'controller': path.resolve(__dirname, 'index.js') 13 | } 14 | }, 15 | module: { 16 | loaders: [{ 17 | test: /\.css$/, 18 | loader: 'style!css' 19 | }, { 20 | test: /\.js$/, 21 | loader: 'babel?optional=es7.decorators', 22 | exclude: node_modules 23 | }] 24 | } 25 | }; 26 | 27 | module.exports = config; 28 | --------------------------------------------------------------------------------