Files
buuh/index.js
T
Yoshua Wuyts eb9b7fd59f v6 beta branch (#489)
* become prototype based

upgrade to new nanobus

remove nanohistory

add nanotiming

remove nanomount

move newTree to next tick

upgrade timing API

add more node warnings to .start/.mount

add morph timing

pass app instance to .use()

self-clearing async-timing

clean up timings

better mount error

fix mount bug

de-prototype helper functions

update to latest nanotiming

upgrade to nanotiming 5

update nanotiming

add toString asserts

more assertions

add event based tracing

expose createlocation

assert return types from render to always be the same

add navigate event

expose route on state

expose events

update uglify

use scroll-to-anchor

update nanobus

document events

update docs

more docs

changelog v6

upgrade nanomorph

fixup! neatify morph assertions

update bel dep

add popstate docs to changelog

* expose events on state

* upgrade deps
2017-06-28 15:53:37 +02:00

182 lines
5.8 KiB
JavaScript

var scrollToAnchor = require('scroll-to-anchor')
var documentReady = require('document-ready')
var nanotiming = require('nanotiming')
var nanorouter = require('nanorouter')
var nanomorph = require('nanomorph')
var nanohref = require('nanohref')
var nanoraf = require('nanoraf')
var nanobus = require('nanobus')
var assert = require('assert')
module.exports = Choo
var HISTORY_OBJECT = {}
function Choo (opts) {
if (!(this instanceof Choo)) return new Choo(opts)
opts = opts || {}
assert.equal(typeof opts, 'object', 'choo: opts should be type object')
var routerOpts = {
default: opts.defaultRoute || '/404',
curry: true
}
// define events used by choo
this._events = {
DOMCONTENTLOADED: 'DOMContentLoaded',
REPLACESTATE: 'replaceState',
PUSHSTATE: 'pushState',
NAVIGATE: 'navigate',
POPSTATE: 'popState',
RENDER: 'render'
}
// properties for internal use only
this._historyEnabled = opts.history === undefined ? true : opts.history
this._hrefEnabled = opts.href === undefined ? true : opts.href
this._tree = null
// properties that are part of the API
this.router = nanorouter(routerOpts)
this.emitter = nanobus('choo.emit')
this.state = { events: this._events }
}
Choo.prototype.route = function (route, handler) {
assert.equal(typeof route, 'string', 'choo.route: route should be type string')
assert.equal(typeof handler, 'function', 'choo.handler: route should be type function')
var self = this
this.router.on(route, function (params) {
return function () {
self.state.params = params
self.state.route = route
var routeTiming = nanotiming("choo.route('" + route + "')")
var res = handler(self.state, function (eventName, data) {
self.emitter.emit(eventName, data)
})
routeTiming()
return res
}
})
}
Choo.prototype.use = function (cb) {
assert.equal(typeof cb, 'function', 'choo.use: cb should be type function')
var endTiming = nanotiming('choo.use')
cb(this.state, this.emitter, this)
endTiming()
}
Choo.prototype.start = function () {
assert.equal(typeof window, 'object', 'choo.start: window was not found. .start() must be called in a browser, use .toString() if running in Node')
var self = this
if (this._historyEnabled) {
this.emitter.prependListener(this._events.NAVIGATE, function () {
self.emitter.emit(self._events.RENDER)
setTimeout(scrollToAnchor.bind(null, window.location.hash), 0)
})
this.emitter.prependListener(this._events.POPSTATE, function () {
self.emitter.emit(self._events.NAVIGATE)
})
this.emitter.prependListener(this._events.PUSHSTATE, function (href) {
assert.equal(typeof href, 'string', 'events.pushState: href should be type string')
window.history.pushState(HISTORY_OBJECT, null, href)
self.emitter.emit(self._events.NAVIGATE)
})
this.emitter.prependListener(this._events.REPLACESTATE, function (href) {
assert.equal(typeof href, 'string', 'events.replaceState: href should be type string')
window.history.replaceState(HISTORY_OBJECT, null, href)
self.emitter.emit(self._events.NAVIGATE)
})
window.onpopstate = function () {
self.emitter.emit(self._events.POPSTATE)
}
if (self._hrefEnabled) {
nanohref(function (location) {
var href = location.href
var currHref = window.location.href
if (href === currHref) return
self.emitter.emit(self._events.PUSHSTATE, href)
})
}
}
var location = this._createLocation()
this._tree = this.router(location)
assert.ok(this._tree, 'choo.start: no valid DOM node returned for location ' + location)
this.emitter.prependListener(self._events.RENDER, nanoraf(function () {
var renderTiming = nanotiming('choo.render')
var newTree = self.router(self._createLocation())
assert.ok(newTree, 'choo.render: no valid DOM node returned for location ' + location)
assert.equal(self._tree.nodeName, newTree.nodeName, 'choo.render: The target node <' +
self._tree.nodeName.toLowerCase() + '> is not the same type as the new node <' +
newTree.nodeName.toLowerCase() + '>.')
var morphTiming = nanotiming('choo.morph')
nanomorph(self._tree, newTree)
morphTiming()
renderTiming()
}))
documentReady(function () {
self.emitter.emit(self._events.DOMCONTENTLOADED)
})
return this._tree
}
Choo.prototype.mount = function mount (selector) {
assert.equal(typeof window, 'object', 'choo.mount: window was not found. .mount() must be called in a browser, use .toString() if running in Node')
assert.equal(typeof selector, 'string', 'choo.mount: selector should be type string')
var self = this
documentReady(function () {
var renderTiming = nanotiming('choo.render')
var newTree = self.start()
self._tree = document.querySelector(selector)
assert.ok(self._tree, 'choo.mount: could not query selector: ' + selector)
assert.equal(self._tree.nodeName, newTree.nodeName, 'choo.mount: The target node <' +
self._tree.nodeName.toLowerCase() + '> is not the same type as the new node <' +
newTree.nodeName.toLowerCase() + '>.')
var morphTiming = nanotiming('choo.morph')
nanomorph(self._tree, newTree)
morphTiming()
renderTiming()
})
}
Choo.prototype.toString = function (location, state) {
this.state = state || {}
assert.equal(typeof location, 'string', 'choo.toString: location should be type string')
assert.equal(typeof this.state, 'object', 'choo.toString: state should be type object')
var html = this.router(location)
assert.ok(html, 'choo.toString: no valid value returned for the route ' + location)
return html.toString()
}
Choo.prototype._createLocation = function () {
var pathname = window.location.pathname.replace(/\/$/, '')
var hash = window.location.hash.replace(/^#/, '/')
return pathname + hash
}