Files
buuh/index.js
T

173 lines
5.0 KiB
JavaScript
Raw Normal View History

2016-05-12 14:45:20 +07:00
const history = require('sheet-router/history')
2016-05-11 13:51:17 +07:00
const sheetRouter = require('sheet-router')
const document = require('global/document')
2016-07-03 15:08:48 -07:00
const onReady = require('document-ready')
2016-05-12 14:45:20 +07:00
const href = require('sheet-router/href')
2016-06-04 15:03:56 -04:00
const hash = require('sheet-router/hash')
const hashMatch = require('hash-match')
2016-06-27 01:04:21 +02:00
const barracks = require('barracks')
2016-05-23 10:44:32 +09:00
const assert = require('assert')
2016-05-11 13:51:17 +07:00
const xtend = require('xtend')
const yo = require('yo-yo')
2016-05-10 23:23:33 +07:00
module.exports = choo
2016-05-23 10:44:32 +09:00
// framework for creating sturdy web applications
2016-05-11 13:51:17 +07:00
// null -> fn
2016-06-27 01:04:21 +02:00
function choo (opts) {
opts = opts || {}
2016-07-05 13:22:22 +02:00
const _store = start._store = barracks(xtend(opts, { onStateChange: render }))
2016-07-02 00:13:13 +02:00
var _router = start._router = null
var _defaultRoute = null
2016-06-27 01:04:21 +02:00
var _rootNode = null
2016-07-02 00:13:13 +02:00
var _routes = null
2016-05-11 13:51:17 +07:00
2016-05-21 21:59:03 +09:00
start.toString = toString
2016-05-11 13:51:17 +07:00
start.router = router
start.model = model
start.start = start
return start
2016-05-21 21:59:03 +09:00
// render the application to a string
2016-05-23 10:44:32 +09:00
// (str, obj) -> str
2016-05-23 23:39:22 +09:00
function toString (route, serverState) {
2016-06-27 01:04:21 +02:00
serverState = serverState || {}
assert.equal(typeof route, 'string', 'choo.app.toString: route must be a string')
assert.equal(typeof serverState, 'object', 'choo.app.toString: serverState must be an object')
2016-07-05 13:22:22 +02:00
_store.start({ subscriptions: false, reducers: false, effects: false })
2016-07-02 00:13:13 +02:00
2016-06-27 01:04:21 +02:00
const state = _store.state({ state: serverState })
2016-07-02 00:13:13 +02:00
const router = createRouter(_defaultRoute, _routes, createSend)
const tree = router(route, state)
return tree.outerHTML || tree.toString()
2016-07-02 00:13:13 +02:00
function createSend () {
return function send () {
assert.fail('choo: send() cannot be called from Node')
}
}
2016-05-21 21:59:03 +09:00
}
2016-05-11 13:51:17 +07:00
// start the application
2016-05-23 23:39:22 +09:00
// (str?, obj?) -> DOMNode
2016-06-27 01:04:21 +02:00
function start (selector, startOpts) {
if (!startOpts && typeof selector !== 'string') {
startOpts = selector
selector = null
}
2016-06-27 01:04:21 +02:00
startOpts = startOpts || {}
2016-05-16 04:09:02 +07:00
2016-06-27 01:04:21 +02:00
_store.model(appInit(startOpts))
const createSend = _store.start(startOpts)
2016-07-02 00:13:13 +02:00
_router = start._router = createRouter(_defaultRoute, _routes, createSend)
2016-07-03 15:08:48 -07:00
const state = _store.state({state: {}})
2016-05-11 13:51:17 +07:00
2016-06-27 01:04:21 +02:00
if (!selector) {
2016-07-02 00:13:13 +02:00
const tree = _router(state.location.pathname, state)
2016-06-27 01:04:21 +02:00
_rootNode = tree
2016-05-23 23:39:22 +09:00
return tree
2016-06-27 01:04:21 +02:00
} else {
2016-07-03 15:08:48 -07:00
onReady(function onReady () {
2016-06-27 01:04:21 +02:00
const oldTree = document.querySelector(selector)
assert.ok(oldTree, 'could not query selector: ' + selector)
2016-07-02 00:13:13 +02:00
const newTree = _router(state.location.pathname, state)
2016-06-27 01:04:21 +02:00
_rootNode = yo.update(oldTree, newTree)
})
2016-05-23 23:39:22 +09:00
}
2016-06-27 01:04:21 +02:00
}
2016-05-11 13:51:17 +07:00
2016-06-27 01:04:21 +02:00
// update the DOM after every state mutation
// (obj, obj, obj, str, fn) -> null
2016-07-02 21:05:52 -04:00
function render (data, state, prev, name, createSend) {
2016-07-05 13:22:22 +02:00
if (opts.onStateChange) {
opts.onStateChange(data, state, prev, name, createSend)
}
2016-05-16 04:09:02 +07:00
2016-07-02 00:13:13 +02:00
const newTree = _router(state.location.pathname, state, prev)
2016-06-27 01:04:21 +02:00
_rootNode = yo.update(_rootNode, newTree)
2016-05-11 13:51:17 +07:00
}
2016-05-23 10:44:32 +09:00
// register all routes on the router
2016-06-23 16:44:45 +02:00
// (str?, [fn|[fn]]) -> obj
2016-07-02 00:13:13 +02:00
function router (defaultRoute, routes) {
_defaultRoute = defaultRoute
_routes = routes
2016-05-11 13:51:17 +07:00
}
// create a new model
// (str?, obj) -> null
2016-05-23 02:45:15 +09:00
function model (model) {
2016-06-27 01:04:21 +02:00
_store.model(model)
2016-05-11 13:51:17 +07:00
}
2016-07-02 00:13:13 +02:00
// create a new router with a custom `createRoute()` function
// (str?, obj, fn?) -> null
function createRouter (defaultRoute, routes, createSend) {
var prev = {}
return sheetRouter(defaultRoute, routes, createRoute)
function createRoute (routeFn) {
return function (route, inline, child) {
if (typeof inline === 'function') {
inline = wrap(inline, route)
}
return routeFn(route, inline, child)
}
function wrap (child, route) {
2016-07-08 12:19:15 +02:00
const send = createSend('view: ' + route, true)
2016-07-02 00:13:13 +02:00
return function chooWrap (params, state) {
const nwPrev = prev
const nwState = prev = xtend(state, { params: params })
2016-07-05 13:22:22 +02:00
if (opts.freeze !== false) Object.freeze(nwState)
2016-07-02 00:13:13 +02:00
return child(nwState, nwPrev, send)
}
}
}
}
2016-05-11 13:51:17 +07:00
}
2016-05-12 14:45:20 +07:00
// initial application state model
2016-05-16 04:09:02 +07:00
// obj -> obj
function appInit (opts) {
2016-06-27 01:04:21 +02:00
const loc = document.location
2016-07-02 00:13:13 +02:00
const state = { pathname: (opts.hash) ? hashMatch(loc.hash) : loc.href }
2016-06-27 01:04:21 +02:00
const reducers = {
2016-07-02 21:05:52 -04:00
setLocation: function setLocation (data, state) {
return { pathname: data.location.replace(/#.*/, '') }
2016-05-23 10:44:32 +09:00
}
2016-05-11 13:51:17 +07:00
}
2016-06-04 19:53:05 -04:00
// if hash routing explicitly enabled, subscribe to it
2016-06-27 01:04:21 +02:00
const subs = {}
2016-06-04 19:53:05 -04:00
if (opts.hash === true) {
2016-06-04 15:03:56 -04:00
pushLocationSub(function (navigate) {
hash(function (fragment) {
navigate(hashMatch(fragment))
})
2016-06-27 01:04:21 +02:00
}, 'handleHash', subs)
2016-06-04 19:53:05 -04:00
} else {
2016-07-02 00:13:13 +02:00
if (opts.history !== false) pushLocationSub(history, 'handleHistory', subs)
2016-06-27 01:04:21 +02:00
if (opts.href !== false) pushLocationSub(href, 'handleHref', subs)
2016-06-04 15:03:56 -04:00
}
2016-05-16 04:09:02 +07:00
2016-06-27 01:04:21 +02:00
return {
2016-07-02 00:13:13 +02:00
namespace: 'location',
2016-06-27 01:04:21 +02:00
subscriptions: subs,
reducers: reducers,
state: state
}
2016-05-16 04:09:02 +07:00
2016-05-23 10:44:32 +09:00
// create a new subscription that modifies
2016-05-23 14:40:52 +09:00
// 'app:location' and push it to be loaded
2016-06-27 01:04:21 +02:00
// (fn, obj) -> null
function pushLocationSub (cb, key, model) {
model[key] = function (send, done) {
2016-07-02 00:13:13 +02:00
cb(function navigate (pathname) {
send('location:setLocation', { location: pathname }, done)
2016-05-23 10:44:32 +09:00
})
2016-06-27 01:04:21 +02:00
}
2016-05-11 13:51:17 +07:00
}
}