Files
buuh/index.js
T

179 lines
4.3 KiB
JavaScript

const history = require('sheet-router/history')
const sheetRouter = require('sheet-router')
const document = require('global/document')
const href = require('sheet-router/href')
const sendAction = require('send-action')
const xtend = require('xtend')
const yo = require('yo-yo')
choo.view = yo
module.exports = choo
// A framework for creating sturdy web applications
// null -> fn
function choo () {
const _models = []
var _router = null
start.toString = toString
start.router = router
start.model = model
start.start = start
return start
// render the application to a string
function toString (route, state) {
const initialState = {}
_models.forEach(function (model) {
if (model.state) apply(model.name, model.state, initialState)
})
const tree = _router(route, xtend(initialState, state), function () {
throw new Error('send() cannot be called on the server')
})
return tree.toString()
}
// start the application
// obj -> DOMNode
function start (opts) {
opts = opts || {}
const name = opts.name || 'choo'
const initialState = {}
const reducers = {}
const effects = {}
_models.push(appInit(opts))
_models.forEach(function (model) {
if (model.state) apply(model.namespace, model.state, initialState)
if (model.reducers) apply(model.namespace, model.reducers, reducers)
if (model.effects) apply(model.namespace, model.effects, effects)
})
const send = sendAction({
onaction: handleAction,
onchange: onchange,
state: initialState
})
_models.forEach(function (model) {
if (model.subscriptions) {
model.subscriptions.forEach(function (sub) {
sub(send)
})
}
})
const rootId = name + '-root'
const tree = _router(send.state().app.location, send.state(), send)
tree.setAttribute('id', rootId)
return tree
function handleAction (action, state, send) {
var _reducers = false
var _effects = false
var newState = null
if (/:/.test(action.type)) {
const arr = action.type.split(':')
var ns = arr[0]
action.type = arr[1]
}
const nsReducers = ns ? reducers[ns] : reducers
if (nsReducers[action.type]) {
if (ns) {
state[ns] = reducers[ns][action.type](action, state[ns])
newState = state
} else {
newState = xtend(state, reducers[action.type](action, state))
}
_reducers = true
}
const nsEffects = ns ? effects[ns] : effects
if (nsEffects) {
nsEffects[action.type](action, newState || state)
_effects = true
}
if (!_reducers && !_effects) {
throw new Error('Could not find action ' + action.type)
}
return newState || state
}
// update on every change
function onchange (action, state) {
const oldTree = document.querySelector('#' + rootId)
const newTree = _router(state.app.location, state, send)
newTree.setAttribute('id', rootId)
yo.update(oldTree, newTree)
}
}
// register all routes
// [obj|fn] -> null
function router (cb) {
_router = sheetRouter(cb)
return _router
}
// create a new model
// (str?, obj) -> null
function model (model) {
_models.push(model)
}
}
// initial application state model
// obj -> obj
function appInit (opts) {
const model = {
namespace: 'app',
state: { location: document.location.href },
reducers: { location: setLocation },
subscriptions: []
}
if (opts.href !== false) {
model.subscriptions.push(function (send) {
href(function (href) {
send('location', { location: href })
})
})
}
if (opts.history !== false) {
model.subscriptions.push(function (send) {
history(function (href) {
send('location', { location: href })
})
})
}
return model
// handle href links
function setLocation (action, state) {
const location = action.location.replace(/#.*/, '')
return xtend(state, { location: location })
}
}
// compose an object conditionally
// (str, obj, obj) -> null
function apply (name, source, target) {
Object.keys(source).forEach(function (key) {
if (name) {
if (!target[name]) target[name] = {}
target[name][key] = source[key]
target[name][key].namespace = name
} else target[key] = source[key]
})
}