Merge pull request #125 from yoshuawuyts/readme-fixes

Readme fixes
This commit is contained in:
Yoshua Wuyts
2016-07-06 01:28:54 +02:00
committed by GitHub
+59 -33
View File
@@ -121,16 +121,16 @@ const app = choo()
app.model({ app.model({
state: { title: 'Not quite set yet' }, state: { title: 'Not quite set yet' },
reducers: { reducers: {
update: (data, state) => ({ title: data.value }) update: (data, state) => ({ title: data })
} }
}) })
const mainView = (params, state, send) => html` const mainView = (state, prev, send) => html`
<main> <main>
<h1>Title: ${state.title}</h1> <h1>Title: ${state.title}</h1>
<input <input
type="text" type="text"
oninput=${(e) => send('update', { value: e.target.value })}> oninput=${(e) => send('update', e.target.value)}>
</main> </main>
` `
@@ -142,19 +142,21 @@ const tree = app.start()
document.body.appendChild(tree) document.body.appendChild(tree)
``` ```
And to run it, save it as `client.js` and run with [budo][budo] and To run it, save it as `client.js` and run with [budo][budo] and
[es2020][es2020]: [es2020][es2020]. These tools are convenient but any [browserify][browserify]
based tool should do:
```sh ```sh
$ budo 'client.js' -p 8080 --open -- -t es2020 $ budo 'client.js' -p 8080 --open -- -t es2020
``` ```
And to save to static files for deployment, open a new terminal and do: And to save the output to files so it can be deployed, open a new terminal and
do:
```bash ```bash
$ mkdir -p 'dist/' $ mkdir -p 'dist/'
$ curl 'localhost:8080' > 'dist/index.html' $ curl 'localhost:8080' > 'dist/index.html'
$ curl 'localhost:8080/bundle.js' > 'dist/bundle.js' $ curl 'localhost:8080/bundle.js' > 'dist/bundle.js'
``` ```
All just a couple of shell commands and `.js` files, no grandiose boilerplate All using a couple of shell commands and `.js` files, no grandiose boilerplate
needed. needed.
## Philosophy ## Philosophy
@@ -189,7 +191,7 @@ sources of data. `effects` react to changes, perform an `action` and can then
post the results. `reducers` take data, modify it, and update the internal post the results. `reducers` take data, modify it, and update the internal
`state`. `state`.
Communication of data is done using objects called `actions`. Each `action` Communication of data is done using something called `actions`. Each `action`
consists of a unique `actionName` and an optional payload of `data`, which can consists of a unique `actionName` and an optional payload of `data`, which can
be any value. be any value.
@@ -232,14 +234,14 @@ namespaced or not. Namespacing means that only state within the model can be
accessed. Models can still trigger actions on other models, though it's accessed. Models can still trigger actions on other models, though it's
recommended to keep that to a minimum. recommended to keep that to a minimum.
So say we have a `todos` namespace, an `add` reducer and a `todos` model. So say we have a `myTodos` namespace, an `add` reducer and a `todos` model.
Outside the model they're called by `send('todos:add')` and Outside the model they're called by `send('todos:add')` and
`state.todos.todos`. Inside the namespaced model they're called by `state.todos.todos`. Inside the namespaced model they're called by
`send('todos:add')` and `state.todos`. An example namespaced model: `send('todos:add')` and `state.todos`. An example namespaced model:
```js ```js
const app = choo() const app = choo()
app.model({ app.model({
namespace: 'todos', namespace: 'myTodos',
state: { todos: [] }, state: { todos: [] },
reducers: { reducers: {
add: (data, state) => ({ todos: state.todos.concat(data.payload) }) add: (data, state) => ({ todos: state.todos.concat(data.payload) })
@@ -274,6 +276,18 @@ A typical `effect` flow looks like:
4. When the async call is done, either a success or error action is emitted 4. When the async call is done, either a success or error action is emitted
5. A reducer catches the action and updates the state 5. A reducer catches the action and updates the state
Examples of effects include: performing
[xhr](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) requests
(server requests), calling multiple `reducers`, persisting state to
[localstorage][localstorage].
When an `effect` is done executing, it should call the `done(err, res)`
callback. This callback used to communicate when an `effect` is done, handle
possible errors and send values back to the caller. You'll probably notice when
applications become more complex, that composing multiple namespaced models
using higher level effects becomes real powerful - without becoming
complicated.
### Subscriptions ### Subscriptions
Subscriptions are a way of receiving data from a source. For example when Subscriptions are a way of receiving data from a source. For example when
listening for events from a server using `SSE` or `Websockets` for a listening for events from a server using `SSE` or `Websockets` for a
@@ -285,13 +299,17 @@ const app = choo()
app.model({ app.model({
namespace: 'app', namespace: 'app',
subscriptions: [ subscriptions: [
(send) => setInterval(() => send('app:print', { payload: 'dog?' }), 1000) (send, done) => {
setInterval(() => send('app:print', { payload: 'dog?' }), 1000)
}
], ],
effects: { effects: {
print: (data, state) => console.log(data.payload) print: (data, state) => console.log(data.payload)
} }
}) })
``` ```
If a `subscription` runs into an error, it can call `done(err)` to signal the
error to the error hook.
### Router ### Router
The `router` manages which `views` are rendered at any given time. It also The `router` manages which `views` are rendered at any given time. It also
@@ -315,9 +333,9 @@ figure out how views relate to each other.
Under the hood `choo` uses [sheet-router][sheet-router]. Internally the Under the hood `choo` uses [sheet-router][sheet-router]. Internally the
currently rendered route is kept in `state.app.location`. If you want to modify currently rendered route is kept in `state.app.location`. If you want to modify
the location programmatically the `reducer` for the location can be called the location programmatically the `reducer` for the location can be called
using `send('app:location', { location: href })`. This will not work from using `send('location:setLocation', { location: href })`. This will not work
within namespaced `models`, and usage should preferably be kept to a minimum. from within namespaced `models`, and usage should preferably be kept to a
Changing views all over the place tends to lead to messiness. minimum. Changing views all over the place tends to lead to messiness.
### Views ### Views
Views are pure functions that return a DOM tree for the router to render. Theyre passed the current state, and any time the state changes theyre run again with the new state. Views are pure functions that return a DOM tree for the router to render. Theyre passed the current state, and any time the state changes theyre run again with the new state.
@@ -325,11 +343,13 @@ Views are pure functions that return a DOM tree for the router to render. They
Views are also passed the `send` function, which they can use to dispatch actions that can update the state. For example, the DOM tree can have an `onclick` handler that dispatches an `add` action. Views are also passed the `send` function, which they can use to dispatch actions that can update the state. For example, the DOM tree can have an `onclick` handler that dispatches an `add` action.
```javascript ```javascript
const view = (params, state, send) => { const view = (state, prev, send) => {
return html` return html`
<div> <div>
<h1>Total todos: ${state.todos.length}</h1> <h1>Total todos: ${state.todos.length}</h1>
<button onclick=${(e) => send('add', { payload: {title: 'demo'})}>Add</button> <button onclick=${(e) => send('add', {title: 'demo'})}>
Add
</button>
</div>` </div>`
} }
``` ```
@@ -370,33 +390,35 @@ arguments:
and handlers in other models and handlers in other models
- __state:__ initial values of `state` inside the model - __state:__ initial values of `state` inside the model
- __reducers:__ synchronous operations that modify state. Triggered by - __reducers:__ synchronous operations that modify state. Triggered by
`actions`. Signature of `(actionData, state)`. `actions`. Signature of `(data, state)`.
- __effects:__ asynchronous operations that don't modify state directly. - __effects:__ asynchronous operations that don't modify state directly.
Triggered by `actions`, can call `actions`. Signature of `(actionData, state, Triggered by `actions`, can call `actions`. Signature of `(data, state,
send, done)` send, done)`
- __subscriptions:__ asynchronous read-only operations that don't modify state - __subscriptions:__ asynchronous read-only operations that don't modify state
directly. Can call `actions`. Signature of `(state, send, done)`. directly. Can call `actions`. Signature of `(state, send, done)`.
#### send() #### send(actionName, data?)
Send a new action to the models with optional data attached. Namespaced models Send a new action to the models with optional data attached. Namespaced models
can be accessed by prefixing the name with the namespace separated with a `:`, can be accessed by prefixing the name with the namespace separated with a `:`,
e.g. `namespace:name`. e.g. `namespace:name`.
#### done() #### done(err?, res?)
When an `effect` or `subscription` is done executing, or encounters an error, When an `effect` or `subscription` is done executing, or encounters an error,
it should call the final `done(err)` callback. If an `effect` was called by it should call the final `done(err, res)` callback. If an `effect` was called
another `effect` it will call the callback of the caller. When an error by another `effect` it will call the callback of the caller. When an error
propegates all the way to the top, the `onError` handler will be called, propegates all the way to the top, the `onError` handler will be called,
registered in `barracks(handlers)`. If no callback is registered, errors will registered in `choo(handlers)`. If no callback is registered, errors will
`throw`. `throw`.
### app.router((route) => [routes]) ### app.router(defaultRoute?, (route) => [routes])
Creates a new router. Takes a function that exposes a single `route` function, Creates a new router. Takes a function that exposes a single `route` function,
and that expects a tree of `routes` to be returned. See and that expects a tree of `routes` to be returned. See
[`sheet-router`](https://github.com/yoshuawuyts/sheet-router) for full [`sheet-router`](https://github.com/yoshuawuyts/sheet-router) for full
documentation. Registered views have a signature of `(state, prev, send)`, documentation. Registered views have a signature of `(state, prev, send)`,
where `state` is the current `state`, `prev` is the last state, `state.params` where `state` is the current `state`, `prev` is the last state, `state.params`
is URI partials and `send()` can be called to trigger actions. 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 specified it will throw instead.
### 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.
@@ -412,8 +434,8 @@ first argument, the tree will diff against the selected node rather than be
returned. This is useful for [rehydration](#rehydration). Opts can contain the returned. This is useful for [rehydration](#rehydration). Opts can contain the
following values: following values:
- __opts.history:__ default: `true`. Enable a `subscription` to the browser - __opts.history:__ default: `true`. Enable a `subscription` to the browser
history API. e.g. updates the internal `state.location` state whenever the history API. e.g. updates the internal `location.href` state whenever the
browser "forward" and "backward" buttons are pressed. browsers "forward" and "backward" buttons are pressed.
- __opts.href:__ default: `true`. Handle all relative `<a - __opts.href:__ default: `true`. Handle all relative `<a
href="<location>"></a>` clicks and update internal `state.location` href="<location>"></a>` clicks and update internal `state.location`
accordingly. accordingly.
@@ -422,8 +444,8 @@ following values:
changes (eg `localhost/#posts/123`). Enabling this option automatically changes (eg `localhost/#posts/123`). Enabling this option automatically
disables `opts.history` and `opts.href`. disables `opts.history` and `opts.href`.
### view = choo/html\`html\` ### view = require('choo/html')\`html\`
Tagged template string HTML builder. Built on top of [yo-yo][bel], [bel][bel] Tagged template string HTML builder. Built on top of [yo-yo][yo-yo], [bel][bel]
and [hyperx][hyperx]. To register a view on the `router` it should be wrapped and [hyperx][hyperx]. To register a view on the `router` it should be wrapped
in a function with the signature of `(state, prev, send)` where `state` is the in a function with the signature of `(state, prev, send)` where `state` is the
current `state`, `prev` is the last state, `state.params` is URI partials and current `state`, `prev` is the last state, `state.params` is URI partials and
@@ -437,12 +459,13 @@ html`
` `
``` ```
Example listeners include: `onclick`, `onsubmit`, `oninput`, `onkeydown`, Example listeners include: `onclick`, `onsubmit`, `oninput`, `onkeydown`,
`onkeyup`. When creating listeners always remember to call `e.preventDefault()` `onkeyup`. A full list can be found [at the yo-yo
on the event so it doesn't bubble up and do stuff like refreshing the full page repo](https://github.com/maxogden/yo-yo/blob/master/update-events.js). When
or the like. creating listeners always remember to call `e.preventDefault()` on the event so
it doesn't bubble up and do stuff like refreshing the full page or the like.
To trigger lifecycle events on any part of a view, set the `onload=${(el) => To trigger lifecycle events on any part of a view, set the `onload=${(el) =>
{}}` and `onunload=${() => {}}` attributes. These parameters are useful when {}}` and `onunload=${() => {el}}` attributes. These parameters are useful when
creating self-contained widgets that take care of their own state and lifecycle creating self-contained widgets that take care of their own state and lifecycle
(e.g. a maps widget) or to trigger animations. Most elements shouldn't have a (e.g. a maps widget) or to trigger animations. Most elements shouldn't have a
need for these hooks though. need for these hooks though.
@@ -640,3 +663,6 @@ $ npm install choo
[hyperx]: https://github.com/substack/hyperx [hyperx]: https://github.com/substack/hyperx
[budo]: https://github.com/mattdesl/budo [budo]: https://github.com/mattdesl/budo
[es2020]: https://github.com/yoshuawuyts/es2020 [es2020]: https://github.com/yoshuawuyts/es2020
[browserify]: https://github.com/substack/browserify
[localstorage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
[handbook]: https://github.com/yoshuawuyts/choo-handbook