Comparing version 0.12.0 to 0.13.0
{ | ||
"name": "cerebral", | ||
"version": "0.12.0", | ||
"version": "0.13.0", | ||
"description": "A state controller with its own debugger", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
172
README.md
# Cerebral ![build status](https://travis-ci.org/christianalfoni/cerebral.svg?branch=master) | ||
A state controller with its own debugger | ||
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/christianalfoni/cerebral?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
<img src="images/logo.jpg" width="300" align="center"> | ||
@@ -8,3 +10,3 @@ | ||
- [Grab the Chrome Debugger](#grab-the-chrome-debugger) | ||
- [Cerebral packages](#cerebral-packages) | ||
- [How to get started](#how-to-get-started) | ||
- [How to use Cerebral with an existing package](#how-to-use-cerebral-with-an-existing-package) | ||
@@ -25,3 +27,3 @@ - [Instantiate a controller](#instantiate-a-controller) | ||
If you are familiar with a Backbone View with a template or an Angular Controller/Directive with a template, that is pretty much how a VIEW works. The more recent React js VIEW (component) library is much the same concept in regards of being responsbile for rendering HTML based on STATE inside the VIEW, but it does it in a radically different way that is a lot faster. | ||
If you are familiar with a Backbone View with a template or an Angular Controller/Directive with a template, that is pretty much how a VIEW works. The more recent React js VIEW (component) library is much the same concept in regards of being responsible for rendering HTML based on STATE inside the VIEW, but it does it in a radically different way that is a lot faster. | ||
@@ -37,7 +39,12 @@ The traditional MODEL layer of your app, like Backbone Model or Angular Resource, are wrappers for your database entities. To make it easier to communicate changes back and forth to the server. This has changed radically the last year. Instead of thinking the MODEL layer as wrapped objects that allows for easier communication, it is now just one big plain object containing any data/state your application needs, it being a database entity or just some state indicating that your application is loading, show a modal etc. | ||
## Grab the Chrome debugger | ||
[Cerebral Debugger](https://chrome.google.com/webstore/detail/cerebral-debugger/ddefoknoniaeoikpgneklcbjlipfedbb) | ||
## Cerebral packages | ||
The Cerebral Core API is "low level", but extremely flexible. You can check out a few packages here that will instantly get you started with some of your favorite development tools: | ||
## How to get started | ||
### 1. Install debugger | ||
Install the [Chrome Cerebral Debugger](https://chrome.google.com/webstore/detail/cerebral-debugger/ddefoknoniaeoikpgneklcbjlipfedbb) | ||
### 2. Choose a package | ||
The Cerebral Core API is "low level", but extremely flexible. If you do not have any specific needs in regards of VIEW or MODEL layer, you can choose one of the preset packages that will get you quickly up and running: | ||
- [cerebral-react-immutable-store](https://github.com/christianalfoni/cerebral-react-immutable-store) | ||
@@ -49,155 +56,8 @@ - [cerebral-angular-immutable-store](https://github.com/christianalfoni/cerebral-angular-immutable-store) | ||
## How to use Cerebral with an existing package | ||
### 3. Signals and actions | ||
Depending on the package you choose you instantiate and create signals differently. Please continue with the README of the specific package you chose | ||
### Instantiate a Controller | ||
```js | ||
import Controller from 'cerebral-some-package'; | ||
// Define a single object representing all the base state | ||
// of your application | ||
const state = { | ||
foo: 'bar' | ||
}; | ||
// Define an optional object with utils etc. you want to | ||
// pass into each action. Typically ajax libs etc. | ||
const defaultArgs = { | ||
foo: 'bar' | ||
}; | ||
// Instantiate the controller | ||
const controller = Controller(state, defaultArgs); | ||
``` | ||
### Creating actions and signals | ||
Actions is where it all happens. This is where you define mutations to your application state based on information sent from the VIEW layer. Actions are pure functions that can run synchronously and asynchronously. They are easily reused across signals and can easily be tested. | ||
```js | ||
const controller = Controller(state, defaultArgs); | ||
// Define an action with a function. It receives two arguments when run | ||
// synchronously | ||
const setLoading = function setLoading (args, state) { | ||
state.set('isLoading', true); | ||
}; | ||
// There are many types of mutations you can do, "set" is just one of them | ||
const unsetLoading = function unsetLoading (args, state) { | ||
state.set('isLoading', false); | ||
}; | ||
// When an action is run asynchronously it receives a third argument, | ||
// a promise you can either resolve or reject. In this example we | ||
// are using an ajax util we passed as a default argument and an argument | ||
// we passed when the signal was triggered | ||
const saveForm = function saveForm (args, state, promise) { | ||
args.utils.ajax.post('/form', args.formData, function (err, response) { | ||
promise.resolve(); | ||
}); | ||
}; | ||
// The saveForm action runs async because it is in an array. You can have multiple | ||
// actions in one array that runs async in parallell. | ||
controller.signal('formSubmitted', setLoading, [saveForm], unsetLoading); | ||
``` | ||
### Trigger a signal | ||
Depending on the package being used the controller needs to be exposed to the VIEW layer. This allows you to trigger a signal. | ||
```js | ||
controller.signals.formSubmitted({ | ||
formData: {foo: 'bar'} | ||
}); | ||
``` | ||
Signals are batched up and run with "requestAnimationFrame", but you can force it to run synchronously by passing `true` as the first argument. This ensure that inputs and textareas updates correctly. | ||
```js | ||
controller.signals.inputChanged(true, { | ||
value: event.target.value | ||
}); | ||
``` | ||
### Get initial state | ||
When running the application you need to grab the initial state of the application. You can do this with the exposed "get" method. | ||
```js | ||
const state = controller.get(); // Returns all state | ||
state.isLoading // false | ||
``` | ||
### Get state updates | ||
Depending on the package you are using you will get state updates. This might for example be an event triggered on the controller. | ||
```js | ||
controller.on('update', function (state) { | ||
state.isLoading // false | ||
}); | ||
``` | ||
### Mutations | ||
You can do any traditional mutation to the state, the signature is just a bit different. You call the kind of mutation first, then the path and then an optional value. The path can either be a string or an array for nested paths. | ||
```js | ||
const someAction = function someAction (args, state) { | ||
state.set('isLoading', false); | ||
state.unset('isLoading'); | ||
state.merge('user', {name: 'foo'}); | ||
state.push('list', 'foo'); | ||
state.unshift('list', 'bar'); | ||
state.pop('list'); | ||
state.shift('list'); | ||
state.concat('list', [1, 2, 3]); | ||
state.splice('list', 1, 1, [1]); | ||
state.push(['admin', 'users'], {foo: 'bar'}); | ||
}; | ||
``` | ||
### Get state in actions | ||
```js | ||
const someAction = function someAction (args, state) { | ||
const isLoading = state.get('isLoading'); | ||
}; | ||
``` | ||
### Async actions | ||
```js | ||
const someAction = function someAction (args, state, promise) { | ||
args.utils.ajax('/foo', function (err, result) { | ||
if (err) { | ||
promise.reject({error: err}); | ||
} else { | ||
promise.resolve({result: result}); | ||
} | ||
}) | ||
}; | ||
``` | ||
You can optionally redirect resolved and rejected async actions to different actions by inserting an object as the last entry in the async array definition. | ||
```js | ||
controller.signal('formSubmitted', | ||
setLoading, | ||
[saveForm, { | ||
resolve: [closeModal], | ||
reject: [setFormError] | ||
}], | ||
unsetLoading | ||
); | ||
``` | ||
### Recording | ||
With the Cerebral controller you can record and replay state changes. | ||
```js | ||
// Start recording by passing the initial state of the recording | ||
controller.recorder.record(controller.get()); | ||
// Stop recording | ||
controller.recorder.stop(); | ||
// Seek to specific time and optionally start playback | ||
controller.recorder.seek(0, true); | ||
``` | ||
## How to create a custom Cerebral package | ||
If the current packages does not meet your needs you are free to create your own package with its own VIEW and MODEL layer. To define a Controller you need somewhere to store the state. You can use whatever you want in this regard, but to gain the full power of the developer tools the state store should be immutable. This specifically allows you to move back and forth in time in the debugger and you will gain benefits in rendering optimization. | ||
To define a Controller you need somewhere to store the state. You can use whatever you want in this regard, but to gain the full power of the developer tools the state store should be immutable. This specifically allows you to move back and forth in time in the debugger and you will gain benefits in rendering optimization. | ||
In this example we will use the [immutable-store](https://github.com/christianalfoni/immutable-store) project as a state store, but [freezer](https://github.com/arqex/freezer), [baobab](https://github.com/Yomguithereal/baobab), [immutable-js](https://github.com/facebook/immutable-js) are also good alternatives. | ||
@@ -250,3 +110,3 @@ | ||
onRemember: function () { | ||
events.emit('change', state); | ||
events.emit('change', state); | ||
}, | ||
@@ -253,0 +113,0 @@ |
@@ -88,3 +88,3 @@ var utils = require('./utils.js'); | ||
record: function (initialState, meta) { | ||
record: function () { | ||
@@ -102,6 +102,5 @@ if (signalStore.isRemembering()) { | ||
currentRecording = { | ||
initialState: initialState, | ||
initialState: options.onGetRecordingState && options.onGetRecordingState(), | ||
start: Date.now(), | ||
signals: [], | ||
meta: meta | ||
signals: [] | ||
}; | ||
@@ -108,0 +107,0 @@ |
@@ -40,2 +40,5 @@ var Lib = require('./../src/index.js'); | ||
var ctrl = Lib.Controller({ | ||
onGetRecordingState: function () { | ||
return state; | ||
}, | ||
onSeek: function (seek, startPlaying, currentRecording) { | ||
@@ -81,2 +84,5 @@ state = currentRecording.initialState; | ||
var ctrl = Lib.Controller({ | ||
onGetRecordingState: function () { | ||
return state; | ||
}, | ||
onSeek: function (seek, startPlaying, currentRecording) { | ||
@@ -117,2 +123,5 @@ state = currentRecording.initialState; | ||
var ctrl = Lib.Controller({ | ||
onGetRecordingState: function () { | ||
return state; | ||
}, | ||
onSeek: function (seek, startPlaying, currentRecording) { | ||
@@ -165,2 +174,5 @@ state = currentRecording.initialState; | ||
var ctrl = Lib.Controller({ | ||
onGetRecordingState: function () { | ||
return state; | ||
}, | ||
onSeek: function (seek, startPlaying, currentRecording) { | ||
@@ -167,0 +179,0 @@ state = currentRecording.initialState; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25826
2044974
163