examples/http: update for ns

This commit is contained in:
Yoshua Wuyts
2016-05-23 13:44:32 +09:00
parent 300b6325a9
commit 0cc0caa8e1
5 changed files with 89 additions and 76 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ const mainView = require('./views/main')
const app = choo() const app = choo()
app.model(require('./models/error')) app.model(require('./models/error'))
app.model('api', require('./models/api')) app.model(require('./models/api'))
app.router((route) => [ app.router((route) => [
route('/', mainView) route('/', mainView)
+11 -24
View File
@@ -1,45 +1,32 @@
const http = require('../../../http') const http = require('../../../http')
module.exports = { module.exports = {
namespace: 'api',
state: { state: {
title: 'Button pushing machine 3000' title: 'Button pushing machine 3000'
}, },
reducers: { reducers: {
set: function (action, state) { set: (action, state) => ({ 'title': action.payload })
return { 'api:title': action.payload }
}
}, },
effects: { effects: {
good: performGoodRequest, good: (action, state, send) => request('/good', send),
bad: performBadRequest bad: (action, state, send) => request('/bad', send)
} }
} }
function performGoodRequest (action, state, send) { function request (uri, send) {
http('/good', { json: true }, function (err, res, body) { http(uri, { json: true }, function (err, res, body) {
if (err) return send('error', { payload: 'HTTP error' }) if (err) return send('app:error', { payload: 'HTTP error' })
if (res.statusCode !== 200) {
return send('error', { payload: body.payload })
}
if (!body) {
return send('error', { payload: 'fatal: no body received' })
}
send('api:set', { payload: body.message })
})
}
function performBadRequest (action, state, send) {
http('/bad', { json: true }, function (err, res, body) {
if (err) return send('error', { payload: 'HTTP error' })
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
const message = (body && body.message) const message = (body && body.message)
? body.message ? body.message
: 'unknown server error' : 'unknown server error'
return send('error', { payload: message }) return send('app:error', { payload: message })
} }
if (!body) { if (!body) {
return send('error', { payload: 'fatal: no body received' }) console.log('req made!')
return send('app:error', { payload: 'fatal: no body received' })
} }
send('api:set', { payload: body.title }) send('api:set', { payload: body.message || body.title })
}) })
} }
+3 -2
View File
@@ -8,6 +8,7 @@
const ERROR_TIMEOUT = 1000 const ERROR_TIMEOUT = 1000
module.exports = { module.exports = {
namespace: 'app',
state: { state: {
error: [], error: [],
errorTimeDone: null, errorTimeDone: null,
@@ -33,9 +34,9 @@ module.exports = {
}, },
effects: { effects: {
error: function (action, state, send) { error: function (action, state, send) {
const timeout = state.errorTimeDone - Date.now() const timeout = state.app.errorTimeDone - Date.now()
setTimeout(function () { setTimeout(function () {
send('error:delete') send('app:error:delete')
}, timeout) }, timeout)
} }
} }
+4 -2
View File
@@ -1,10 +1,12 @@
const choo = require('../../../') const choo = require('../../../')
module.exports = function (params, state, send) { module.exports = function (params, state, send) {
const error = state.app.error[0]
const title = state.api.title
return choo.view` return choo.view`
<section> <section>
<h1>${state['api:title']}</h1> <h1>${title}</h1>
<h2>Latest error: ${state.error[0]}</h2> <h2>Latest error: ${error}</h2>
<button onclick=${(e) => send('api:good')}>OK!</button> <button onclick=${(e) => send('api:good')}>OK!</button>
<button onclick=${(e) => send('api:bad')}>Naughty</button> <button onclick=${(e) => send('api:bad')}>Naughty</button>
</section> </section>
+68 -45
View File
@@ -3,13 +3,14 @@ const sheetRouter = require('sheet-router')
const document = require('global/document') const document = require('global/document')
const href = require('sheet-router/href') const href = require('sheet-router/href')
const sendAction = require('send-action') const sendAction = require('send-action')
const assert = require('assert')
const xtend = require('xtend') const xtend = require('xtend')
const yo = require('yo-yo') const yo = require('yo-yo')
choo.view = yo choo.view = yo
module.exports = choo module.exports = choo
// A framework for creating sturdy web applications // framework for creating sturdy web applications
// null -> fn // null -> fn
function choo () { function choo () {
const _models = [] const _models = []
@@ -23,6 +24,7 @@ function choo () {
return start return start
// render the application to a string // render the application to a string
// (str, obj) -> str
function toString (route, state) { function toString (route, state) {
const initialState = {} const initialState = {}
@@ -53,61 +55,79 @@ function choo () {
if (model.effects) apply(model.namespace, model.effects, effects) if (model.effects) apply(model.namespace, model.effects, effects)
}) })
// send() is used to trigger actions inside
// views, effects and subscriptions
const send = sendAction({ const send = sendAction({
onaction: handleAction, onaction: handleAction,
onchange: onchange, onchange: onchange,
state: initialState state: initialState
}) })
// subscriptions are loaded after sendAction() is called
// because they both need access to send() and can't
// react to actions (read-only)
_models.forEach(function (model) { _models.forEach(function (model) {
if (model.subscriptions) { if (model.subscriptions) {
assert.ok(Array.isArray(model.subscriptions, 'subs must be an array'))
model.subscriptions.forEach(function (sub) { model.subscriptions.forEach(function (sub) {
sub(send) sub(send)
}) })
} }
}) })
// the rootId is determined to find the application root
// on update. Since the DOM nodes change between updates,
// we must call document.querySelector() to find the root.
// Use different names when loading multiple choo applications
// on the same page
const rootId = name + '-root' const rootId = name + '-root'
const tree = _router(send.state().app.location, send.state(), send) const tree = _router(send.state().app.location, send.state(), send)
tree.setAttribute('id', rootId) tree.setAttribute('id', rootId)
return tree return tree
// handle an action by either reducers, effects
// or both - return the new state when done
// (obj, obj, fn) -> obj
function handleAction (action, state, send) { function handleAction (action, state, send) {
var _reducers = false var reducersCalled = false
var _effects = false var effectsCalled = false
var newState = null
// validate if a namespace exists. Namespaces
// are delimited by the first ':'. Perhaps
// we'll allow recursive namespaces in the
// future - who knows
if (/:/.test(action.type)) { if (/:/.test(action.type)) {
const arr = action.type.split(':') const arr = action.type.split(':')
var ns = arr[0] var ns = arr.shift()
action.type = arr[1] action.type = arr.join(':')
} }
const nsReducers = ns ? reducers[ns] : reducers const _reducers = ns ? reducers[ns] : reducers
if (nsReducers && nsReducers[action.type]) { if (_reducers && _reducers[action.type]) {
if (ns) { if (ns) {
state[ns] = reducers[ns][action.type](action, state[ns]) const newState = reducers[ns][action.type](action, state[ns])
newState = state state[ns] = xtend(state[ns], newState)
} else { } else {
newState = xtend(state, reducers[action.type](action, state)) state = xtend(state, reducers[action.type](action, state))
} }
_reducers = true reducersCalled = true
} }
const nsEffects = ns ? effects[ns] : effects const _effects = ns ? effects[ns] : effects
if (nsEffects && nsEffects[action.type]) { if (_effects && _effects[action.type]) {
nsEffects[action.type](action, newState || state) _effects[action.type](action, state, send)
_effects = true effectsCalled = true
} }
if (!_reducers && !_effects) { if (!reducersCalled && !effectsCalled) {
throw new Error('Could not find action ' + action.type) throw new Error('Could not find action ' + action.type)
} }
return newState || state return state
} }
// update on every change // update the DOM after every state mutation
// (obj, obj) -> null
function onchange (action, state) { function onchange (action, state) {
const oldTree = document.querySelector('#' + rootId) const oldTree = document.querySelector('#' + rootId)
const newTree = _router(state.app.location, state, send) const newTree = _router(state.app.location, state, send)
@@ -116,7 +136,7 @@ function choo () {
} }
} }
// register all routes // register all routes on the router
// [obj|fn] -> null // [obj|fn] -> null
function router (cb) { function router (cb) {
_router = sheetRouter(cb) _router = sheetRouter(cb)
@@ -136,42 +156,45 @@ function appInit (opts) {
const model = { const model = {
namespace: 'app', namespace: 'app',
state: { location: document.location.href }, state: { location: document.location.href },
reducers: { location: setLocation }, subscriptions: [],
subscriptions: [] reducers: {
// handle href links
location: function setLocation (action, state) {
return {
location: action.location.replace(/#.*/, '')
}
}
}
} }
if (opts.href !== false) { // enable catching <href a=""></href> links
model.subscriptions.push(function (send) { // enable HTML5 history API
href(function (href) { if (opts.history !== false) pushLocationSub(href)
send('app:location', { location: href }) if (opts.history !== false) pushLocationSub(history)
})
})
}
if (opts.history !== false) {
model.subscriptions.push(function (send) {
history(function (href) {
send('app:location', { location: href })
})
})
}
return model return model
// handle href links // create a new subscription that modifies
function setLocation (action, state) { // 'app:location' and push it to the subs
return { location: action.location.replace(/#.*/, '') } // fn -> null
function pushLocationSub (cb) {
model.subscriptions.push(function (send) {
cb(function (href) {
send('app:location', { location: href })
})
})
} }
} }
// compose an object conditionally // compose an object conditionally
// optionally contains a namespace
// which is used to nest properties.
// (str, obj, obj) -> null // (str, obj, obj) -> null
function apply (name, source, target) { function apply (ns, source, target) {
Object.keys(source).forEach(function (key) { Object.keys(source).forEach(function (key) {
if (name) { if (ns) {
if (!target[name]) target[name] = {} if (!target[ns]) target[ns] = {}
target[name][key] = source[key] target[ns][key] = source[key]
target[name][key].namespace = name
} else target[key] = source[key] } else target[key] = source[key]
}) })
} }