Skip to content
Please note that GitHub no longer supports Internet Explorer.

We recommend upgrading to the latest Microsoft Edge, Google Chrome, or Firefox.

Learn more
Permalink
Tree: 74dc1286e6
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time. Cannot retrieve contributors at this time
181 lines (145 sloc) 5.56 KB
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
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
}
// 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 = {}
}
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
var routeTiming = nanotiming("choo.route('" + route + "')")
var res = handler(self.state, function (eventName, data) {
self.emitter.emit(eventName, data)
})
routeTiming(self._trace.bind(self))
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(this._trace.bind(this))
}
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('navigate', function () {
self.emitter.emit('render')
setTimeout(scrollIntoView, 0)
})
this.emitter.prependListener('popState', function () {
self.emitter.emit('navigate')
})
this.emitter.prependListener('pushState', function (href) {
window.history.pushState({}, null, href)
self.emitter.emit('navigate')
})
this.emitter.prependListener('replaceState', function (href) {
window.history.replaceState({}, null, href)
self.emitter.emit('navigate')
})
window.onpopstate = function () {
self.emitter.emit('popState')
}
if (self._hrefEnabled) {
nanohref(function (location) {
var href = location.href
var currHref = window.location.href
if (href === currHref) return
self.emitter.emit('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('render', nanoraf(function () {
var renderTiming = nanotiming('choo.render')
var newTree = self.router(self._createLocation())
var morphTiming = nanotiming('choo.morph')
var resultTree = nanomorph(self._tree, newTree)
assert.ok(resultTree, 'choo.render: no valid DOM node returned for location ' + location)
assert.equal(resultTree, self._tree, 'choo.render: The target node <' +
resultTree.nodeName.toLowerCase() + '> is not the same type as the new node <' +
self._tree.nodeName.toLowerCase() + '>.')
self._tree = resultTree
morphTiming(self._trace.bind(self))
renderTiming(self._trace.bind(self))
}))
documentReady(function () {
self.emitter.emit('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()
var root = document.querySelector(selector)
assert.ok(root, 'choo.mount: could not query selector: ' + selector)
var morphTiming = nanotiming('choo.morph')
var resultTree = nanomorph(root, newTree)
morphTiming(self._trace.bind(self))
assert.equal(resultTree, root, 'choo.mount: The target node <' +
resultTree.nodeName.toLowerCase() + '> is not the same type as the new node <' +
newTree.nodeName.toLowerCase() + '>.')
self._tree = root
renderTiming(self._trace.bind(self))
})
}
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
}
Choo.prototype._trace = function (timing, name) {
if (timing) timing.__name = name
this.emitter.emit('trace', timing)
}
function scrollIntoView () {
var hash = window.location.hash
if (hash) {
try {
var el = document.querySelector(hash)
if (el) el.scrollIntoView(true)
} catch (e) {}
}
}
You can’t perform that action at this time.