.use: add new api method

This commit is contained in:
Yoshua Wuyts
2016-07-24 22:01:40 +01:00
parent eaad27c859
commit 2b7dc1901c
2 changed files with 69 additions and 26 deletions
+57 -21
View File
@@ -382,7 +382,35 @@ const view = (state, prev, send) => {
</div>` </div>`
} }
``` ```
In this example, when the `Add` button is clicked, the view will dispatch an `add` action that the models `add` reducer will receive. [As seen above](#models), the reducer will add an item to the states `todos` array. The state change will cause this view to be run again with the new state, and the resulting DOM tree will be used to [efficiently patch the DOM](#does-choo-use-a-virtual-dom). In this example, when the `Add` button is clicked, the view will dispatch an
`add` action that the models `add` reducer will receive. [As seen
above](#models), the reducer will add an item to the states `todos` array. The
state change will cause this view to be run again with the new state, and the
resulting DOM tree will be used to [efficiently patch the
DOM](#does-choo-use-a-virtual-dom).
### Plugins
Sometimes it's necessary to change the way `choo` itself works. For example to
report whenever an action is triggered, handle errors globally or perist state
somewhere. This is done through something called `plugins`. Plugins are objects
that contain `hook` functions and are passed to `app.use()`:
```js
const log = require('choo-log')
const choo = require('choo')
const app = choo()
app.use(log())
const tree = app.start()
document.body.appendChild(tree)
```
Generally people using `choo` shouldn't be too worried about the specifics of
`plugins`, as the internal API is (unfortunatly by necessecity) quite complex.
After all they're the most powerful way to modify a `choo` appliction. If you
want to learn more about creating your own `plugins`, and which `hooks` are
available, head on over to [app.use()](#appusehooks).
## Badges ## Badges
Using `choo` in a project? Show off which version you've used using a badge: Using `choo` in a project? Show off which version you've used using a badge:
@@ -400,26 +428,8 @@ the first time, consider reading through the [handbook][handbook] or
[concepts](#concepts) first :sparkles: [concepts](#concepts) first :sparkles:
### app = choo(opts) ### app = choo(opts)
Initialize a new `choo` app. Takes an optional object of handlers. Handlers can Initialize a new `choo` app. Takes an optional object of handlers that is
be: passed to [app.use()](#appusehooks).
- __onError(err, state, createSend):__ called when an `effect` or
`subscription` emit an error. If no handler is passed, the default handler
will `throw` on each error.
- __onAction(action, state, name, caller, createSend):__ called when an
`action` is fired.
- __onStateChange(action, state, prev, caller, createSend):__ called after a
reducer changes the `state`.
`createSend()` is a special function that allows the creation of a new named
`send()` function. The first argument should be a string which is the name, the
second argument is a boolean `callOnError` which can be set to `true` to call
the `onError` hook istead of a provided callback. It then returns a
`send(actionName, data?)` function.
Handlers should be used with care, as they're the most powerful interface into
the state. For application level code it's generally recommended to delegate to
actions inside models using the `send()` call, and only shape the actions
inside the handlers.
### app.model(obj) ### app.model(obj)
Create a new model. Models modify data and perform IO. Takes the following Create a new model. Models modify data and perform IO. Takes the following
@@ -460,6 +470,32 @@ is URI partials and `send()` can be called to trigger actions. If
`defaultRoute` is passed in, that will be called if no paths match. If no `defaultRoute` is passed in, that will be called if no paths match. If no
`defaultRoute` is specified it will throw instead. `defaultRoute` is specified it will throw instead.
### app.use(hooks)
Register an object of hooks on the application. This is useful to extend the
way `choo` works, adding custom behavior and listeners. Generally returning
objects of `hooks` is done by returning them from functions (which we call
`plugins` throughout the documentation).
There are several `hooks` that are picked up by `choo`:
- __onError(err, state, createSend):__ called when an `effect` or
`subscription` emit an error. If no handler is passed, the default handler
will `throw` on each error.
- __onAction(action, state, name, caller, createSend):__ called when an
`action` is fired.
- __onStateChange(action, state, prev, caller, createSend):__ called after a
reducer changes the `state`.
`createSend()` is a special function that allows the creation of a new named
`send()` function. The first argument should be a string which is the name, the
second argument is a boolean `callOnError` which can be set to `true` to call
the `onError` hook istead of a provided callback. It then returns a
`send(actionName, data?)` function.
Hooks should be used with care, as they're the most powerful interface into
the state. For application level code it's generally recommended to delegate to
actions inside models using the `send()` call, and only shape the actions
inside the hooks.
### html = app.toString(route, state?) ### html = app.toString(route, state?)
Render the application to a string of HTML. Useful for rendering on the server. Render the application to a string of HTML. Useful for rendering on the server.
First argument is a path that's passed to the router. Second argument is an First argument is a path that's passed to the router. Second argument is an
+12 -5
View File
@@ -18,17 +18,21 @@ module.exports = choo
function choo (opts) { function choo (opts) {
opts = opts || {} opts = opts || {}
const _store = start._store = barracks(xtend(opts, { onStateChange: render })) const _store = start._store = barracks()
var _router = start._router = null var _router = start._router = null
var _defaultRoute = null var _defaultRoute = null
var _rootNode = null var _rootNode = null
var _routes = null var _routes = null
var _frame = null var _frame = null
_store.use({ onStateChange: render })
_store.use(opts)
start.toString = toString start.toString = toString
start.router = router start.router = router
start.model = model start.model = model
start.start = start start.start = start
start.use = use
return start return start
@@ -83,10 +87,6 @@ function choo (opts) {
// update the DOM after every state mutation // update the DOM after every state mutation
// (obj, obj, obj, str, fn) -> null // (obj, obj, obj, str, fn) -> null
function render (data, state, prev, name, createSend) { function render (data, state, prev, name, createSend) {
if (opts.onStateChange) {
opts.onStateChange(data, state, prev, name, createSend)
}
if (!_frame) { if (!_frame) {
_frame = nanoraf(function (state, prev) { _frame = nanoraf(function (state, prev) {
const newTree = _router(state.location.pathname, state, prev) const newTree = _router(state.location.pathname, state, prev)
@@ -109,6 +109,13 @@ function choo (opts) {
_store.model(model) _store.model(model)
} }
// register a plugin
// (obj) -> null
function use (hooks) {
assert.equal(typeof hooks, 'object', 'choo.use: hooks should be an object')
_store.use(hooks)
}
// create a new router with a custom `createRoute()` function // create a new router with a custom `createRoute()` function
// (str?, obj, fn?) -> null // (str?, obj, fn?) -> null
function createRouter (defaultRoute, routes, createSend) { function createRouter (defaultRoute, routes, createSend) {