examples/http: update for ns
This commit is contained in:
@@ -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
@@ -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 })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user