Merge branch 'rewrite' into array-isArray
This commit is contained in:
commit
1050ade7c8
73 changed files with 2011 additions and 3136 deletions
|
|
@ -182,7 +182,7 @@ module.exports = {
|
|||
"prefer-const": "error",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"prefer-spread": "error",
|
||||
"prefer-spread": "off",
|
||||
"prefer-template": "off",
|
||||
"quote-props": "off",
|
||||
"quotes": [
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ install:
|
|||
- npm install
|
||||
- npm install @alrra/travis-scripts@^3.0.1
|
||||
|
||||
# Lint (but don't fail build) before running tests
|
||||
before_script: npm run lint || true
|
||||
|
||||
# Run more than just npm test
|
||||
script: npm run build && npm test
|
||||
|
||||
# After a successful build create bundles & commit back to the repo
|
||||
after_success:
|
||||
- |
|
||||
|
|
@ -29,8 +35,7 @@ after_success:
|
|||
--path-encrypted-key "./.deploy.enc"
|
||||
|
||||
# Build & commit changes
|
||||
$(npm bin)/commit-changes --commands "npm run build" \
|
||||
--commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \
|
||||
$(npm bin)/commit-changes --commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \
|
||||
--branch "$BRANCH"
|
||||
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Mithril.js - A framework for building brilliant applications
|
||||
|
||||
[Installation](docs/installation.md) | [API](docs/api.md) | [Examples](docs/examples.md) | [Migration Guide](docs/v1.x-migration.md)
|
||||
[Installation](docs/installation.md) | [API](docs/api.md) | [Examples](docs/examples.md) | [Changelog/Migration Guide](docs/change-log.md)
|
||||
|
||||
Note: This branch is the upcoming version 1.0. It's a rewrite from the ground up and it's not backwards compatible with [Mithril 0.2.x](http://mithril.js.org). You can find preliminary [documentation here](docs) and [migration guide here](docs/v1.x-migration.md)
|
||||
Note: This branch is the upcoming version 1.0. It's a rewrite from the ground up and it's not backwards compatible with [Mithril 0.2.x](http://mithril.js.org). You can find preliminary [documentation here](docs) and [migration guide here](docs/change-log.md)
|
||||
|
||||
This rewrite aims to fix longstanding API design issues, significantly improve performance, and clean up the codebase.
|
||||
|
||||
|
|
@ -34,6 +34,6 @@ There are over 4000 assertions in the test suite, and tests cover even difficult
|
|||
|
||||
## Modularity
|
||||
|
||||
Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.42 KB<!-- /size --> min+gzip
|
||||
Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.43 KB<!-- /size --> min+gzip
|
||||
|
||||
In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var throttle = require("../api/throttle")
|
||||
|
||||
module.exports = function(root, renderer, pubsub, callback) {
|
||||
var run = throttle(callback)
|
||||
if (renderer != null) {
|
||||
renderer.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) pubsub.publish()
|
||||
})
|
||||
}
|
||||
|
||||
if (pubsub != null) {
|
||||
if (root.redraw) pubsub.unsubscribe(root.redraw)
|
||||
pubsub.subscribe(run)
|
||||
}
|
||||
|
||||
return root.redraw = run
|
||||
}
|
||||
20
api/mount.js
20
api/mount.js
|
|
@ -1,23 +1,21 @@
|
|||
"use strict"
|
||||
|
||||
var Vnode = require("../render/vnode")
|
||||
var autoredraw = require("../api/autoredraw")
|
||||
|
||||
module.exports = function(renderer, pubsub) {
|
||||
module.exports = function(redrawService) {
|
||||
return function(root, component) {
|
||||
if (component === null) {
|
||||
renderer.render(root, [])
|
||||
pubsub.unsubscribe(root.redraw)
|
||||
delete root.redraw
|
||||
redrawService.render(root, [])
|
||||
redrawService.unsubscribe(root)
|
||||
return
|
||||
}
|
||||
|
||||
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
|
||||
var run = autoredraw(root, renderer, pubsub, function() {
|
||||
renderer.render(root, Vnode(component, undefined, undefined, undefined, undefined, undefined))
|
||||
})
|
||||
|
||||
run()
|
||||
|
||||
var run = function() {
|
||||
redrawService.render(root, Vnode(component))
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
redrawService.redraw()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = function() {
|
||||
var callbacks = []
|
||||
function unsubscribe(callback) {
|
||||
var index = callbacks.indexOf(callback)
|
||||
if (index > -1) callbacks.splice(index, 1)
|
||||
}
|
||||
function publish() {
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i].apply(this, arguments)
|
||||
}
|
||||
}
|
||||
return {subscribe: callbacks.push.bind(callbacks), unsubscribe: unsubscribe, publish: publish}
|
||||
}
|
||||
47
api/redraw.js
Normal file
47
api/redraw.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"use strict"
|
||||
|
||||
var coreRenderer = require("../render/render")
|
||||
|
||||
function throttle(callback) {
|
||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||
var time = 16
|
||||
var last = 0, pending = null
|
||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||
return function() {
|
||||
var now = Date.now()
|
||||
if (last === 0 || now - last >= time) {
|
||||
last = now
|
||||
callback()
|
||||
}
|
||||
else if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = null
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, time - (now - last))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function($window) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) redraw()
|
||||
})
|
||||
|
||||
var callbacks = []
|
||||
function subscribe(key, callback) {
|
||||
unsubscribe(key)
|
||||
callbacks.push(key, throttle(callback))
|
||||
}
|
||||
function unsubscribe(key) {
|
||||
var index = callbacks.indexOf(key)
|
||||
if (index > -1) callbacks.splice(index, 2)
|
||||
}
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
}
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
|
|
@ -3,55 +3,45 @@
|
|||
var Vnode = require("../render/vnode")
|
||||
var coreRouter = require("../router/router")
|
||||
|
||||
module.exports = function($window, mount) {
|
||||
var router = coreRouter($window)
|
||||
var currentResolve, currentComponent, currentRender, currentArgs, currentPath
|
||||
|
||||
var RouteComponent = {view: function() {
|
||||
return [currentRender(Vnode(currentComponent, null, currentArgs, undefined, undefined, undefined))]
|
||||
}}
|
||||
function defaultRender(vnode) {
|
||||
return vnode
|
||||
}
|
||||
module.exports = function($window, redrawService) {
|
||||
var routeService = coreRouter($window)
|
||||
|
||||
var identity = function(v) {return v}
|
||||
var resolver, component, attrs, currentPath, resolve
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
currentComponent = "div"
|
||||
currentRender = defaultRender
|
||||
currentArgs = null
|
||||
|
||||
mount(root, RouteComponent)
|
||||
|
||||
router.defineRoutes(routes, function(payload, args, path) {
|
||||
var isResolver = typeof payload.view !== "function"
|
||||
var render = defaultRender
|
||||
|
||||
var resolve = currentResolve = function (component) {
|
||||
if (resolve !== currentResolve) return
|
||||
currentResolve = null
|
||||
|
||||
currentComponent = component != null ? component : isResolver ? "div" : payload
|
||||
currentRender = render
|
||||
currentArgs = args
|
||||
currentPath = path
|
||||
|
||||
root.redraw(true)
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var update = function(routeResolver, comp, params, path) {
|
||||
resolver = routeResolver, component = comp, attrs = params, currentPath = path, resolve = null
|
||||
resolver.render = routeResolver.render || identity
|
||||
render()
|
||||
}
|
||||
var render = function() {
|
||||
if (resolver != null) redrawService.render(root, resolver.render(Vnode(component, attrs.key, attrs)))
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path) {
|
||||
if (payload.view) update({}, payload, params, path)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
if (resolve != null) update(payload, component, params, path)
|
||||
else {
|
||||
resolve = function(resolved) {
|
||||
update(payload, resolved, params, path)
|
||||
}
|
||||
payload.onmatch(function(resolved) {
|
||||
if (resolve != null) resolve(resolved)
|
||||
}, params, path)
|
||||
}
|
||||
}
|
||||
else update(payload, "div", params, path)
|
||||
}
|
||||
var onmatch = function() {
|
||||
resolve()
|
||||
}
|
||||
if (isResolver) {
|
||||
if (typeof payload.render === "function") render = payload.render.bind(payload)
|
||||
if (typeof payload.onmatch === "function") onmatch = payload.onmatch
|
||||
}
|
||||
|
||||
onmatch.call(payload, resolve, args, path)
|
||||
}, function() {
|
||||
router.setPath(defaultRoute, null, {replace: true})
|
||||
routeService.setPath(defaultRoute)
|
||||
})
|
||||
redrawService.subscribe(root, render)
|
||||
}
|
||||
route.link = router.link
|
||||
route.prefix = router.setPrefix
|
||||
route.set = router.setPath
|
||||
route.set = routeService.setPath
|
||||
route.get = function() {return currentPath}
|
||||
|
||||
route.prefix = routeService.setPrefix
|
||||
route.link = routeService.link
|
||||
return route
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<script src="../../test-utils/xhrMock.js"></script>
|
||||
<script src="../../test-utils/browserMock.js"></script>
|
||||
|
||||
<script src="../../util/stream.js"></script>
|
||||
<script src="../../promise/promise.js"></script>
|
||||
<script src="../../render/vnode.js"></script>
|
||||
<script src="../../render/trust.js"></script>
|
||||
<script src="../../render/fragment.js"></script>
|
||||
|
|
@ -24,15 +24,11 @@
|
|||
<script src="../../querystring/parse.js"></script>
|
||||
<script src="../../request/request.js"></script>
|
||||
<script src="../../router/router.js"></script>
|
||||
<script src="../../api/throttle.js"></script>
|
||||
<script src="../../api/pubsub.js"></script>
|
||||
<script src="../../api/autoredraw.js"></script>
|
||||
<script src="../../api/redraw.js"></script>
|
||||
<script src="../../api/mount.js"></script>
|
||||
<script src="../../api/router.js"></script>
|
||||
|
||||
<script src="./test-throttle.js"></script>
|
||||
<script src="./test-pubsub.js"></script>
|
||||
<script src="./test-autoredraw.js"></script>
|
||||
<script src="./test-redraw.js"></script>
|
||||
<script src="./test-mount.js"></script>
|
||||
<script src="./test-router.js"></script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiPubSub = require("../../api/pubsub")
|
||||
var autoredraw = require("../../api/autoredraw")
|
||||
|
||||
o.spec("autoredraw", function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, renderer, pubsub, spy
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
root = $window.document.body
|
||||
renderer = coreRenderer($window)
|
||||
pubsub = apiPubSub()
|
||||
spy = o.spy()
|
||||
})
|
||||
|
||||
o("returns self-trigger", function() {
|
||||
var run = autoredraw(root, renderer, pubsub, spy)
|
||||
|
||||
run()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("null renderer doesn't throw", function(done) {
|
||||
autoredraw(root, null, pubsub, spy)
|
||||
done()
|
||||
})
|
||||
|
||||
o("null pubsub doesn't throw", function(done) {
|
||||
autoredraw(root, renderer, null, spy)
|
||||
done()
|
||||
})
|
||||
|
||||
o("registers onevent", function() {
|
||||
autoredraw(root, renderer, pubsub, spy)
|
||||
|
||||
renderer.render(root, {tag: "div", attrs: {onclick: function() {}}})
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("registers pubsub", function() {
|
||||
autoredraw(root, renderer, pubsub, spy)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("re-registering pubsub works", function() {
|
||||
autoredraw(root, renderer, pubsub, spy)
|
||||
autoredraw(root, renderer, pubsub, spy)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("throttles", function(done) {
|
||||
var run = autoredraw(root, renderer, pubsub, spy)
|
||||
|
||||
run()
|
||||
run()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("does not redraw if e.redraw is false", function() {
|
||||
autoredraw(root, renderer, pubsub, spy)
|
||||
|
||||
renderer.render(root, {tag: "div", attrs: {onclick: function(e) {e.redraw = false}}})
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
@ -5,20 +5,20 @@ var domMock = require("../../test-utils/domMock")
|
|||
|
||||
var m = require("../../render/hyperscript")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiPubSub = require("../../api/pubsub")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
o.spec("mount", function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redraw, mount, render
|
||||
var $window, root, redrawService, mount, render
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
redraw = apiPubSub()
|
||||
mount = apiMounter(coreRenderer($window), redraw)
|
||||
redrawService = apiRedraw($window)
|
||||
mount = apiMounter(redrawService)
|
||||
render = coreRenderer($window).render
|
||||
})
|
||||
|
||||
|
|
@ -42,18 +42,16 @@ o.spec("mount", function() {
|
|||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("mounting null deletes `redraw` from `root`", function() {
|
||||
o("mounting null unmounts", function() {
|
||||
mount(root, {
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
|
||||
o(typeof root.redraw).equals('function')
|
||||
|
||||
mount(root, null)
|
||||
|
||||
o(typeof root.redraw).equals('undefined')
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
|
|
@ -161,7 +159,7 @@ o.spec("mount", function() {
|
|||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var oninit = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
|
@ -197,8 +195,8 @@ o.spec("mount", function() {
|
|||
mount(root, {
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit,
|
||||
onupdate : onupdate
|
||||
oninit: oninit,
|
||||
onupdate: onupdate
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -206,7 +204,7 @@ o.spec("mount", function() {
|
|||
o(oninit.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
redraw.publish()
|
||||
redrawService.redraw()
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
|
|
@ -215,4 +213,26 @@ o.spec("mount", function() {
|
|||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
var i = 0
|
||||
mount(root, {view: function() {i++}})
|
||||
var before = i
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
done()
|
||||
},40)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var apiPubSub = require("../../api/pubsub")
|
||||
|
||||
o.spec("pubsub", function() {
|
||||
var pubsub
|
||||
o.beforeEach(function() {
|
||||
pubsub = apiPubSub()
|
||||
})
|
||||
|
||||
o("shouldn't error if there are no renderers", function() {
|
||||
pubsub.publish()
|
||||
})
|
||||
|
||||
o("should run a single renderer entry", function() {
|
||||
var spy = o.spy()
|
||||
|
||||
pubsub.subscribe(spy)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
pubsub.publish()
|
||||
pubsub.publish()
|
||||
pubsub.publish()
|
||||
|
||||
o(spy.callCount).equals(4)
|
||||
})
|
||||
|
||||
o("should run all renderer entries", function() {
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
var spy3 = o.spy()
|
||||
|
||||
pubsub.subscribe(spy1)
|
||||
pubsub.subscribe(spy2)
|
||||
pubsub.subscribe(spy3)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
|
||||
pubsub.subscribe(spy)
|
||||
pubsub.unsubscribe(spy)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
|
||||
pubsub.subscribe(spy)
|
||||
pubsub.unsubscribe(null)
|
||||
|
||||
pubsub.publish()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
97
api/tests/test-redraw.js
Normal file
97
api/tests/test-redraw.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
|
||||
o.spec("redrawService", function() {
|
||||
var root, redrawService, $document
|
||||
o.beforeEach(function() {
|
||||
var $window = domMock()
|
||||
root = $window.document.body
|
||||
redrawService = apiRedraw($window)
|
||||
$document = $window.document
|
||||
})
|
||||
|
||||
o("shouldn't error if there are no renderers", function() {
|
||||
redrawService.redraw()
|
||||
})
|
||||
|
||||
o("should run a single renderer entry", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should run all renderer entries", function(done) {
|
||||
var el1 = $document.createElement("div")
|
||||
var el2 = $document.createElement("div")
|
||||
var el3 = $document.createElement("div")
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
var spy3 = o.spy()
|
||||
|
||||
redrawService.subscribe(el1, spy1)
|
||||
redrawService.subscribe(el2, spy2)
|
||||
redrawService.subscribe(el3, spy3)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
redrawService.unsubscribe(root, spy)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
redrawService.unsubscribe(null)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
|
|
@ -6,25 +6,23 @@ var browserMock = require("../../test-utils/browserMock")
|
|||
|
||||
var m = require("../../render/hyperscript")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiPubSub = require("../../api/pubsub")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiRouter = require("../../api/router")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
o.spec("route", function() {
|
||||
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
||||
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
||||
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redraw, mount, route
|
||||
var $window, root, redrawService, route
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = browserMock(env)
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
redraw = apiPubSub()
|
||||
mount = apiMounter(coreRenderer($window), redraw)
|
||||
route = apiRouter($window, mount)
|
||||
redrawService = apiRedraw($window)
|
||||
route = apiRouter($window, redrawService)
|
||||
route.prefix(prefix)
|
||||
})
|
||||
|
||||
|
|
@ -59,7 +57,7 @@ o.spec("route", function() {
|
|||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redraw.publish(true)
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
|
||||
|
|
@ -122,15 +120,15 @@ o.spec("route", function() {
|
|||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
redraw.publish(true)
|
||||
redrawService.redraw()
|
||||
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
|
@ -167,8 +165,8 @@ o.spec("route", function() {
|
|||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
|
@ -240,25 +238,28 @@ o.spec("route", function() {
|
|||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
o(this).equals(resolver)
|
||||
resolve(Component)
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
o(this).equals(resolver)
|
||||
|
||||
return vnode
|
||||
},
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
|
||||
resolve(Component)
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
|
||||
return vnode
|
||||
},
|
||||
},
|
||||
"/:id" : resolver
|
||||
})
|
||||
|
||||
o(matchCount).equals(1)
|
||||
|
|
@ -403,7 +404,7 @@ o.spec("route", function() {
|
|||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
|
||||
redraw.publish(true)
|
||||
redrawService.redraw()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
|
|
@ -505,7 +506,7 @@ o.spec("route", function() {
|
|||
o(view.callCount).equals(1)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
||||
redraw.publish(true)
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
|
@ -515,22 +516,25 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(done){
|
||||
var onmatch = o.spy(function(resolve){resolve()})
|
||||
var onmatch = o.spy(function(resolve) {resolve()})
|
||||
var render = o.spy(function(){return m("div")})
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, '/', {
|
||||
"/":{
|
||||
"/": {
|
||||
onmatch: onmatch,
|
||||
render: function(){return m("div")}
|
||||
render: render
|
||||
}
|
||||
})
|
||||
|
||||
o(onmatch.callCount).equals(1)
|
||||
o(render.callCount).equals(1)
|
||||
|
||||
route.set(route.get())
|
||||
|
||||
setTimeout(function() {
|
||||
o(onmatch.callCount).equals(2)
|
||||
o(render.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
|
|
@ -610,6 +614,53 @@ o.spec("route", function() {
|
|||
done()
|
||||
}, 30)
|
||||
})
|
||||
|
||||
o("route changes activate onbeforeremove", function(done, timeout) {
|
||||
var spy = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a": {
|
||||
onbeforeremove: spy,
|
||||
view: function() {}
|
||||
},
|
||||
"/b": {
|
||||
view: function() {}
|
||||
}
|
||||
})
|
||||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 30)
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
var i = 0
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/": {view: function(v) {i++}}
|
||||
})
|
||||
var before = i
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
o(before).equals(1) // routes synchronously
|
||||
o(after).equals(2) // redraws synchronously
|
||||
o(i).equals(3) // throttles rest
|
||||
done()
|
||||
},40)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var throttle = require("../../api/throttle")
|
||||
|
||||
o.spec("throttle", function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var spy, throttled
|
||||
o.beforeEach(function() {
|
||||
spy = o.spy()
|
||||
throttled = throttle(spy)
|
||||
})
|
||||
|
||||
o("runs first call synchronously", function() {
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("throttles subsequent synchronous calls", function(done) {
|
||||
throttled()
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET) //this delay is much higher than 16.6ms due to setTimeout clamp and other runtime costs
|
||||
})
|
||||
|
||||
o("calls after threshold", function(done) {
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
setTimeout(function(t) {
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
|
||||
})
|
||||
|
||||
o("throttles before threshold", function(done) {
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
callAsync(function(t) {
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("it only runs once per tick", function(done) {
|
||||
throttled()
|
||||
throttled()
|
||||
throttled()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("it supports forcing a synchronous redraw", function(done) {
|
||||
throttled()
|
||||
throttled()
|
||||
throttled(true)
|
||||
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(3)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = function(callback) {
|
||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||
var time = 16
|
||||
var last = 0, pending = null
|
||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||
return function(synchronous) {
|
||||
var now = Date.now()
|
||||
if (synchronous === true || last === 0 || now - last >= time) {
|
||||
last = now
|
||||
callback()
|
||||
}
|
||||
else if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = null
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, time - (now - last))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ function parse(file) {
|
|||
try {return JSON.parse(json)} catch (e) {throw new Error("invalid JSON: " + json)}
|
||||
}
|
||||
|
||||
var error
|
||||
function run(input, output) {
|
||||
try {
|
||||
var modules = {}
|
||||
|
|
@ -31,8 +32,9 @@ function run(input, output) {
|
|||
def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
|
||||
if (def[0] === ",") def = "\nvar ", pre = "\n"
|
||||
var dependency = resolve(filepath, filename)
|
||||
var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, uuid) : def + variable + eq + modules[dependency]))
|
||||
modules[dependency] = rest ? "_" + uuid : variable
|
||||
var localUUID = uuid // global uuid can update from nested `process` call, ensure same id is used on declaration and consumption
|
||||
var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, localUUID) : def + variable + eq + modules[dependency]))
|
||||
modules[dependency] = rest ? "_" + localUUID : variable
|
||||
uuid++
|
||||
return code + rest
|
||||
})
|
||||
|
|
@ -56,8 +58,11 @@ function run(input, output) {
|
|||
var code = read(filepath)
|
||||
// if there's a syntax error, report w/ proper stack trace
|
||||
try {new Function(code)} catch (e) {
|
||||
proc.exec("node " + filename, function(error) {
|
||||
if (error !== null) console.log("\x1b[31m" + error.message)
|
||||
proc.exec("node " + filepath, function(e) {
|
||||
if (e !== null && e.message !== error) {
|
||||
error = e.message
|
||||
console.log("\x1b[31m" + e.message + "\x1b[0m")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +116,11 @@ function run(input, output) {
|
|||
|
||||
code = "new function() {\n" + code + "\n}"
|
||||
|
||||
if (!isFile(output) || code !== read(output)) fs.writeFileSync(output, code, "utf8")
|
||||
if (!isFile(output) || code !== read(output)) {
|
||||
//try {new Function(code); console.log("build completed at " + new Date())} catch (e) {}
|
||||
error = null
|
||||
fs.writeFileSync(output, code, "utf8")
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e.message)
|
||||
|
|
|
|||
|
|
@ -273,6 +273,30 @@ o.spec("bundler", function() {
|
|||
remove("d.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if included multiple times", function() {
|
||||
write("a.js", `module.exports = 123`)
|
||||
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`)
|
||||
write("c.js", `var a = require("./a").toString()\nvar b = require("./b")`)
|
||||
bundle(ns + "c.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}`)
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
})
|
||||
o("works if included multiple times reverse", function() {
|
||||
write("a.js", `module.exports = 123`)
|
||||
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`)
|
||||
write("c.js", `var b = require("./b")\nvar a = require("./a").toString()`)
|
||||
bundle(ns + "c.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}`)
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
})
|
||||
o("reuses binding if possible", function() {
|
||||
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
|
||||
write("b.js", `var d = require("./d")\nmodule.exports = function() {return d + 1}`)
|
||||
|
|
|
|||
190
docs/api.md
190
docs/api.md
|
|
@ -1,22 +1,174 @@
|
|||
# API
|
||||
|
||||
- [m](hyperscript.md)
|
||||
- [m.render](render.md)
|
||||
- [m.mount](mount.md)
|
||||
- [m.route](route.md)
|
||||
- [m.route.set](route.md#routeset)
|
||||
- [m.route.get](route.md#routeget)
|
||||
- [m.route.prefix](route.md#routeprefix)
|
||||
- [m.route.link](route.md#routelink)
|
||||
- [m.request](request.md)
|
||||
- [m.jsonp](jsonp.md)
|
||||
- [m.parseQueryString](parseQueryString.md)
|
||||
- [m.buildQueryString](buildQueryString.md)
|
||||
- [m.withAttr](withAttr.md)
|
||||
- [m.trust](trust.md)
|
||||
- [m.fragment](fragment.md)
|
||||
- [m.redraw](redraw.md)
|
||||
- [m.version](version.md)
|
||||
### Cheatsheet
|
||||
|
||||
Here are examples for the most commonly used methods. If a method is not listed below, it's meant for advanced usage.
|
||||
|
||||
#### m(selector, attrs, children) - [docs](hyperscript.md)
|
||||
|
||||
```javascript
|
||||
m("div.class#id", {title: "title"}, ["children"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.mount(element, component) - [docs](mount.md)
|
||||
|
||||
```javascript
|
||||
var state = {
|
||||
count: 0,
|
||||
inc: function() {state.count++}
|
||||
}
|
||||
|
||||
var Counter = {
|
||||
view: function() {
|
||||
return m("div", {onclick: state.inc}, state.count)
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, Counter)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.route(root, defaultRoute, routes) - [docs](route.md)
|
||||
|
||||
```javascript
|
||||
var Home = {
|
||||
view: function() {
|
||||
return "Welcome"
|
||||
}
|
||||
}
|
||||
|
||||
m.route(document.body, "/home", {
|
||||
"/home": Home, // defines `http://localhost/#!/home`
|
||||
})
|
||||
```
|
||||
|
||||
#### m.route.set(path) - [docs](route.md#routeset)
|
||||
|
||||
```javascript
|
||||
m.route.set("/home")
|
||||
```
|
||||
|
||||
#### m.route.get() - [docs](route.md#routeget)
|
||||
|
||||
```javascript
|
||||
var currentRoute = m.route.get()
|
||||
```
|
||||
|
||||
#### m.route.prefix(prefix) - [docs](route.md#routeprefix)
|
||||
|
||||
Call this before `m.route()`
|
||||
|
||||
```javascript
|
||||
m.route.prefix("#!")
|
||||
```
|
||||
|
||||
#### m.route.link() - [docs](route.md#routelink)
|
||||
|
||||
```javascript
|
||||
m("a[href='/Home']", {oncreate: m.route.link}, "Go to home page")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.request(options) - [docs](request.md)
|
||||
|
||||
```javascript
|
||||
m.request({
|
||||
method: "PUT",
|
||||
url: "/api/v1/users/:id",
|
||||
data: {id: 1, name: "test"}
|
||||
})
|
||||
.then(function(result) {
|
||||
console.log(result)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.jsonp(options) - [docs](jsonp.md)
|
||||
|
||||
```javascript
|
||||
m.jsonp({
|
||||
url: "/api/v1/users/:id",
|
||||
data: {id: 1},
|
||||
callbackKey: "callback",
|
||||
})
|
||||
.then(function(result) {
|
||||
console.log(result)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.parseQueryString(querystring) - [docs](parseQueryString.md)
|
||||
|
||||
```javascript
|
||||
var object = m.parseQueryString("a=1&b=2")
|
||||
// {a: "1", b: "2"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.buildQueryString(object) - [docs](buildQueryString.md)
|
||||
|
||||
```javascript
|
||||
var querystring = m.buildQueryString({a: "1", b: "2"})
|
||||
// "a=1&b=2"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.withAttr(attrName, callback) - [docs](withAttr.md)
|
||||
|
||||
```javascript
|
||||
var state = {
|
||||
value: "",
|
||||
setValue: function(v) {value = v}
|
||||
}
|
||||
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("input", {
|
||||
oninput: m.withAttr("value", state.setValue),
|
||||
value: state.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, Component)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.trust(htmlString) - [docs](trust.md)
|
||||
|
||||
```javascript
|
||||
m.render(document.body, m.trust("<h1>Hello</h1>"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### m.redraw() - [docs](redraw.md)
|
||||
|
||||
```javascript
|
||||
var count = 0
|
||||
function inc() {
|
||||
setInterval(function() {
|
||||
count++
|
||||
m.redraw()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
var Counter = {
|
||||
oninit: inc,
|
||||
view: function() {
|
||||
return m("div", count)
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, Counter)
|
||||
```
|
||||
|
||||
- [Promise](promise.md)
|
||||
- [Stream](stream.md)
|
||||
|
|
@ -1,11 +1,23 @@
|
|||
# buildQueryString(object)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Turns an object into a string of form `a=1&b=2`
|
||||
|
||||
```javascript
|
||||
var querystring = m.buildQueryString({a: "1", b: "2"})
|
||||
// "a=1&b=2"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`querystring = m.buildQueryString(object)`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
# Migrating from `v0.2.x` to `v1.x`
|
||||
# Change log
|
||||
|
||||
- [Migrating from v0.2.x](#migrating-from-v02x)
|
||||
|
||||
---
|
||||
|
||||
### Migrating from `v0.2.x`
|
||||
|
||||
`v1.x` is largely API-compatible with `v0.2.x`, but there are some breaking changes.
|
||||
|
||||
If you are migrating, consider using the [mithril-codemods](https://www.npmjs.com/package/mithril-codemods) tool to help automate the most straightforward migrations.
|
||||
|
||||
- [`m.prop` removed](#mprop-removed)
|
||||
- [`m.component` removed](#mcomponent-removed)
|
||||
- [`config` function](#config-function)
|
||||
|
|
@ -15,14 +23,20 @@
|
|||
- [`m.route` and anchor tags](#mroute-and-anchor-tags)
|
||||
- [Reading/writing the current route](#readingwriting-the-current-route)
|
||||
- [Accessing route params](#accessing-route-params)
|
||||
- [Preventing unmounting](#preventing-unmounting)
|
||||
- [`m.request`](#mrequest)
|
||||
- [`m.sync` removed](#msync-removed)
|
||||
- [`xlink` namespace required](#xlink-namespace-required)
|
||||
- [Nested arrays in views](#nested-arrays-in-views)
|
||||
- [`vnode` equality checks](#vnode-equality-checks)
|
||||
- [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed)
|
||||
- [Synchronous redraw removed](#synchronous-redraw-removed)
|
||||
|
||||
---
|
||||
|
||||
## `m.prop` removed
|
||||
|
||||
In `v1.x`, `m.prop` is now a more powerful stream micro-library, but it's no longer part of core.
|
||||
In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
|
|
@ -243,7 +257,7 @@ m.mount(document.body, {
|
|||
oninit : function(vnode) {
|
||||
// ...
|
||||
},
|
||||
|
||||
|
||||
view : function(vnode) {
|
||||
// Use vnode.state instead of ctrl
|
||||
// Use vnode.attrs instead of options
|
||||
|
|
@ -406,9 +420,42 @@ m.route(document.body, "/booga", {
|
|||
|
||||
---
|
||||
|
||||
## Preventing unmounting
|
||||
|
||||
It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefault()`. Instead you should explicitly call `m.route.set` when the expected conditions are met.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
if (condition) e.preventDefault()
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return m("a[href=/]", {config: m.route})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("a", {onclick: function() {if (!condition) m.route.set("/")}})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## m.request
|
||||
|
||||
Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue` is no longer a supported option.
|
||||
Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue`, `unwrapSuccess` and `unwrapError` are no longer supported options.
|
||||
|
||||
In addition, requests no longer have `m.startComputation`/`m.endComputation` semantics. Instead, redraws are always triggered when a request promise chain completes (unless `background:true` is set).
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
|
|
@ -441,7 +488,13 @@ setTimeout(function() {
|
|||
}, 1000)
|
||||
```
|
||||
|
||||
The equivalent of `m.sync` is now `Promise.all`
|
||||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve its promise, and the `deserialize` callback is ignored.
|
||||
|
||||
---
|
||||
|
||||
## `m.sync` removed
|
||||
|
||||
`m.sync` has been removed in favor of `Promise.all`
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
|
|
@ -467,8 +520,6 @@ Promise.all([
|
|||
})
|
||||
```
|
||||
|
||||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve its promise, and the `deserialize` callback is ignored.
|
||||
|
||||
---
|
||||
|
||||
## `xlink` namespace required
|
||||
|
|
@ -492,3 +543,39 @@ m("svg",
|
|||
m("image[xlink:href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nested arrays in views
|
||||
|
||||
Arrays now represent [fragments](fragment.md), which are structurally significant in v1.x virtual DOM. Whereas nested arrays in v0.2.x would be flattened into one continuous list of virtual nodes for the purposes of diffing, v1.x preserves the array structure - the children of any given array are not considered siblings of those of adjacent arrays.
|
||||
|
||||
---
|
||||
|
||||
## `vnode` equality checks
|
||||
|
||||
If a vnode is strictly equal to the vnode occupying its place in the last draw, v1.x will skip that part of the tree without checking for mutations or triggering any lifecycle methods in the subtree. The component documentation contains [more detail on this issue](components.md#avoid-creating-component-instances-outside-views).
|
||||
|
||||
---
|
||||
|
||||
## `m.startComputation`/`m.endComputation` removed
|
||||
|
||||
They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x
|
||||
|
||||
---
|
||||
|
||||
## Synchronous redraw removed
|
||||
|
||||
In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. This behavior complicated usage of `m.redraw()` and caused some hard-to-reason about issues and has been removed.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.redraw(true); // redraws immediately & synchronously
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.redraw(); // schedules a redraw on the next requestAnimationFrame tick
|
||||
```
|
||||
|
|
@ -173,7 +173,7 @@ Although Mithril is flexible, some code patterns are discouraged:
|
|||
|
||||
#### Avoid restrictive interfaces
|
||||
|
||||
A component has a restrictive interface when it exposes only specific properties, under the assumption that other properties will not be needed, or that they can be added at a later time.
|
||||
Try to keep component interfaces generic - using `attrs` and `children` directly - unless the component requires special logic to operate on input.
|
||||
|
||||
In the example below, the `button` configuration is severely limited: it does not support any events other than `onclick`, it's not styleable and it only accepts text as children (but not elements, fragments or trusted HTML).
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ var RestrictiveComponent = {
|
|||
}
|
||||
```
|
||||
|
||||
It's preferable to allow passing through parameters to a component's root node, if it makes sense to do so:
|
||||
If the required attributes are equivalent to generic DOM attributes, it's preferable to allow passing through parameters to a component's root node.
|
||||
|
||||
```javascript
|
||||
// PREFER
|
||||
|
|
@ -201,7 +201,9 @@ var FlexibleComponent = {
|
|||
}
|
||||
```
|
||||
|
||||
#### Avoid magic indexes
|
||||
#### Don't manipulate `children`
|
||||
|
||||
However, if a component is opinionated in how it applies attributes or children, you should switch to using custom attributes.
|
||||
|
||||
Often it's desirable to define multiple sets of children, for example, if a component has a configurable title and body.
|
||||
|
||||
|
|
@ -233,7 +235,7 @@ m(Header, [
|
|||
])
|
||||
```
|
||||
|
||||
The component above makes different children look different based on where they appear in the array. It's difficult to understand the component without reading its implementation. Instead, use attributes as named parameters and reserve `children` for uniform child content:
|
||||
The component above breaks the assumption that children will be output in the same contiguous format as they are received. It's difficult to understand the component without reading its implementation. Instead, use attributes as named parameters and reserve `children` for uniform child content:
|
||||
|
||||
```javascript
|
||||
// PREFER
|
||||
|
|
@ -261,7 +263,9 @@ m(BetterHeader, {
|
|||
})
|
||||
```
|
||||
|
||||
#### Avoid component factories
|
||||
#### Define components statically, call them dynamically
|
||||
|
||||
##### Avoid creating component definitions inside views
|
||||
|
||||
If you create a component from within a `view` method (either directly inline or by calling a function that does so), each redraw will have a different clone of the component. When diffing component vnodes, if the component referenced by the new vnode is not strictly equal to the one referenced by the old component, the two are assumed to be different components even if they ultimately run equivalent code. This means components created dynamically via a factory will always be re-created from scratch.
|
||||
|
||||
|
|
@ -291,3 +295,65 @@ m.render(document.body, m(Component, {greeting: "hello"}))
|
|||
// calling a second time does not modify DOM
|
||||
m.render(document.body, m(Component, {greeting: "hello"}))
|
||||
```
|
||||
|
||||
##### Avoid creating component instances outside views
|
||||
|
||||
Conversely, for similar reasons, if a component instance is created outside of a view, future redraws will perform an equality check on the node and skip it. Therefore component instances should always be created inside views:
|
||||
|
||||
```javascript
|
||||
// AVOID
|
||||
var Counter = {
|
||||
count: 0,
|
||||
view: function(vnode) {
|
||||
return m("div",
|
||||
m("p", "Count: " + vnode.state.count ),
|
||||
|
||||
m("button", {
|
||||
onclick: function() {
|
||||
vnode.state.count++
|
||||
}
|
||||
}, "Increase count")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var counter = m(Counter)
|
||||
|
||||
m.mount(document.body, {
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m("h1", "My app"),
|
||||
counter
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, clicking the counter component button will increase its state count, but its view will not be triggered because the vnode representing the component shares the same reference, and therefore the render process doesn't diff them. You should always call components in the view to ensure a new vnode is created:
|
||||
|
||||
```javascript
|
||||
// PREFER
|
||||
var Counter = {
|
||||
count: 0,
|
||||
view: function(vnode) {
|
||||
return m("div",
|
||||
m("p", "Count: " + vnode.state.count ),
|
||||
|
||||
m("button", {
|
||||
onclick: function() {
|
||||
vnode.state.count++
|
||||
}
|
||||
}, "Increase count")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, {
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m("h1", "My app"),
|
||||
m(Counter)
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,21 +4,38 @@
|
|||
|
||||
## How do I go about contributing ideas or new features?
|
||||
|
||||
Create an issue to suggest it and discuss first. Avoid submitting large changes.
|
||||
Create an [issue thread on Github](https://github.com/lhorie/mithril.js/issues/new) to suggest your idea so the community can discuss it. And don't worry, we're nice :)
|
||||
|
||||
If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities.
|
||||
|
||||
|
||||
|
||||
## How should I report bugs?
|
||||
|
||||
Ideally, provide code to reproduce the issue (via jsfiddle, a gist, etc). Even better, submit a pull request with a fix and tests. If you don't know how to test your fix, or lint or whatever, submit anyways, and we can help you.
|
||||
Ideally, the best way to report bugs is to provide a small snippet of code where the issue can be reproduced (via jsfiddle, jsbin, a gist, etc). Even better would be to submit a pull request with a fix and tests. If you don't know how to test your fix, or lint or whatever, submit anyways, and we can help you.
|
||||
|
||||
|
||||
|
||||
## How do I run tests?
|
||||
## How do I send a pull request?
|
||||
|
||||
Assuming you have forked this repo, you can open the `index.html` file in a module's `tests` folder and look at console output to see only tests for that module, or you can run `ospec/bin/ospec` from the command line to run all tests under a Node.js environment. Additionally, you can modify a test to use `o.only(description, test)` instead of `o(description, test)` if you wish to run only a specific test.
|
||||
To send a pull request:
|
||||
|
||||
There is no need to `npm install` anything in order to run the test suite, however NodeJS is required to run the test suite from the command line.
|
||||
- fork the repo (button at the top right in Github)
|
||||
- clone the forked repo to your computer (green button in Github)
|
||||
- create a feature branch (run `git checkout -b the-feature-branch-name`)
|
||||
- make your changes
|
||||
- run the tests (run `npm t`)
|
||||
- submit a pull request (go to the pull requests tab in Github, click the green button and select your feature branch)
|
||||
|
||||
|
||||
|
||||
## I'm submitting a PR. How do I run tests?
|
||||
|
||||
Assuming you have forked this repo, you can open the `index.html` file in a module's `tests` folder and look at console output to see only tests for that module, or you can run `ospec/bin/ospec` from the command line to run all tests.
|
||||
|
||||
While testing, you can modify a test to use `o.only(description, test)` instead of `o(description, test)` if you wish to run only a specific test to speed up your debugging experience. Don't forget to remove the `.only` after you're done!
|
||||
|
||||
There is no need to `npm install` anything in order to run the test suite, however NodeJS is required to run the test suite from the command line. You do need to `npm install` if you want to lint or get a code coverage report though.
|
||||
|
||||
|
||||
|
||||
|
|
@ -26,7 +43,15 @@ There is no need to `npm install` anything in order to run the test suite, howev
|
|||
|
||||
If all you're trying to do is run examples in the codebase, you don't need to build Mithril, you can just open the various html files and things should just work.
|
||||
|
||||
To generate the bundled file, run `node bundler/bundler.js` from the command line. There is no need to `npm install` anything, but NodeJS is required to run the build script.
|
||||
To generate the bundled file for testing, run `npm run dev` from the command line. To generate the minified file, run `npm run build`. There is no need to `npm install` anything, but NodeJS is required to run the build scripts.
|
||||
|
||||
|
||||
|
||||
## Is there a style guide?
|
||||
|
||||
Yes, there's an `eslint` configuration, but it's not strict about formatting at all. If your contribution passes `npm run lint`, it's good enough for a PR (and it can still be accepted even if it doesn't pass).
|
||||
|
||||
Spacing and formatting inconsistencies may be fixed after the fact, and we don't want that kind of stuff getting in the way of contributing.
|
||||
|
||||
|
||||
|
||||
|
|
@ -40,11 +65,11 @@ Another important reason is that it allows us to document browser API quirks via
|
|||
|
||||
## Why does Mithril use its own testing framework and not Mocha/Jasmine/Tape?
|
||||
|
||||
Mainly to avoid requiring dependencies. ospec is customized to provide only essential information for common testing workflows (namely, no spamming ok's on pass, and accurate noiseless errors on failure)
|
||||
Mainly to avoid requiring dependencies. `ospec` is customized to provide only essential information for common testing workflows (namely, no spamming ok's on pass, and accurate noiseless errors on failure)
|
||||
|
||||
|
||||
|
||||
## Why do tests and examples use `module/module.js`? Why not use Browserify, Webpack or Rollup?
|
||||
## Why do tests use `module/module.js`? Why not use Browserify, Webpack or Rollup?
|
||||
|
||||
Again, to avoid requiring dependencies. The Mithril codebase is written using a statically analyzable subset of CommonJS module definitions (as opposed to ES6 modules) because its syntax is backwards compatible with ES5, therefore making it possible to run source code unmodified in browsers without the need for a build tool or a file watcher.
|
||||
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
# Examples
|
||||
|
||||
Here are some examples of Mithril in action
|
||||
|
||||
- [Animation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/animation/mosaic.html)
|
||||
- [DBMonster](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html)
|
||||
- [Markdown Editor](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/editor/index.html)
|
||||
- SVG: [Clock](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/tiger.html)
|
||||
- [ThreadItJS](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/threaditjs/index.html)
|
||||
- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html)
|
||||
- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
# fragment(html)
|
||||
# fragment(attrs, children)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Generates a trusted HTML [vnode](vnodes.md)
|
||||
Allows attaching lifecycle methods to a fragment [vnode](vnodes.md)
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
Generates a fragment [vnode](vnodes.md)
|
||||
|
||||
`vnode = m.fragment(attrs, children)`
|
||||
|
||||
|
|
|
|||
53
docs/generate.js
Normal file
53
docs/generate.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
var marked = require("marked")
|
||||
var layout = fs.readFileSync("./docs/layout.html", "utf-8")
|
||||
var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version
|
||||
try {fs.mkdirSync("docs/archive/")} catch (e) {}
|
||||
try {fs.mkdirSync("docs/archive/" + version)} catch (e) {}
|
||||
try {fs.mkdirSync("docs/archive/" + version + "/lib")} catch (e) {}
|
||||
try {fs.mkdirSync("docs/archive/" + version + "/lib/prism")} catch (e) {}
|
||||
|
||||
var guides = fs.readFileSync("docs/guides.md", "utf-8")
|
||||
var methods = fs.readFileSync("docs/methods.md", "utf-8")
|
||||
|
||||
generate("docs")
|
||||
|
||||
function generate(pathname) {
|
||||
if (fs.lstatSync(pathname).isDirectory()) {
|
||||
fs.readdirSync(pathname).forEach(function(filename) {
|
||||
generate(pathname + "/" + filename)
|
||||
})
|
||||
}
|
||||
else if (!pathname.match(/tutorials|archive/)) {
|
||||
if (pathname.match(/\.md$/)) {
|
||||
var outputFilename = pathname.replace(/\.md$/, ".html")
|
||||
var markdown = fs.readFileSync(pathname, "utf-8")
|
||||
var fixed = markdown
|
||||
.replace(/(`[^`]+?)<(.*`)/gim, "$1<$2") // fix generic syntax
|
||||
.replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags
|
||||
return "<code>" + (a + b + c).replace(/\|/g, "|") + "</code>"
|
||||
})
|
||||
.replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav, space) { // inject menu
|
||||
var file = path.basename(pathname)
|
||||
var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))")
|
||||
var replace = function(match, space, li, link) {
|
||||
return space + li + "**" + link + "**" + (nav ? "\n" + nav.replace(/(^|\n)/g, "$1\t" + space) : "")
|
||||
}
|
||||
var modified = guides.match(link) ? guides.replace(link, replace) : methods.replace(link, replace)
|
||||
return title + modified + "\n\n"
|
||||
})
|
||||
.replace(/\.md/gim, ".html") // fix links
|
||||
var html = layout
|
||||
.replace(/\[body\]/, marked(fixed))
|
||||
.replace(/<h5 id="([^"]+?)">([^<]+?)<\/h5>/gim, function(match, id, text) { // fix anchors
|
||||
return "<h5 id=\"" + text.toLowerCase().replace(/\.|\[|\]|"|\//g, "") + "\">" + text + "</h5>"
|
||||
})
|
||||
fs.writeFileSync("docs/archive/" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
}
|
||||
else {
|
||||
fs.writeFileSync("docs/archive/" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
docs/guides.md
Normal file
17
docs/guides.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
- Tutorials
|
||||
- [Installation](installation.md)
|
||||
- [Introduction](introduction.md)
|
||||
- [Tutorial](tutorial.md)
|
||||
- [Testing](testing.md)
|
||||
- [Examples](examples.md)
|
||||
- Key concepts
|
||||
- [Vnodes](vnodes.md)
|
||||
- [Components](components.md)
|
||||
- [Lifecycle methods](lifecycle-methods.md)
|
||||
- [Keys](keys.md)
|
||||
- Social
|
||||
- [Community chat](https://gitter.im/lhorie/mithril.js)
|
||||
- [Contributing](contributing.md)
|
||||
- [Credits](credits.md)
|
||||
- Misc
|
||||
- [Change log/Migration](change-log.md)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# m(selector, attributes, children)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Flexibility](#flexibility)
|
||||
- [CSS selectors](#css-selectors)
|
||||
|
|
@ -17,13 +18,31 @@
|
|||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Represents an HTML element in a Mithril view
|
||||
|
||||
```javascript
|
||||
m("div", {class: "foo"}, "hello")
|
||||
// represents <div class="foo">hello</div>
|
||||
```
|
||||
|
||||
You can also [use HTML syntax](https://babeljs.io/repl/#?code=%2F**%20%40jsx%20m%20*%2F%0A%3Ch1%3EMy%20first%20app%3C%2Fh1%3E) via a Babel plugin.
|
||||
|
||||
```markup
|
||||
/** jsx m */
|
||||
<div class="foo">hello</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`vnode = m(selector, attributes, children)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------ | ------------------------------------------ | -------- | ---
|
||||
`selector` | `String|Object` | Yes | A CSS selector or a [component](https://github.com/lhorie/mithril.js/blob/rewrite/docs/components.md)
|
||||
`selector` | `String|Object` | Yes | A CSS selector or a [component](components.md)
|
||||
`attributes` | `Object` | No | HTML attributes or element properties
|
||||
`children` | `Array<Vnode>|String|Number|Boolean` | No | Child [vnodes](vnodes.md#structure). Can be written as [splat arguments](signatures.md#splats)
|
||||
**returns** | `Vnode` | | A [vnode](vnodes.md#structure)
|
||||
|
|
@ -408,3 +427,9 @@ var BetterListComponent = {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Avoid creating vnodes outside views
|
||||
|
||||
When a redraw encounters a vnode which is strictly equal to the one in the previous render, it will be skipped and its contents will not be updated. While this may seem like an opportunity for performance optimisation, it should be avoided because it prevents dynamic changes in that node's tree - this leads to side-effects such as downstream lifecycle methods failing to trigger on redraw. In this sense, Mithril vnodes are immutable: new vnodes are compared to old ones; mutations to vnodes are not persisted.
|
||||
|
||||
The component documentation contains [more detail and an example of this anti-pattern](components.md#avoid-creating-component-instances-outside-views).
|
||||
|
|
|
|||
|
|
@ -1,32 +1,52 @@
|
|||
# Installation
|
||||
|
||||
### CDN
|
||||
|
||||
If you're new to Javascript or just want a very simple setup to get your feet wet, you can get Mithril from a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network):
|
||||
|
||||
```markup
|
||||
<script src="http://cdn.rawgit.com/lhorie/mithril.js/rewrite/mithril.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### NPM
|
||||
|
||||
#### Quick start
|
||||
|
||||
```
|
||||
#install
|
||||
```bash
|
||||
# 1) install
|
||||
npm install mithril@rewrite --save
|
||||
|
||||
# add this line into the scripts section in package.json
|
||||
# 2) add this line into the scripts section in package.json
|
||||
# "scripts": {
|
||||
# "build": "bundle index.js --output app.js --watch"
|
||||
# }
|
||||
|
||||
# create an `index.js` file
|
||||
# 3) create an `index.js` file
|
||||
|
||||
# run bundler
|
||||
# 4) run bundler
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Step by step
|
||||
|
||||
For production-level projects, the recommended way of installing Mithril is to use NPM.
|
||||
|
||||
NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the Javascript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well.
|
||||
|
||||
To use Mithril via NPM:
|
||||
To use Mithril via NPM, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`.
|
||||
|
||||
- go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`.
|
||||
- run `npm install mithril@rewrite --save`. This will create a folder called `node_modules`, and a `mithril` folder inside of it. It will also add an entry under `dependencies` in the `package.json` file
|
||||
```bash
|
||||
npm init --yes
|
||||
# creates a file called package.json
|
||||
```
|
||||
|
||||
Then, run `npm install mithril@rewrite --save` to install Mithril. This will create a folder called `node_modules`, and a `mithril` folder inside of it. It will also add an entry under `dependencies` in the `package.json` file
|
||||
|
||||
```bash
|
||||
npm install mithril@rewrite --save
|
||||
```
|
||||
|
||||
You are now ready to start using Mithril. The recommended way to structure code is to modularize it via CommonJS modules:
|
||||
|
||||
|
|
@ -129,13 +149,6 @@ webpack --watch
|
|||
|
||||
If you don't have the ability to run a bundler script due to company security policies, there's an options to not use a module system at all:
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
|
||||
// if a CommonJS environment is not detected, Mithril will be created in the global scope
|
||||
m.render(document.body, "hello world")
|
||||
```
|
||||
|
||||
```markup
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -147,3 +160,10 @@ m.render(document.body, "hello world")
|
|||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
|
||||
// if a CommonJS environment is not detected, Mithril will be created in the global scope
|
||||
m.render(document.body, "hello world")
|
||||
```
|
||||
|
|
|
|||
238
docs/introduction.md
Normal file
238
docs/introduction.md
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# Introduction
|
||||
|
||||
- [What is Mithril?](#what-is-mithril)
|
||||
- [Getting started](#getting-started)
|
||||
- [Hello world](#hello-world)
|
||||
- [DOM elements](#dom-elements)
|
||||
- [Components](#components)
|
||||
- [Routing](#routing)
|
||||
- [XHR](#xhr)
|
||||
|
||||
---
|
||||
|
||||
### What is Mithril?
|
||||
|
||||
Mithril is a framework for building Single Page Applications. It's small but batteries-included.
|
||||
|
||||
---
|
||||
|
||||
### Getting started
|
||||
|
||||
The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface but it'll only take 10 minutes.
|
||||
|
||||
Let's create an HTML file to follow along:
|
||||
|
||||
```markup
|
||||
<body></body>
|
||||
<script src="http://cdn.rawgit.com/lhorie/mithril.js/rewrite/mithril.js"></script>
|
||||
<script>
|
||||
var root = document.body
|
||||
|
||||
// your code goes here!
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hello world
|
||||
|
||||
Let's start as small as well can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
|
||||
|
||||
```javascript
|
||||
var root = document.body
|
||||
|
||||
m.render(root, "Hello world")
|
||||
```
|
||||
|
||||
Now, let's change the text to something else. Add this line of code under the previous one:
|
||||
|
||||
```javascript
|
||||
m.render(root, "My first app")
|
||||
```
|
||||
|
||||
As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch.
|
||||
|
||||
---
|
||||
|
||||
### DOM elements
|
||||
|
||||
Let's wrap our text in an `<h1>` tag.
|
||||
|
||||
```javascript
|
||||
m.render(root, m("h1", "My first app"))
|
||||
```
|
||||
|
||||
The `m()` function can be used to describe any HTML structure you want. So if you to add a class to the `<h1>`:
|
||||
|
||||
```javascript
|
||||
m("h1", {class: "title"}, "My first app")
|
||||
```
|
||||
|
||||
If you want to have multiple elements:
|
||||
|
||||
```javascript
|
||||
[
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
]
|
||||
```
|
||||
|
||||
And so on:
|
||||
|
||||
```javascript
|
||||
m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
])
|
||||
```
|
||||
|
||||
Note: If you prefer `<html>` syntax, [it's possible via Babel](https://babeljs.io/repl/#?code=%2F**%20%40jsx%20m%20*%2F%0A%3Ch1%3EMy%20first%20app%3C%2Fh1%3E).
|
||||
|
||||
```markup
|
||||
// HTML syntax via Babel's JSX plugin
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
<button>A button</button>
|
||||
</main>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Components
|
||||
|
||||
A Mithril component is just an object with a `view` function. Here's the code above as a component:
|
||||
|
||||
```javascript
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To activate the component, we use `m.mount`.
|
||||
|
||||
```javascript
|
||||
m.mount(root, Hello)
|
||||
```
|
||||
|
||||
As you would expect, doing so creates this markup:
|
||||
|
||||
```markup
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
<button>A button</button>
|
||||
</main>
|
||||
```
|
||||
|
||||
The `m.mount` function is similar to `m.render`, but instead of rendering some HTML only once, it activates Mithril's auto-redrawing system. To understand what that means, let's add some events:
|
||||
|
||||
```javascript
|
||||
var count = 0 // added a variable
|
||||
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", {onclick: function() {count++}}, count + " clicks"), // changed this line
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(root, Hello)
|
||||
```
|
||||
|
||||
We defined an `onclick` event on the button, which increments a variable `count` (which was declared at the top). We are now also rendering the value of that variable in the button label.
|
||||
|
||||
You can now update the label of the button by clicking the button. Since we used `m.mount`, you don't need to manually call `m.render` to apply the changes in the `count` variable to the HTML; Mithril does it for you.
|
||||
|
||||
If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates.
|
||||
|
||||
---
|
||||
|
||||
### Routing
|
||||
|
||||
Routing just means going from one screen to another in an application with several screens.
|
||||
|
||||
Let's add a splash page that appears before our click counter. First we create a component for it:
|
||||
|
||||
```javascript
|
||||
var Splash = {
|
||||
view: function() {
|
||||
return m("a", {href: "#!/hello"}, "Enter!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, this component simply renders a link to `#!/hello`. The `#!` part is known as a hashbang, and it's a common convention used in Single Page Applications to indicate that the stuff after it (the `/hello` part) is a route path.
|
||||
|
||||
Now that we going to have more than one screen, we use `m.route` instead of `m.mount`.
|
||||
|
||||
```javascript
|
||||
m.route(root, "/splash", {
|
||||
"/splash": Splash,
|
||||
"/hello": Hello,
|
||||
})
|
||||
```
|
||||
|
||||
The `m.route` function still has the same auto-redrawing functionality that `m.mount` does, and it also enables URL awareness; in other words, it lets Mithril know what to do when it sees a `#!` in the URL.
|
||||
|
||||
The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril redirects to the default route. So if you open the page in a browser and your URL is `http://localhost`, then you get redirected to `http://localhost/#!/splash`.
|
||||
|
||||
Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button.
|
||||
|
||||
---
|
||||
|
||||
### XHR
|
||||
|
||||
Basically, XHR is just a way to talk to a server.
|
||||
|
||||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial.
|
||||
|
||||
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `useCredentials` means to enable cookies (a requirement for the REM API to work)
|
||||
|
||||
```javascript
|
||||
var count = 0
|
||||
var increment = function() {
|
||||
m.request({
|
||||
method: "PUT",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/tutorial/1",
|
||||
data: {count: count + 1},
|
||||
useCredentials: true,
|
||||
})
|
||||
.then(function(data) {
|
||||
count = parseInt(data.count)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Calling the increment function [upserts](https://en.wiktionary.org/wiki/upsert) an object `{count: 1}` to the `/api/tutorial/1` endpoint. This endpoint returns an object with the same `count` value that was sent to it. Notice that the `count` variable is only updated after the request completes, and it's updated with the response value from the server now.
|
||||
|
||||
Let's replace the event handler in the component to call the `increment` function instead of incrementing the `count` variable directly:
|
||||
|
||||
```javascript
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", {onclick: increment}, count + " clicks"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking the button should now update the count.
|
||||
|
||||
---
|
||||
|
||||
We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR.
|
||||
|
||||
This should be enough to get you started writing the frontend for a real application. Now that you are comfortable with the basics of the Mithril API, [be sure to check out the simple application tutorial](simple-application.md), which walks you through building a realistic application.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,19 +1,38 @@
|
|||
# jsonp(options)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Typical usage](#typical-usage)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
`promise = m.jsonp(options)`
|
||||
Makes JSON-P requests. Typically, it's useful to interact with servers that allow JSON-P but that don't have CORS enabled.
|
||||
|
||||
```javascript
|
||||
m.jsonp({
|
||||
url: "/api/v1/users/:id",
|
||||
data: {id: 1},
|
||||
callbackKey: "callback",
|
||||
})
|
||||
.then(function(result) {
|
||||
console.log(result)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`promise = m.jsonp([url,] options)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
---------------------- | --------------------------------- | -------- | ---
|
||||
`url` | `String` | No | If present, it's equivalent to having the option `{url: url}`. Values passed to the `options` argument override options set via this shorthand.
|
||||
`options.url` | `String` | Yes | The URL to send the request to. The URL may be either absolute or relative, and it may contain [interpolations](#dynamic-urls).
|
||||
`options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring (for GET requests) or body (for other types of requests).
|
||||
`options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring.
|
||||
`options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
|
||||
`options.callbackName` | `String` | No | The name of the function that will be called as the callback. Defaults to a randomized string (e.g. `_mithril_6888197422121285_0({a: 1})`
|
||||
`options.callbackKey` | `String` | No | The name of the querystring parameter name that specifies the callback name. Defaults to `callback` (e.g. `/someapi?callback=_mithril_6888197422121285_0`)
|
||||
|
|
|
|||
29
docs/layout.html
Normal file
29
docs/layout.html
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Mithril.js</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css' />
|
||||
<link href="lib/prism/prism.css" rel="stylesheet" />
|
||||
<link href="style.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<section>
|
||||
<h1>Mithril</h1>
|
||||
<nav>
|
||||
<a href="introduction.html">Guide</a>
|
||||
<a href="api.html">API</a>
|
||||
<a href="https://github.com/lhorie/mithril.js">Github</a>
|
||||
</nav>
|
||||
</section>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
[body]
|
||||
<hr />
|
||||
<small>License: MIT. © Leo Horie.</small>
|
||||
</section>
|
||||
</main>
|
||||
<script src="lib/prism/prism.js"></script>
|
||||
</body>
|
||||
</html
|
||||
6
docs/lib/prism/prism.css
Normal file
6
docs/lib/prism/prism.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.token.comment,.token.prolog,.token.doctype,.token.cdata {color:#888;}
|
||||
.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol {color:#905;}
|
||||
.token.selector,.token.attr-name,.token.string,.token.builtin {color:#690;}
|
||||
.token.atrule,.token.attr-value,.token.punctuation,.token.keyword {color:#1e5799;}
|
||||
.token.regex,.token.important {color:#e90;}
|
||||
|
||||
9
docs/lib/prism/prism.js
Normal file
9
docs/lib/prism/prism.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Prism: Lightweight, robust, elegant syntax highlighting
|
||||
* MIT license http://www.opensource.org/licenses/mit-license.php/
|
||||
* @author Lea Verou http://lea.verou.me
|
||||
*/(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ");var l={element:r,language:o,grammar:u,code:f};t.hooks.run("before-highlight",l);if(i&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){l.highlightedCode=n.stringify(JSON.parse(e.data),o);t.hooks.run("before-insert",l);l.element.innerHTML=l.highlightedCode;s&&s.call(l.element);t.hooks.run("after-highlight",l)};c.postMessage(JSON.stringify({language:l.language,code:l.code}))}else{l.highlightedCode=t.highlight(l.code,l.grammar,l.language);t.hooks.run("before-insert",l);l.element.innerHTML=l.highlightedCode;s&&s.call(r);t.hooks.run("after-highlight",l)}},highlight:function(e,r,i){return n.stringify(t.tokenize(e,r),i)},tokenize:function(e,n,r){var i=t.Token,s=[e],o=n.rest;if(o){for(var u in o)n[u]=o[u];delete n.rest}e:for(var u in n){if(!n.hasOwnProperty(u)||!n[u])continue;var a=n[u],f=a.inside,l=!!a.lookbehind,c=0;a=a.pattern||a;for(var h=0;h<s.length;h++){var p=s[h];if(s.length>e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+"</"+s.tag+">"};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();;
|
||||
Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});;
|
||||
Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};
|
||||
;
|
||||
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});;
|
||||
|
|
@ -86,6 +86,9 @@ function initMocks() {
|
|||
"GET /api/v1/todos": function(request) {
|
||||
return {status: 200, responseText: JSON.stringify([])}
|
||||
},
|
||||
"PUT /api/v1/users/1": function(request) {
|
||||
return {status: 200, responseText: request.query.callback ? request.query.callback + "([])" : "[]"}
|
||||
},
|
||||
"POST /api/v1/upload": function(request) {
|
||||
return {status: 200, responseText: JSON.stringify([])}
|
||||
},
|
||||
|
|
@ -132,11 +135,11 @@ function traverseDirectory(pathname, callback) {
|
|||
|
||||
//run
|
||||
traverseDirectory("./docs", function(pathname) {
|
||||
if (pathname.indexOf(".md") > -1 && !pathname.match(/migration|zero|simple|node_modules/)) {
|
||||
if (pathname.indexOf(".md") > -1 && !pathname.match(/change-log|node_modules/)) {
|
||||
fs.readFile(pathname, "utf8", function(err, data) {
|
||||
if (err) console.log(err)
|
||||
else lint(pathname, data)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
.then(process.exit)
|
||||
|
|
|
|||
20
docs/methods.md
Normal file
20
docs/methods.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
- Core
|
||||
- [m](hyperscript.md)
|
||||
- [m.render](render.md)
|
||||
- [m.mount](mount.md)
|
||||
- [m.route](route.md)
|
||||
- [m.request](request.md)
|
||||
- [m.jsonp](jsonp.md)
|
||||
- [m.parseQueryString](parseQueryString.md)
|
||||
- [m.buildQueryString](buildQueryString.md)
|
||||
- [m.withAttr](withAttr.md)
|
||||
- [m.trust](trust.md)
|
||||
- [m.fragment](fragment.md)
|
||||
- [m.redraw](redraw.md)
|
||||
- [m.version](version.md)
|
||||
- [Promise](promise.md)
|
||||
- Optional
|
||||
- [Stream](stream.md)
|
||||
- Tooling
|
||||
- [Bundler](bundler.md)
|
||||
- [Ospec](ospec.md)
|
||||
|
|
@ -1,13 +1,35 @@
|
|||
# mount(root, component)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Performance considerations](#performance-considerations)
|
||||
- [Differences from m.render](#differences-from-m-render)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Activates a component, enabling it to autoredraw on user events
|
||||
|
||||
```javascript
|
||||
var state = {
|
||||
count: 0,
|
||||
inc: function() {state.count++}
|
||||
}
|
||||
|
||||
var Counter = {
|
||||
view: function() {
|
||||
return m("div", {onclick: state.inc}, state.count)
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, Counter)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`m.mount(element, component)`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
# parseQueryString(string)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Turns a string of the form `?a=1&b=2` to an object
|
||||
|
||||
```javascript
|
||||
var object = m.parseQueryString("a=1&b=2")
|
||||
// {a: "1", b: "2"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`object = m.parseQueryString(string)`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,34 @@
|
|||
# Promise(executor)
|
||||
|
||||
- [API](#api)
|
||||
- [Static members](#static-members)
|
||||
- [Promise.resolve](#promiseresolve)
|
||||
- [Promise.reject](#promisereject)
|
||||
- [Promise.all](#promiseall)
|
||||
- [Promise.race](#promiserace)
|
||||
- [Instance members](#static-members)
|
||||
- [promise.then](#promisethen)
|
||||
- [promise.catch](#promisecatch)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [Static members](#static-members)
|
||||
- [Promise.resolve](#promiseresolve)
|
||||
- [Promise.reject](#promisereject)
|
||||
- [Promise.all](#promiseall)
|
||||
- [Promise.race](#promiserace)
|
||||
- [Instance members](#instance-members)
|
||||
- [promise.then](#promisethen)
|
||||
- [promise.catch](#promisecatch)
|
||||
- [How it works](#how-it-works)
|
||||
- [Promise chaining](#promise-chaining)
|
||||
- [Promise absorption](#promise-absorption)
|
||||
- [Error handling](#error-handling)
|
||||
- [Shorthands](#shorthands)
|
||||
- [Waiting for multiple promises](#waiting-for-multiple-promises)
|
||||
- [Multiple promises](#multiple-promises)
|
||||
- [Why not callbacks](#why-not-callbacks)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
A [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) polyfill.
|
||||
|
||||
A Promise is a mechanism for working with asynchronous computations.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`promise = new Promise(executor)`
|
||||
|
||||
|
|
@ -146,7 +156,7 @@ promise.then(function(value) {
|
|||
})
|
||||
```
|
||||
|
||||
Promises are useful for working with [asynchronous](https://en.wikipedia.org/wiki/Asynchrony_(computer_programming)) APIs, such as [`m.request`](request.md)
|
||||
Promises are useful for working with asynchronous APIs, such as [`m.request`](request.md)
|
||||
|
||||
Asynchronous APIs are those which typically take a long time to run, and therefore would take too long to return a value using the `return` statement of a function. Instead, they do their work in the background, allowing other Javascript code to run in the meantime. When they are done, they call a function with their results.
|
||||
|
||||
|
|
@ -262,7 +272,7 @@ promise
|
|||
|
||||
---
|
||||
|
||||
### Waiting for multiple promises
|
||||
### Multiple promises
|
||||
|
||||
In some occasions, you may need to make HTTP requests in parallel, and run code after all requests complete. This can be accomplished by `Promise.all`
|
||||
|
||||
|
|
@ -287,3 +297,13 @@ Promise.all([
|
|||
In the example above, there are two user searches happening in parallel. Once they both complete, we take the names of all the users and alert them.
|
||||
|
||||
This example also illustrates another benefit of smaller functions: we reused the `getUserNames` function we had created above.
|
||||
|
||||
---
|
||||
|
||||
### Why not callbacks
|
||||
|
||||
Callbacks are another mechanism for working with asynchrounous computations, and are indeed more adequate to use if an asynchronous computation may occur more than one time (for example, an `onscroll` event handler).
|
||||
|
||||
However, for asynchronous computations that only occur once in response to an action, promises can be refactored more effectively, reducing code smells known as pyramids of doom (deeply nested series of callbacks with unmanaged state being used across several closure levels).
|
||||
|
||||
In addition, promises can considerably reduce boilerplate related to error handling.
|
||||
|
|
@ -1,11 +1,22 @@
|
|||
# redraw()
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Updates the DOM after a change in the application data layer.
|
||||
|
||||
You DON'T need to call it if data is modified within the execution context of an event handler defined in a Mithril view, or after request completion when using `m.request`/`m.jsonp`.
|
||||
|
||||
You DO need to call it in `setTimeout`/`setInterval`/`requestAnimationFrame` callbacks, or callbacks from 3rd party libraries.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`m.redraw()`
|
||||
|
||||
|
|
@ -19,6 +30,6 @@ Argument | Type | Required | Description
|
|||
|
||||
When callbacks outside of Mithril run, you need to notify Mithril's rendering engine that a redraw is needed. External callbacks could be `setTimeout`/`setInterval`/`requestAnimationFrame` callbacks, web socket library callbacks, event handlers in jQuery plugins, third party XHR request callbacks, etc.
|
||||
|
||||
To trigger a redraw, call `m.redraw()`
|
||||
To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you used `m.mount` or `m.route`. If you rendered via `m.render`, you should use `m.render` to redraw.
|
||||
|
||||
You should not call m.redraw from a [lifecycle method](lifecycle-methods.md). Doing so will result in undefined behavior.
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# render(element, vnodes)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Why Virtual DOM](#why-virtual-dom)
|
||||
- [Differences from other API methods](#differences-from-other-api-methods)
|
||||
|
|
@ -8,7 +9,18 @@
|
|||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Renders a template to the DOM
|
||||
|
||||
```javascript
|
||||
m.render(document.body, "hello")
|
||||
// <body>hello</body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`m.render(element, vnodes)`
|
||||
|
||||
|
|
@ -52,6 +64,8 @@ Another difference is that `m.render` method expects a [vnode](vnodes.md) (or a
|
|||
|
||||
### Standalone usage
|
||||
|
||||
`var render = require("mithril/render")`
|
||||
|
||||
The `m.render` module is similar in scope to view libraries like Knockout, React and Vue. It is approximately 500 lines of code (3kb min+gzip) and implements a virtual DOM diffing engine with a modern search space reduction algorithm and DOM recycling, which translate to top-of-class performance, both in terms of initial page load and re-rendering. It has no dependencies on other parts of Mithril and can be used as a standalone library.
|
||||
|
||||
Despite being incredibly small, the render module is fully functional and self-suficient. It supports everything you might expect: SVG, custom elements, and all valid attributes and events - without any weird case-sensitive edge cases or exceptions. Of course, it also fully supports [components](components.md) and [lifecycle methods](lifecycle-methods.md).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# request(options)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Typical usage](#typical-usage)
|
||||
- [Loading icons and error messages](#loading-icons-and-error-messages)
|
||||
|
|
@ -10,12 +11,31 @@
|
|||
- [Monitoring progress](#monitoring-progress)
|
||||
- [Casting response to a type](#casting-response-to-a-type)
|
||||
- [Non-JSON responses](#non-json-responses)
|
||||
- [Retrieving response details](#retrieving-response-details)
|
||||
- [Why JSON instead of HTML](#why-json-instead-of-html)
|
||||
- [Why XMLHttpRequest instead of fetch](#why-xmlhttprequest-instead-of-fetch)
|
||||
- [Why XHR instead of fetch](#why-xhr-instead-of-fetch)
|
||||
- [Avoid anti-patterns](#avoid-anti-patterns)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Makes XHR (aka AJAX) requests, and returns a [promise](promise.md)
|
||||
|
||||
```javascript
|
||||
m.request({
|
||||
method: "PUT",
|
||||
url: "/api/v1/users/:id",
|
||||
data: {id: 1, name: "test"}
|
||||
})
|
||||
.then(function(result) {
|
||||
console.log(result)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`promise = m.request([url,] options)`
|
||||
|
||||
|
|
@ -35,6 +55,7 @@ Argument | Type | Required | Descr
|
|||
`options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the response. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses.
|
||||
`options.extract` | `string = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for reading response headers and cookies. Defaults to a function that returns `xhr.responseText`. If defined, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. If a custom `extract` callback is set, `options.deserialize` is ignored and the string returned from the extract callback will not be parsed as JSON.
|
||||
`options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods.
|
||||
`options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`.
|
||||
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
|
@ -421,7 +442,7 @@ Data services may be organized in many different ways depending on the nature of
|
|||
|
||||
---
|
||||
|
||||
### Why XMLHttpRequest instead of fetch
|
||||
### Why XHR instead of fetch
|
||||
|
||||
[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is a newer Web API for fetching resources from servers, similar to `XMLHttpRequest`.
|
||||
|
||||
|
|
@ -430,11 +451,13 @@ Mithril's `m.request` uses `XMLHttpRequest` instead of `fetch()` for a number of
|
|||
- `fetch` is not fully standardized yet, and may be subject to specification changes.
|
||||
- `XMLHttpRequest` calls can be aborted before they resolve (e.g. to avoid race conditions in for instant search UIs).
|
||||
- `XMLHttpRequest` provides hooks for progress listeners for long running requests (e.g. file uploads).
|
||||
- `XMLHttpRequest` is supported by all browsers, whereas `fetch()` is not supported by Internet Explorer and Safari.
|
||||
- `XMLHttpRequest` is supported by all browsers, whereas `fetch()` is not supported by Internet Explorer, Safari and Android (non-Chromium).
|
||||
|
||||
Currently, due to lack of browser support, `fetch()` typically requires a [polyfill](https://github.com/github/fetch), which is over 11kb uncompressed - nearly three times larger than Mithril's `m.request`.
|
||||
Currently, due to lack of browser support, `fetch()` typically requires a [polyfill](https://github.com/github/fetch), which is over 11kb uncompressed - nearly three times larger than Mithril's XHR module.
|
||||
|
||||
Despite being much smaller, `m.request` supports many important and not-so-trivial-to-implement features like [URL interpolation](#dynamic-urls), querystring serialization and [JSON-P requests](jsonp.md). The `fetch` polyfill does not support any of those.
|
||||
Despite being much smaller, Mithril's XHR module supports many important and not-so-trivial-to-implement features like [URL interpolation](#dynamic-urls), querystring serialization and [JSON-P requests](jsonp.md), in addition to its ability to integrate seamlessly to Mithril's autoredrawing subsystem. The `fetch` polyfill does not support any of those, and requires extra libraries and boilerplates to achieve the same level of functionality.
|
||||
|
||||
In addition, Mithril's XHR module is optimized for JSON-based endpoints and makes that most common case appropriately terse - i.e. `m.request(url)` - whereas `fetch` requires an additional explicit step to parse the response data as JSON: `fetch(url).then(function(response) {return response.json()})`
|
||||
|
||||
The `fetch()` API does have a few technical advantages over `XMLHttpRequest` in a few uncommon cases:
|
||||
|
||||
|
|
@ -442,3 +465,24 @@ The `fetch()` API does have a few technical advantages over `XMLHttpRequest` in
|
|||
- it integrates to the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), which provides an extra layer of control over how and when network requests happen. This API also allows access to push notifications and background synchronization features.
|
||||
|
||||
In typical scenarios, streaming won't provide noticeable performance benefits because it's generally not advisable to download megabytes of data to begin with. Also, the memory gains from repeatedly reusing small buffers may be offset or nullified if they result in excessive browser repaints. For those reasons, choosing `fetch()` streaming instead of `m.request` is only recommended for extremely resource intensive applications.
|
||||
|
||||
---
|
||||
|
||||
### Avoid anti-patterns
|
||||
|
||||
#### Promises are not the response data
|
||||
|
||||
The `m.request` method returns a [Promise](promise.md), not the response data itself. It cannot return that data directly because an HTTP request may take a long time to complete (due to network latency), and if Javascript waited for it, it would freeze the application until the data was available.
|
||||
|
||||
```javascript
|
||||
// AVOID
|
||||
var users = m.request("/api/v1/users")
|
||||
console.log("list of users:", users)
|
||||
// `users` is NOT a list of users, it's a promise
|
||||
|
||||
// PREFER
|
||||
m.request("/api/v1/users").then(function(users) {
|
||||
console.log("list of users:", users)
|
||||
})
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
# route(root, defaultRoute, routes)
|
||||
|
||||
- [API](#api)
|
||||
- [Static members](#static-members)
|
||||
- [route.set](#routeset)
|
||||
- [route.get](#routeget)
|
||||
- [route.prefix](#routeprefix)
|
||||
- [route.link](#routelink)
|
||||
- [RouteResolver](#routeresolver)
|
||||
- [routeResolver.onmatch](#routeresolveronmatch)
|
||||
- [routeResolver.render](#routeresolverrender)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [Static members](#static-members)
|
||||
- [route.set](#routeset)
|
||||
- [route.get](#routeget)
|
||||
- [route.prefix](#routeprefix)
|
||||
- [route.link](#routelink)
|
||||
- [RouteResolver](#routeresolver)
|
||||
- [routeResolver.onmatch](#routeresolveronmatch)
|
||||
- [routeResolver.render](#routeresolverrender)
|
||||
- [How it works](#how-it-works)
|
||||
- [Typical usage](#typical-usage)
|
||||
- [Navigating to different routes](#navigating-to-different-routes)
|
||||
|
|
@ -21,7 +22,25 @@
|
|||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Navigate between "pages" within an application
|
||||
|
||||
```javascript
|
||||
var Home = {
|
||||
view: function() {
|
||||
return "Welcome"
|
||||
}
|
||||
}
|
||||
|
||||
m.route(document.body, "/home", {
|
||||
"/home": Home, // defines `http://localhost/#!/home`
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`m.route(root, defaultRoute, routes)`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
# stream()
|
||||
|
||||
- [API](#api)
|
||||
- [Static members](#static-members)
|
||||
- [stream.combine](#streamcombine)
|
||||
- [stream.merge](#streammerge)
|
||||
- [stream.HALT](#streamhalt)
|
||||
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||
- [Instance members](#static-members)
|
||||
- [stream.map](#streammap)
|
||||
- [stream.end](#streamend)
|
||||
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||
- [stream["fantasy-land/map"]](#streamfantasy-landmap)
|
||||
- [stream["fantasy-land/ap"]](#streamfantasy-landap)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [Static members](#static-members)
|
||||
- [stream.combine](#streamcombine)
|
||||
- [stream.merge](#streammerge)
|
||||
- [stream.HALT](#streamhalt)
|
||||
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||
- [Instance members](#static-members)
|
||||
- [stream.map](#streammap)
|
||||
- [stream.end](#streamend)
|
||||
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||
- [stream["fantasy-land/map"]](#streamfantasy-landmap)
|
||||
- [stream["fantasy-land/ap"]](#streamfantasy-landap)
|
||||
- [Basic usage](#basic-usage)
|
||||
- [Streams as variables](#streams-as-variables)
|
||||
- [Bidirectional bindings](#bidirectional-bindings)
|
||||
|
|
@ -25,7 +26,17 @@
|
|||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
A Stream is a reactive data structure, similar to cells in spreadsheet applications.
|
||||
|
||||
For example, in a spreadsheet, if `A1 = B1 + C1`, then changing the value of `B1` or `C1` automatically changes the value of `A1`.
|
||||
|
||||
Similarly, you can make a stream depend on other streams so that changing the value of one automatically updates the other. This is useful when you have very expensive computations and want to only run them when necessary, as opposed to, say, on every redraw.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
Creates a stream
|
||||
|
||||
|
|
|
|||
45
docs/style.css
Normal file
45
docs/style.css
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
body,table,h5 {font:normal 16px 'Open Sans';}
|
||||
header,main {margin:auto;max-width:1000px;}
|
||||
header section {position:absolute;width:250px;}
|
||||
nav a {border-left:1px solid #ddd;padding:0 10px;}
|
||||
nav a:first-child {border:0;padding-left:0;}
|
||||
main {margin-bottom:100px;}
|
||||
main section {margin-left:270px;}
|
||||
h1 {margin:0 0 15px;}
|
||||
h5 {font-style:italic;}
|
||||
pre,code {background:#eee;font-family:monospace;}
|
||||
pre {border-left:3px solid #1e5799;overflow:auto;padding:10px 20px;}
|
||||
code {border:1px solid #ddd;display:inline-block;margin:0 0 1px;padding:3px;white-space:pre;}
|
||||
pre code {border:0;margin:0;padding:0;}
|
||||
table {border-collapse:collapse;width:100%;}
|
||||
tbody tr:nth-child(odd) {background:#fafafa;}
|
||||
thead tr,tbody tr:nth-child(even) {background:#f3f3f3;}
|
||||
th {text-align:left;}
|
||||
th,td {padding:3px 10px;vertical-align:top;}
|
||||
a {color:#1e5799;display:inline-block;text-decoration:none;}
|
||||
a:hover {text-decoration:underline;}
|
||||
hr {border:0;border-bottom:1px solid #ddd;margin:30px 0;}
|
||||
|
||||
#signature + p code {padding:3px 10px;}
|
||||
h1 + ul {margin:40px 0 0 -270px;padding:0;position:absolute;width:250px;}
|
||||
h1 + ul + hr {display:none;}
|
||||
h1 + ul li {border-bottom:1px solid #eee;list-style:none;margin:0;padding:0;}
|
||||
h1 + ul li:last-child {border-bottom:0;}
|
||||
h1 + ul ul {margin:0 0 10px;padding:0 0 0 15px;}
|
||||
h1 + ul ul li {border:0;}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
main section {margin:0;}
|
||||
h1 + ul + hr {display:block;}
|
||||
header section,h1 + ul {margin:0 0 20px;position:static;width:auto;}
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
#signature + p + table,#signature + p + table tbody,#signature + p + table tr,#signature + p + table th,#signature + p + table td {display:block;}
|
||||
#signature + p + table thead {display:none;}
|
||||
#signature + p + table td:before {display:inline-block;font-style:italic;padding:0 10px 0 0;width:100px;}
|
||||
#signature + p + table tr:not(:last-child) td:nth-child(1):before {content:"Argument:";}
|
||||
#signature + p + table td:nth-child(2):before {content:"Type:";}
|
||||
#signature + p + table td:nth-child(3):before {content:"Required:";}
|
||||
#signature + p + table td:nth-child(4):before {content:"Description:";}
|
||||
#signature + p + table tr:last-child td:nth-child(3) {display:none;}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Testing
|
||||
|
||||
Mithril comes with a testing framework called [ospec](../ospec/README.md). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis).
|
||||
Mithril comes with a testing framework called [ospec](https://github.com/lhorie/mithril.js/tree/rewrite/ospec). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis).
|
||||
|
||||
The easist way to setup the test runner is to create an NPM script for it. Open your project's `package.json` file and edit the `test` line under the `scripts` section:
|
||||
|
||||
|
|
@ -38,6 +38,8 @@ To run the test, use the command `npm test`. Ospec considers any Javascript file
|
|||
npm test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Good testing practices
|
||||
|
||||
Generally speaking, there are two ways to write tests: upfront and after the fact.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# trust(html)
|
||||
|
||||
- [API](#api)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Security considerations](#security-considerations)
|
||||
- [Scripts that do not run](#scripts-that-do-not-run)
|
||||
|
|
@ -8,9 +9,15 @@
|
|||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Generates a trusted HTML [vnode](vnodes.md)
|
||||
Turns an HTML string into unescaped HTML. **Do not use `m.trust` on unsanitized user input.**
|
||||
|
||||
Always try to use an [alternative method](#avoid-trusting-html) first, before considering using `m.trust`.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`vnode = m.trust(html)`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# version
|
||||
|
||||
- [API](#api)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Signature
|
||||
|
||||
`m.version`
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ The first time a virtual DOM tree is rendered, it is used as a blueprint to crea
|
|||
|
||||
Typically, Virtual DOM trees are then recreated every render cycle, which normally occurs in response to event handlers or to data changes. Mithril *diffs* a vnode tree against its previous version and only modifies DOM elements in spots where there are changes.
|
||||
|
||||
It may seem wasteful to recreate vnodes so frequently, but as it turns out, modern Javascript engines can create hundres of thousands of objects in less than a millisecond. On the other hand, modifying the DOM is several orders of magnitude more expensive than creating vnodes.
|
||||
It may seem wasteful to recreate vnodes so frequently, but as it turns out, modern Javascript engines can create hundreds of thousands of objects in less than a millisecond. On the other hand, modifying the DOM is several orders of magnitude more expensive than creating vnodes.
|
||||
|
||||
For that reason, Mithril uses a sophisticated and highly optimized virtual DOM diffing algorithm to minimize the amount of DOM updates. Mithril *also* generates carefully crafted vnode data structures that are compiled by Javascript engines for near-native data structure access performance. In addition, Mithril aggressively optimizes the function that creates vnodes as well.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,38 @@
|
|||
# withAttr(attrName, callback)
|
||||
|
||||
- [API](#api)
|
||||
- [How to use](#how-to-use)
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [Predictable event target](#predictable-event-target)
|
||||
- [Attributes and properties](#attributes-and-properties)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
### Description
|
||||
|
||||
Creates an event handler. The event handler takes the value of a DOM element's property and calls a function with it as the argument.
|
||||
Returns an event handler that runs `callback` with the value of the specified DOM attribute
|
||||
|
||||
This helper function is provided to help decouple the browser's event model from application code.
|
||||
```javascript
|
||||
var state = {
|
||||
value: "",
|
||||
setValue: function(v) {value = v}
|
||||
}
|
||||
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("input", {
|
||||
oninput: m.withAttr("value", state.setValue),
|
||||
value: state.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, Component)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
`m.withAttr(attrName, callback, thisArg?)`
|
||||
|
||||
|
|
@ -26,7 +47,11 @@ Argument | Type | Required | Description
|
|||
|
||||
---
|
||||
|
||||
### How to use
|
||||
### How it works
|
||||
|
||||
The `m.withAttr` method creates an event handler. The event handler takes the value of a DOM element's property and calls a function with it as the argument.
|
||||
|
||||
This helper function is provided to help decouple the browser's event model from application code.
|
||||
|
||||
```javascript
|
||||
// standalone usage
|
||||
|
|
|
|||
3
examples/dbmonster/angular/app.js
vendored
3
examples/dbmonster/angular/app.js
vendored
|
|
@ -32,9 +32,10 @@ var AppComponent = ng.core.Component({selector: "my-app"})
|
|||
this.update()
|
||||
},
|
||||
update: function() {
|
||||
requestAnimationFrame(function() {self.update()})
|
||||
|
||||
var self = this
|
||||
self.databases = ENV.generateData().toArray()
|
||||
setTimeout(function() {self.update()}, ENV.timeout)
|
||||
|
||||
if (renderStage === 0) {
|
||||
renderStage = 1
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var data = []
|
||||
|
||||
var root = document.getElementById("app")
|
||||
update()
|
||||
|
||||
function update() {
|
||||
data = ENV.generateData().toArray()
|
||||
|
||||
Monitoring.renderRate.ping()
|
||||
|
||||
m.redraw();
|
||||
|
||||
setTimeout(update, ENV.timeout)
|
||||
}
|
||||
|
||||
m.mount(root, {
|
||||
view : function() {
|
||||
return m("div", [
|
||||
m("table", { className: "table table-striped latest-data" }, [
|
||||
m("tbody",
|
||||
data.map(function(db) {
|
||||
return m("tr", {key: db.dbname}, [
|
||||
m("td", { className: "dbname" }, db.dbname),
|
||||
m("td", { className: "query-count" }, [
|
||||
m("span", { className: db.lastSample.countClassName }, db.lastSample.nbQueries)
|
||||
]),
|
||||
db.lastSample.topFiveQueries.map(function(query) {
|
||||
return m("td", { className: query.elapsedClassName }, [
|
||||
m("span", query.formatElapsed),
|
||||
m("div", { className: "popover left" }, [
|
||||
m("div", { className: "popover-content" }, query.query),
|
||||
m("div", { className: "arrow" })
|
||||
])
|
||||
])
|
||||
})
|
||||
])
|
||||
})
|
||||
)
|
||||
])
|
||||
])
|
||||
}
|
||||
});
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="description" content="DBMON Mithril 0.2.x" />
|
||||
<meta charset="utf-8">
|
||||
<link href="../styles.css" rel="stylesheet" type="text/css" />
|
||||
<title>dbmon (Mithril 0.2.x)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./mithril-0.2.4.js"></script>
|
||||
<script src="../ENV.js"></script>
|
||||
<script src="../memory-stats.js"></script>
|
||||
<script src="../monitor.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -35,13 +35,13 @@ m.mount(document.getElementById("app"), {
|
|||
})
|
||||
|
||||
function update() {
|
||||
requestAnimationFrame(update)
|
||||
|
||||
data = ENV.generateData().toArray()
|
||||
|
||||
perfMonitor.startProfile("render")
|
||||
m.redraw()
|
||||
perfMonitor.endProfile("render")
|
||||
|
||||
setTimeout(update, ENV.timeout)
|
||||
}
|
||||
|
||||
update()
|
||||
|
|
|
|||
|
|
@ -38,14 +38,13 @@ var DBMon = React.createClass({
|
|||
|
||||
var root = document.getElementById("app")
|
||||
function update() {
|
||||
requestAnimationFrame(update)
|
||||
|
||||
data = ENV.generateData().toArray()
|
||||
|
||||
perfMonitor.startProfile("render")
|
||||
ReactDOM.render(h(DBMon, null), root)
|
||||
|
||||
perfMonitor.endProfile("render")
|
||||
|
||||
setTimeout(update, ENV.timeout)
|
||||
}
|
||||
|
||||
update()
|
||||
|
|
|
|||
|
|
@ -4,48 +4,48 @@ perfMonitor.startMemMonitor()
|
|||
perfMonitor.initProfiler("render")
|
||||
|
||||
var vm = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
databases: [],
|
||||
},
|
||||
methods: {
|
||||
update: function () {
|
||||
this.databases = ENV.generateData().toArray()
|
||||
setTimeout(this.update.bind(this), ENV.timeout)
|
||||
el: '#app',
|
||||
data: {
|
||||
databases: [],
|
||||
},
|
||||
methods: {
|
||||
update: function () {
|
||||
requestAnimationFrame(this.update.bind(this))
|
||||
this.databases = ENV.generateData().toArray()
|
||||
|
||||
if (renderStage === 0) {
|
||||
renderStage = 1
|
||||
perfMonitor.startProfile('render')
|
||||
}
|
||||
},
|
||||
},
|
||||
updated: function () {
|
||||
if (renderStage === 1) {
|
||||
renderStage = 0
|
||||
perfMonitor.endProfile('render')
|
||||
}
|
||||
},
|
||||
template: '<div>' +
|
||||
'<table class="table table-striped latest-data">' +
|
||||
'<tbody>' +
|
||||
'<tr v-for="db of databases">' +
|
||||
'<td class="dbname">{{ db.dbname }}</td>' +
|
||||
'<td class="query-count">' +
|
||||
'<span v-bind:class="[ db.lastSample.countClassName ]">' +
|
||||
'{{ db.lastSample.nbQueries}}' +
|
||||
'</span>' +
|
||||
'</td>' +
|
||||
'<td v-for="q of db.lastSample.topFiveQueries" v-bind:class="[ q.elapsedClassName ]">' +
|
||||
'{{ q.formatElapsed }}' +
|
||||
'<div class="popover left">' +
|
||||
'<div className="popover-content">{{ q.query }}</div>' +
|
||||
'<div className="arrow"></div>' +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'</tr>' +
|
||||
'</tbody>' +
|
||||
'</table>' +
|
||||
'</div>',
|
||||
if (renderStage === 0) {
|
||||
renderStage = 1
|
||||
perfMonitor.startProfile('render')
|
||||
}
|
||||
},
|
||||
},
|
||||
updated: function () {
|
||||
if (renderStage === 1) {
|
||||
renderStage = 0
|
||||
perfMonitor.endProfile('render')
|
||||
}
|
||||
},
|
||||
template: '<div>' +
|
||||
'<table class="table table-striped latest-data">' +
|
||||
'<tbody>' +
|
||||
'<tr v-for="db of databases">' +
|
||||
'<td class="dbname">{{ db.dbname }}</td>' +
|
||||
'<td class="query-count">' +
|
||||
'<span v-bind:class="[ db.lastSample.countClassName ]">' +
|
||||
'{{ db.lastSample.nbQueries}}' +
|
||||
'</span>' +
|
||||
'</td>' +
|
||||
'<td v-for="q of db.lastSample.topFiveQueries" v-bind:class="[ q.elapsedClassName ]">' +
|
||||
'{{ q.formatElapsed }}' +
|
||||
'<div class="popover left">' +
|
||||
'<div className="popover-content">{{ q.query }}</div>' +
|
||||
'<div className="arrow"></div>' +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'</tr>' +
|
||||
'</tbody>' +
|
||||
'</table>' +
|
||||
'</div>',
|
||||
})
|
||||
|
||||
vm.update()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="//vuejs.org/js/vue.min.js"></script>
|
||||
<script src="https://vuejs.org/js/vue.min.js"></script>
|
||||
<script src="../ENV.js"></script>
|
||||
<script src="https://localvoid.github.io/perf-monitor/0.1/perf-monitor.js"></script>
|
||||
<script src="app.js"></script>
|
||||
|
|
|
|||
4
index.js
4
index.js
|
|
@ -4,13 +4,13 @@ var m = require("./hyperscript")
|
|||
var requestService = require("./request")
|
||||
var redrawService = require("./redraw")
|
||||
|
||||
requestService.setCompletionCallback(redrawService.publish)
|
||||
requestService.setCompletionCallback(redrawService.redraw)
|
||||
|
||||
m.mount = require("./mount")
|
||||
m.route = require("./route")
|
||||
m.withAttr = require("./util/withAttr")
|
||||
m.render = require("./render").render
|
||||
m.redraw = redrawService.publish
|
||||
m.redraw = redrawService.redraw
|
||||
m.request = requestService.request
|
||||
m.jsonp = requestService.jsonp
|
||||
m.parseQueryString = require("./querystring/parse")
|
||||
|
|
|
|||
352
mithril.js
352
mithril.js
|
|
@ -202,30 +202,37 @@ var buildQueryString = function(object) {
|
|||
}
|
||||
var _8 = function($window, Promise) {
|
||||
var callbackCount = 0
|
||||
var count = 0
|
||||
var oncompletion
|
||||
function setCompletionCallback(callback) {oncompletion = callback}
|
||||
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
||||
function finalize(promise0) {
|
||||
var then0 = promise0.then
|
||||
promise0.then = function() {
|
||||
count++
|
||||
var next = then0.apply(promise0, arguments)
|
||||
next.then(complete, function(e) {
|
||||
complete()
|
||||
throw e
|
||||
})
|
||||
return finalize(next)
|
||||
function finalizer() {
|
||||
var count = 0
|
||||
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
||||
return function finalize(promise0) {
|
||||
var then0 = promise0.then
|
||||
promise0.then = function() {
|
||||
count++
|
||||
var next = then0.apply(promise0, arguments)
|
||||
next.then(complete, function(e) {
|
||||
complete()
|
||||
throw e
|
||||
})
|
||||
return finalize(next)
|
||||
}
|
||||
return promise0
|
||||
}
|
||||
return promise0
|
||||
}
|
||||
function normalize(args, extra) {
|
||||
if (typeof args === "string") {
|
||||
var url = args
|
||||
args = extra || {}
|
||||
if (args.url == null) args.url = url
|
||||
}
|
||||
return args
|
||||
}
|
||||
function request(args, extra) {
|
||||
return finalize(new Promise(function(resolve, reject) {
|
||||
if (typeof args === "string") {
|
||||
var url = args
|
||||
args = extra || {}
|
||||
if (args.url == null) args.url = url
|
||||
}
|
||||
var finalize = finalizer()
|
||||
args = normalize(args, extra)
|
||||
var promise0 = new Promise(function(resolve, reject) {
|
||||
if (args.method == null) args.method = "GET"
|
||||
args.method = args.method.toUpperCase()
|
||||
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
|
||||
|
|
@ -265,10 +272,14 @@ var _8 = function($window, Promise) {
|
|||
}
|
||||
if (useBody && (args.data != null)) xhr.send(args.data)
|
||||
else xhr.send()
|
||||
}))
|
||||
})
|
||||
return args.background === true ? promise0 : finalize(promise0)
|
||||
}
|
||||
function jsonp(args) {
|
||||
return finalize(new Promise(function(resolve, reject) {
|
||||
function jsonp(args, extra) {
|
||||
var finalize = finalizer()
|
||||
args = normalize(args, extra)
|
||||
|
||||
var promise0 = new Promise(function(resolve, reject) {
|
||||
var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
|
||||
var script = $window.document.createElement("script")
|
||||
$window[callbackName] = function(data) {
|
||||
|
|
@ -286,7 +297,8 @@ var _8 = function($window, Promise) {
|
|||
args.data[args.callbackKey || "callback"] = callbackName
|
||||
script.src = assemble(args.url, args.data)
|
||||
$window.document.documentElement.appendChild(script)
|
||||
}))
|
||||
})
|
||||
return args.background === true? promise0 : finalize(promise0)
|
||||
}
|
||||
function interpolate(url, data) {
|
||||
if (data == null) return url
|
||||
|
|
@ -327,22 +339,7 @@ var _8 = function($window, Promise) {
|
|||
return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
|
||||
}
|
||||
var requestService = _8(window, PromisePolyfill)
|
||||
var _11 = function() {
|
||||
var callbacks = []
|
||||
function unsubscribe(callback) {
|
||||
var index = callbacks.indexOf(callback)
|
||||
if (index > -1) callbacks.splice(index, 1)
|
||||
}
|
||||
function publish() {
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i].apply(this, arguments)
|
||||
}
|
||||
}
|
||||
return {subscribe: callbacks.push.bind(callbacks), unsubscribe: unsubscribe, publish: publish}
|
||||
}
|
||||
var redrawService = _11()
|
||||
requestService.setCompletionCallback(redrawService.publish)
|
||||
var _13 = function($window) {
|
||||
var coreRenderer = function($window) {
|
||||
var $doc = $window.document
|
||||
var $emptyFragment = $doc.createDocumentFragment()
|
||||
var onevent
|
||||
|
|
@ -455,9 +452,16 @@ var _13 = function($window) {
|
|||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
|
||||
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
||||
else {
|
||||
if (old.length === vnodes.length && vnodes[0] != null && vnodes[0].key == null) {
|
||||
var isUnkeyed = false
|
||||
for (var i = 0; i < vnodes.length; i++) {
|
||||
if (vnodes[i] != null) {
|
||||
isUnkeyed = vnodes[i].key == null
|
||||
break
|
||||
}
|
||||
}
|
||||
if (old.length === vnodes.length && isUnkeyed) {
|
||||
for (var i = 0; i < old.length; i++) {
|
||||
if (old[i] === vnodes[i] || old[i] == null && vnodes[i] == null) continue
|
||||
if (old[i] === vnodes[i]) continue
|
||||
else if (old[i] == null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling))
|
||||
else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
|
||||
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
|
||||
|
|
@ -640,8 +644,8 @@ var _13 = function($window) {
|
|||
for (var i = 0; i < end; i++) {
|
||||
var vnode = vnodes[i]
|
||||
if (vnode != null) {
|
||||
var key1 = vnode.key
|
||||
if (key1 != null) map[key1] = i
|
||||
var key2 = vnode.key
|
||||
if (key2 != null) map[key2] = i
|
||||
}
|
||||
}
|
||||
return map
|
||||
|
|
@ -747,34 +751,34 @@ var _13 = function($window) {
|
|||
}
|
||||
//attrs2
|
||||
function setAttrs(vnode, attrs2, ns) {
|
||||
for (var key1 in attrs2) {
|
||||
setAttr(vnode, key1, null, attrs2[key1], ns)
|
||||
for (var key2 in attrs2) {
|
||||
setAttr(vnode, key2, null, attrs2[key2], ns)
|
||||
}
|
||||
}
|
||||
function setAttr(vnode, key1, old, value, ns) {
|
||||
function setAttr(vnode, key2, old, value, ns) {
|
||||
var element = vnode.dom
|
||||
if (key1 === "key" || (old === value && !isFormAttribute(vnode, key1)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key1)) return
|
||||
var nsLastIndex = key1.indexOf(":")
|
||||
if (nsLastIndex > -1 && key1.substr(0, nsLastIndex) === "xlink") {
|
||||
element.setAttributeNS("http://www.w3.org/1999/xlink", key1.slice(nsLastIndex + 1), value)
|
||||
if (key2 === "key" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return
|
||||
var nsLastIndex = key2.indexOf(":")
|
||||
if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") {
|
||||
element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value)
|
||||
}
|
||||
else if (key1[0] === "o" && key1[1] === "n" && typeof value === "function") updateEvent(vnode, key1, value)
|
||||
else if (key1 === "style") updateStyle(element, old, value)
|
||||
else if (key1 in element && !isAttribute(key1) && ns === undefined) {
|
||||
else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
|
||||
else if (key2 === "style") updateStyle(element, old, value)
|
||||
else if (key2 in element && !isAttribute(key2) && ns === undefined) {
|
||||
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
|
||||
if (vnode.tag === "input" && key1 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
if (vnode.tag === "input" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
//setting select[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "select" && key1 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
if (vnode.tag === "select" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
//setting option[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "option" && key1 === "value" && vnode.dom.value === value) return
|
||||
element[key1] = value
|
||||
if (vnode.tag === "option" && key2 === "value" && vnode.dom.value === value) return
|
||||
element[key2] = value
|
||||
}
|
||||
else {
|
||||
if (typeof value === "boolean") {
|
||||
if (value) element.setAttribute(key1, "")
|
||||
else element.removeAttribute(key1)
|
||||
if (value) element.setAttribute(key2, "")
|
||||
else element.removeAttribute(key2)
|
||||
}
|
||||
else element.setAttribute(key1 === "className" ? "class" : key1, value)
|
||||
else element.setAttribute(key2 === "className" ? "class" : key2, value)
|
||||
}
|
||||
}
|
||||
function setLateAttrs(vnode) {
|
||||
|
|
@ -786,16 +790,16 @@ var _13 = function($window) {
|
|||
}
|
||||
function updateAttrs(vnode, old, attrs2, ns) {
|
||||
if (attrs2 != null) {
|
||||
for (var key1 in attrs2) {
|
||||
setAttr(vnode, key1, old && old[key1], attrs2[key1], ns)
|
||||
for (var key2 in attrs2) {
|
||||
setAttr(vnode, key2, old && old[key2], attrs2[key2], ns)
|
||||
}
|
||||
}
|
||||
if (old != null) {
|
||||
for (var key1 in old) {
|
||||
if (attrs2 == null || !(key1 in attrs2)) {
|
||||
if (key1 === "className") key1 = "class"
|
||||
if (key1[0] === "o" && key1[1] === "n" && !isLifecycleMethod(key1)) updateEvent(vnode, key1, undefined)
|
||||
else if (key1 !== "key") vnode.dom.removeAttribute(key1)
|
||||
for (var key2 in old) {
|
||||
if (attrs2 == null || !(key2 in attrs2)) {
|
||||
if (key2 === "className") key2 = "class"
|
||||
if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined)
|
||||
else if (key2 !== "key") vnode.dom.removeAttribute(key2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -819,32 +823,33 @@ var _13 = function($window) {
|
|||
else if (typeof style === "string") element.style.cssText = style
|
||||
else {
|
||||
if (typeof old === "string") element.style.cssText = ""
|
||||
for (var key1 in style) {
|
||||
element.style[key1] = style[key1]
|
||||
for (var key2 in style) {
|
||||
element.style[key2] = style[key2]
|
||||
}
|
||||
if (old != null && typeof old !== "string") {
|
||||
for (var key1 in old) {
|
||||
if (!(key1 in style)) element.style[key1] = ""
|
||||
for (var key2 in old) {
|
||||
if (!(key2 in style)) element.style[key2] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//event
|
||||
function updateEvent(vnode, key1, value) {
|
||||
function updateEvent(vnode, key2, value) {
|
||||
var element = vnode.dom
|
||||
var callback = function(e) {
|
||||
var callback = typeof onevent !== "function" ? value : function(e) {
|
||||
var result = value.call(element, e)
|
||||
if (typeof onevent === "function") onevent.call(element, e)
|
||||
onevent.call(element, e)
|
||||
return result
|
||||
}
|
||||
if (key1 in element) element[key1] = typeof value === "function" ? callback : null
|
||||
if (key2 in element) element[key2] = typeof value === "function" ? callback : null
|
||||
else {
|
||||
var eventName = key1.slice(2)
|
||||
var eventName = key2.slice(2)
|
||||
if (vnode.events === undefined) vnode.events = {}
|
||||
if (vnode.events[key1] != null) element.removeEventListener(eventName, vnode.events[key1], false)
|
||||
if (vnode.events[key2] === callback) return
|
||||
if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false)
|
||||
if (typeof value === "function") {
|
||||
vnode.events[key1] = callback
|
||||
element.addEventListener(eventName, vnode.events[key1], false)
|
||||
vnode.events[key2] = callback
|
||||
element.addEventListener(eventName, vnode.events[key2], false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -876,7 +881,7 @@ var _13 = function($window) {
|
|||
if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
|
||||
var hooks = []
|
||||
var active = $doc.activeElement
|
||||
// First time rendering into a node clears it out
|
||||
// First time0 rendering into a node clears it out
|
||||
if (dom.vnodes == null) dom.textContent = ""
|
||||
if (!Array.isArray(vnodes)) vnodes = [vnodes]
|
||||
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
|
||||
|
|
@ -886,79 +891,89 @@ var _13 = function($window) {
|
|||
}
|
||||
return {render: render, setEventCallback: setEventCallback}
|
||||
}
|
||||
var renderService = _13(window)
|
||||
var throttle = function(callback1) {
|
||||
function throttle(callback) {
|
||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||
var time = 16
|
||||
var last = 0, pending = null
|
||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||
return function(synchronous) {
|
||||
return function() {
|
||||
var now = Date.now()
|
||||
if (synchronous === true || last === 0 || now - last >= time) {
|
||||
if (last === 0 || now - last >= time) {
|
||||
last = now
|
||||
callback1()
|
||||
callback()
|
||||
}
|
||||
else if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = null
|
||||
callback1()
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, time - (now - last))
|
||||
}
|
||||
}
|
||||
}
|
||||
var autoredraw = function(root, renderer, pubsub, callback0) {
|
||||
var run1 = throttle(callback0)
|
||||
if (renderer != null) {
|
||||
renderer.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) pubsub.publish()
|
||||
})
|
||||
var _11 = function($window) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) redraw()
|
||||
})
|
||||
|
||||
var callbacks = []
|
||||
function subscribe(key1, callback) {
|
||||
unsubscribe(key1)
|
||||
callbacks.push(key1, throttle(callback))
|
||||
}
|
||||
if (pubsub != null) {
|
||||
if (root.redraw) pubsub.unsubscribe(root.redraw)
|
||||
pubsub.subscribe(run1)
|
||||
function unsubscribe(key1) {
|
||||
var index = callbacks.indexOf(key1)
|
||||
if (index > -1) callbacks.splice(index, 2)
|
||||
}
|
||||
return root.redraw = run1
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
}
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
var _17 = function(renderer, pubsub) {
|
||||
var redrawService = _11(window)
|
||||
requestService.setCompletionCallback(redrawService.redraw)
|
||||
var _16 = function(redrawService0) {
|
||||
return function(root, component) {
|
||||
if (component === null) {
|
||||
renderer.render(root, [])
|
||||
pubsub.unsubscribe(root.redraw)
|
||||
delete root.redraw
|
||||
redrawService0.render(root, [])
|
||||
redrawService0.unsubscribe(root)
|
||||
return
|
||||
}
|
||||
|
||||
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
var run0 = autoredraw(root, renderer, pubsub, function() {
|
||||
renderer.render(root, Vnode(component, undefined, undefined, undefined, undefined, undefined))
|
||||
})
|
||||
run0()
|
||||
|
||||
var run0 = function() {
|
||||
redrawService0.render(root, Vnode(component))
|
||||
}
|
||||
redrawService0.subscribe(root, run0)
|
||||
redrawService0.redraw()
|
||||
}
|
||||
}
|
||||
m.mount = _17(renderService, redrawService)
|
||||
var mount = m.mount
|
||||
m.mount = _16(redrawService)
|
||||
var parseQueryString = function(string) {
|
||||
if (string === "" || string == null) return {}
|
||||
if (string.charAt(0) === "?") string = string.slice(1)
|
||||
var entries = string.split("&"), data0 = {}, counters = {}
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = entries[i].split("=")
|
||||
var key3 = decodeURIComponent(entry[0])
|
||||
var key5 = decodeURIComponent(entry[0])
|
||||
var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
|
||||
if (value === "true") value = true
|
||||
else if (value === "false") value = false
|
||||
var levels = key3.split(/\]\[?|\[/)
|
||||
var levels = key5.split(/\]\[?|\[/)
|
||||
var cursor = data0
|
||||
if (key3.indexOf("[") > -1) levels.pop()
|
||||
if (key5.indexOf("[") > -1) levels.pop()
|
||||
for (var j = 0; j < levels.length; j++) {
|
||||
var level = levels[j], nextLevel = levels[j + 1]
|
||||
var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
|
||||
var isValue = j === levels.length - 1
|
||||
if (level === "") {
|
||||
var key3 = levels.slice(0, j).join()
|
||||
if (counters[key3] == null) counters[key3] = 0
|
||||
level = counters[key3]++
|
||||
var key5 = levels.slice(0, j).join()
|
||||
if (counters[key5] == null) counters[key5] = 0
|
||||
level = counters[key5]++
|
||||
}
|
||||
if (cursor[level] == null) {
|
||||
cursor[level] = isValue ? value : isNumber ? [] : {}
|
||||
|
|
@ -973,7 +988,7 @@ var coreRouter = function($window) {
|
|||
var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
var prefix1 = "#!"
|
||||
function setPrefix(value) {prefix1 = value}
|
||||
function normalize(fragment0) {
|
||||
function normalize1(fragment0) {
|
||||
var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
|
||||
if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
|
||||
return data
|
||||
|
|
@ -995,27 +1010,27 @@ var coreRouter = function($window) {
|
|||
if (queryIndex > -1) {
|
||||
var queryEnd = hashIndex > -1 ? hashIndex : path.length
|
||||
var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd))
|
||||
for (var key2 in queryParams) queryData[key2] = queryParams[key2]
|
||||
for (var key4 in queryParams) queryData[key4] = queryParams[key4]
|
||||
}
|
||||
if (hashIndex > -1) {
|
||||
var hashParams = parseQueryString(path.slice(hashIndex + 1))
|
||||
for (var key2 in hashParams) hashData[key2] = hashParams[key2]
|
||||
for (var key4 in hashParams) hashData[key4] = hashParams[key4]
|
||||
}
|
||||
return path.slice(0, pathEnd)
|
||||
}
|
||||
function getPath() {
|
||||
var type2 = prefix1.charAt(0)
|
||||
switch (type2) {
|
||||
case "#": return normalize("hash").slice(prefix1.length)
|
||||
case "?": return normalize("search").slice(prefix1.length) + normalize("hash")
|
||||
default: return normalize("pathname").slice(prefix1.length) + normalize("search") + normalize("hash")
|
||||
case "#": return normalize1("hash").slice(prefix1.length)
|
||||
case "?": return normalize1("search").slice(prefix1.length) + normalize1("hash")
|
||||
default: return normalize1("pathname").slice(prefix1.length) + normalize1("search") + normalize1("hash")
|
||||
}
|
||||
}
|
||||
function setPath(path, data, options) {
|
||||
var queryData = {}, hashData = {}
|
||||
path = parsePath(path, queryData, hashData)
|
||||
if (data != null) {
|
||||
for (var key2 in data) queryData[key2] = data[key2]
|
||||
for (var key4 in data) queryData[key4] = data[key4]
|
||||
path = path.replace(/:([^\/]+)/g, function(match2, token) {
|
||||
delete queryData[token]
|
||||
return data[token]
|
||||
|
|
@ -1028,15 +1043,11 @@ var coreRouter = function($window) {
|
|||
if (supportsPushState) {
|
||||
if (options && options.replace) $window.history.replaceState(null, null, prefix1 + path)
|
||||
else $window.history.pushState(null, null, prefix1 + path)
|
||||
$window.onpopstate()
|
||||
$window.onpopstate(true)
|
||||
}
|
||||
else $window.location.href = prefix1 + path
|
||||
}
|
||||
function defineRoutes(routes, resolve0, reject) {
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix1.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
|
||||
function defineRoutes(routes, resolve, reject) {
|
||||
function resolveRoute() {
|
||||
var path = getPath()
|
||||
var params = {}
|
||||
|
|
@ -1051,14 +1062,17 @@ var coreRouter = function($window) {
|
|||
for (var i = 0; i < keys.length; i++) {
|
||||
params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
||||
}
|
||||
resolve0(routes[route0], params, path, route0)
|
||||
resolve(routes[route0], params, path, route0)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
reject(path, params)
|
||||
}
|
||||
return resolveRoute
|
||||
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix1.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
}
|
||||
function link(vnode2) {
|
||||
vnode2.dom.setAttribute("href", prefix1 + vnode2.attrs.href)
|
||||
|
|
@ -1073,64 +1087,62 @@ var coreRouter = function($window) {
|
|||
}
|
||||
return {setPrefix: setPrefix, getPath: getPath, setPath: setPath, defineRoutes: defineRoutes, link: link}
|
||||
}
|
||||
var _23 = function($window, mount0) {
|
||||
var router = coreRouter($window)
|
||||
var currentResolve, currentComponent, currentRender, currentArgs, currentPath
|
||||
var RouteComponent = {view: function() {
|
||||
return [currentRender(Vnode(currentComponent, null, currentArgs, undefined, undefined, undefined))]
|
||||
}}
|
||||
function defaultRender(vnode1) {
|
||||
return vnode1
|
||||
}
|
||||
var _20 = function($window, redrawService0) {
|
||||
var routeService = coreRouter($window)
|
||||
|
||||
var identity = function(v) {return v}
|
||||
var resolver, component, attrs3, currentPath, resolve
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
currentComponent = "div"
|
||||
currentRender = defaultRender
|
||||
currentArgs = null
|
||||
mount0(root, RouteComponent)
|
||||
router.defineRoutes(routes, function(payload, args0, path) {
|
||||
var isResolver = typeof payload.view !== "function"
|
||||
var render1 = defaultRender
|
||||
var resolve = currentResolve = function (component) {
|
||||
if (resolve !== currentResolve) return
|
||||
currentResolve = null
|
||||
currentComponent = component != null ? component : isResolver ? "div" : payload
|
||||
currentRender = render1
|
||||
currentArgs = args0
|
||||
currentPath = path
|
||||
root.redraw(true)
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var update = function(routeResolver, comp, params, path) {
|
||||
resolver = routeResolver, component = comp, attrs3 = params, currentPath = path, resolve = null
|
||||
resolver.render = routeResolver.render || identity
|
||||
render1()
|
||||
}
|
||||
var render1 = function() {
|
||||
if (resolver != null) redrawService0.render(root, resolver.render(Vnode(component, attrs3.key, attrs3)))
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path) {
|
||||
if (payload.view) update({}, payload, params, path)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
if (resolve != null) update(payload, component, params, path)
|
||||
else {
|
||||
resolve = function(resolved) {
|
||||
update(payload, resolved, params, path)
|
||||
}
|
||||
payload.onmatch(function(resolved) {
|
||||
if (resolve != null) resolve(resolved)
|
||||
}, params, path)
|
||||
}
|
||||
}
|
||||
else update(payload, "div", params, path)
|
||||
}
|
||||
var onmatch = function() {
|
||||
resolve()
|
||||
}
|
||||
if (isResolver) {
|
||||
if (typeof payload.render === "function") render1 = payload.render.bind(payload)
|
||||
if (typeof payload.onmatch === "function") onmatch = payload.onmatch
|
||||
}
|
||||
|
||||
onmatch.call(payload, resolve, args0, path)
|
||||
}, function() {
|
||||
router.setPath(defaultRoute, null, {replace: true})
|
||||
routeService.setPath(defaultRoute)
|
||||
})
|
||||
redrawService0.subscribe(root, render1)
|
||||
}
|
||||
route.link = router.link
|
||||
route.prefix = router.setPrefix
|
||||
route.set = router.setPath
|
||||
route.set = routeService.setPath
|
||||
route.get = function() {return currentPath}
|
||||
route.prefix = routeService.setPrefix
|
||||
route.link = routeService.link
|
||||
return route
|
||||
}
|
||||
m.route = _23(window, mount)
|
||||
m.withAttr = function(attrName, callback2, context) {
|
||||
m.route = _20(window, redrawService)
|
||||
m.withAttr = function(attrName, callback0, context) {
|
||||
return function(e) {
|
||||
return callback2.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
|
||||
return callback0.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
|
||||
}
|
||||
}
|
||||
m.render = renderService.render
|
||||
m.redraw = redrawService.publish
|
||||
var _27 = coreRenderer(window)
|
||||
m.render = _27.render
|
||||
m.redraw = redrawService.redraw
|
||||
m.request = requestService.request
|
||||
m.jsonp = requestService.jsonp
|
||||
m.parseQueryString = parseQueryString
|
||||
m.buildQueryString = buildQueryString
|
||||
m.version = "1.0.0-rc.5"
|
||||
m.version = "1.0.0-rc.6"
|
||||
if (typeof module !== "undefined") module["exports"] = m
|
||||
else window.m = m
|
||||
}
|
||||
80
mithril.min.js
vendored
80
mithril.min.js
vendored
|
|
@ -1,40 +1,40 @@
|
|||
new function(){function m(a,b,k,d,l,h){return{tag:a,key:b,attrs:k,children:d,text:l,dom:h,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function t(a){if(null==a||"string"!==typeof a&&null==a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===H[a]){for(var b,k,d=[],l={};b=O.exec(a);){var h=b[1],v=b[2];""===h&&""!==v?k=v:"#"===h?l.id=v:"."===h?d.push(v):"["===b[3][0]&&((h=b[6])&&(h=h.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),
|
||||
"class"===b[4]?d.push(h):l[b[4]]=h||!0)}0<d.length&&(l.className=d.join(" "));H[a]=function(a,b){var d=!1,h,g,w=a.className||a["class"],n;for(n in l)a[n]=l[n];void 0!==w&&(void 0!==a["class"]&&(a["class"]=void 0,a.className=w),void 0!==l.className&&(a.className=l.className+" "+w));for(n in a)if("key"!==n){d=!0;break}Array.isArray(b)&&1==b.length&&null!=b[0]&&"#"===b[0].tag?g=b[0].children:h=b;return m(k||"div",a.key,d?a:void 0,h,g,void 0)}}var p;null==arguments[1]||"object"===typeof arguments[1]&&
|
||||
void 0===arguments[1].tag&&!Array.isArray(arguments[1])?(p=arguments[1],d=2):d=1;if(arguments.length===d+1)b=Array.isArray(arguments[d])?arguments[d]:[arguments[d]];else for(b=[];d<arguments.length;d++)b.push(arguments[d]);return"string"===typeof a?H[a](p||{},m.normalizeChildren(b)):m(a,p&&p.key,p||{},m.normalizeChildren(b),void 0,void 0)}m.normalize=function(a){return Array.isArray(a)?m("[",void 0,void 0,m.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?m("#",void 0,void 0,a,void 0,
|
||||
void 0):a};m.normalizeChildren=function(a){for(var b=0;b<a.length;b++)a[b]=m.normalize(a[b]);return a};var O=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,H={};t.trust=function(a){null==a&&(a="");return m("<",void 0,void 0,a,void 0,void 0)};t.fragment=function(a,b){return m("[",a.key,a,m.normalizeChildren(b),void 0,void 0)};var y=function(a){function b(a,b){return function w(n){var u;try{if(!b||null==n||"object"!==typeof n&&"function"!==typeof n||"function"!==typeof(u=
|
||||
n.then))m(function(){b||0!==a.length||console.error("Possible unhandled promise rejection:",n);for(var d=0;d<a.length;d++)a[d](n);l.length=0;h.length=0;r.state=b;r.retry=function(){w(n)}});else{if(n===d)throw new TypeError("Promise can't be resolved w/ itself");k(u.bind(n))}}catch(F){p(F)}}}function k(a){function b(a){return function(b){0<g++||a(b)}}var g=0,d=b(p);try{a(b(v),d)}catch(n){d(n)}}if(!(this instanceof y))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");
|
||||
var d=this,l=[],h=[],v=b(l,!0),p=b(h,!1),r=d._instance={resolvers:l,rejectors:h},m="function"===typeof setImmediate?setImmediate:setTimeout;k(a)};y.prototype.then=function(a,b){function k(a,b,k,u){b.push(function(b){if("function"!==typeof a)k(b);else try{l(a(b))}catch(g){h&&h(g)}});"function"===typeof d.retry&&u===d.state&&d.retry()}var d=this._instance,l,h,v=new y(function(a,b){l=a;h=b});k(a,d.resolvers,l,!0);k(b,d.rejectors,h,!1);return v};y.prototype["catch"]=function(a){return this.then(null,
|
||||
a)};y.resolve=function(a){return a instanceof y?a:new y(function(b){b(a)})};y.reject=function(a){return new y(function(b,k){k(a)})};y.all=function(a){return new y(function(b,k){var d=a.length,l=0,h=[];if(0===a.length)b([]);else for(var v=0;v<a.length;v++)(function(v){function r(a){l++;h[v]=a;l===d&&b(h)}null==a[v]||"object"!==typeof a[v]&&"function"!==typeof a[v]||"function"!==typeof a[v].then?r(a[v]):a[v].then(r,k)})(v)})};y.race=function(a){return new y(function(b,k){for(var d=0;d<a.length;d++)a[d].then(b,
|
||||
k)})};"undefined"===typeof Promise&&("undefined"!==typeof window?window.Promise=y:"undefined"!==typeof global&&(global.Promise=y));var C=function(a){function b(a,d){if(Array.isArray(d))for(var h=0;h<d.length;h++)b(a+"["+h+"]",d[h]);else if("[object Object]"===Object.prototype.toString.call(d))for(h in d)b(a+"["+h+"]",d[h]);else k.push(encodeURIComponent(a)+(null!=d&&""!==d?"="+encodeURIComponent(d):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var k=[],d;for(d in a)b(d,a[d]);
|
||||
return k.join("&")},I=function(a,b){function k(){0===--u&&"function"===typeof q&&q()}function d(a){var g=a.then;a.then=function(){u++;var b=g.apply(a,arguments);b.then(k,function(a){k();throw a;});return d(b)};return a}function l(a,b){if(null==b)return a;for(var g=a.match(/:[^\/]+/gi)||[],d=0;d<g.length;d++){var h=g[d].slice(1);null!=b[h]&&(a=a.replace(g[d],b[h]),delete b[h])}return a}function h(a,b){var g=C(b);if(""!==g){var d=0>a.indexOf("?")?"?":"&";a+=d+g}return a}function v(a){try{return""!==
|
||||
a?JSON.parse(a):null}catch(w){throw Error(a);}}function p(a){return a.responseText}function r(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;d<b.length;d++)b[d]=new a(b[d]);else return new a(b);return b}var m=0,u=0,q;return{request:function(g,k){return d(new b(function(b,d){if("string"===typeof g){var u=g;g=k||{};null==g.url&&(g.url=u)}null==g.method&&(g.method="GET");g.method=g.method.toUpperCase();u="boolean"===typeof g.useBody?g.useBody:"GET"!==g.method&&"TRACE"!==g.method;"function"!==
|
||||
typeof g.serialize&&(g.serialize="undefined"!==typeof FormData&&g.data instanceof FormData?function(a){return a}:JSON.stringify);"function"!==typeof g.deserialize&&(g.deserialize=v);"function"!==typeof g.extract&&(g.extract=p);g.url=l(g.url,g.data);u?g.data=g.serialize(g.data):g.url=h(g.url,g.data);var n=new a.XMLHttpRequest;n.open(g.method,g.url,"boolean"===typeof g.async?g.async:!0,"string"===typeof g.user?g.user:void 0,"string"===typeof g.password?g.password:void 0);g.serialize===JSON.stringify&&
|
||||
u&&n.setRequestHeader("Content-Type","application/json; charset=utf-8");g.deserialize===v&&n.setRequestHeader("Accept","application/json, text/*");g.withCredentials&&(n.withCredentials=g.withCredentials);"function"===typeof g.config&&(n=g.config(n,g)||n);n.onreadystatechange=function(){if(4===n.readyState)try{var a=g.extract!==p?g.extract(n,g):g.deserialize(g.extract(n,g));if(200<=n.status&&300>n.status||304===n.status)b(r(g.type,a));else{var h=Error(n.responseText),k;for(k in a)h[k]=a[k];d(h)}}catch(G){d(G)}};
|
||||
u&&null!=g.data?n.send(g.data):n.send()}))},jsonp:function(g){return d(new b(function(b,d){var n=g.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=a.document.createElement("script");a[n]=function(d){k.parentNode.removeChild(k);b(r(g.type,d));delete a[n]};k.onerror=function(){k.parentNode.removeChild(k);d(Error("JSONP request failed"));delete a[n]};null==g.data&&(g.data={});g.url=l(g.url,g.data);g.data[g.callbackKey||"callback"]=n;k.src=h(g.url,g.data);a.document.documentElement.appendChild(k)}))},
|
||||
setCompletionCallback:function(a){q=a}}}(window,"undefined"!==typeof Promise?Promise:y),J=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1<b&&a.splice(b,1)},publish:function(){for(var b=0;b<a.length;b++)a[b].apply(this,arguments)}}}();I.setCompletionCallback(J.publish);var N=function(a){function b(c,f,a,b,d,g,h){for(;a<b;a++){var e=f[a];null!=e&&r(c,k(e,d,h),g)}}function k(c,f,a){var e=c.tag;null!=c.attrs&&t(c.attrs,c,f);if("string"===typeof e)switch(e){case "#":return c.dom=
|
||||
z.createTextNode(c.children);case "<":return d(c);case "[":var g=z.createDocumentFragment();null!=c.children&&(e=c.children,b(g,e,0,e.length,f,null,a));c.dom=g.firstChild;c.domSize=g.childNodes.length;return g;default:var h=c.tag;switch(c.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var E=(e=c.attrs)&&e.is,h=a?E?z.createElementNS(a,h,{is:E}):z.createElementNS(a,h):E?z.createElement(h,{is:E}):z.createElement(h);c.dom=h;if(null!=e)for(g in E=
|
||||
a,e)n(c,g,null,e[g],E);null!=c.attrs&&null!=c.attrs.contenteditable?y(c):(null!=c.text&&(""!==c.text?h.textContent=c.text:c.children=[m("#",void 0,void 0,c.text,void 0,void 0)]),null!=c.children&&(g=c.children,b(h,g,0,g.length,f,null,a),f=c.attrs,"select"===c.tag&&null!=f&&("value"in f&&n(c,"value",null,f.value,void 0),"selectedIndex"in f&&n(c,"selectedIndex",null,f.selectedIndex,void 0))));return h}else{c.state||(c.state={});P(c.state,c.tag);g=c.tag.view;if(null!=g.reentrantLock)c=G;else if(g.reentrantLock=
|
||||
!0,t(c.tag,c,f),c.instance=m.normalize(g.call(c.state,c)),g.reentrantLock=null,null!=c.instance){if(c.instance===c)throw Error("A view cannot return the vnode it received as arguments");f=k(c.instance,f,a);c.dom=c.instance.dom;c.domSize=null!=c.dom?c.instance.domSize:0;c=f}else c.domSize=0,c=G;return c}}function d(c){var f={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(c.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",f=z.createElement(f);
|
||||
f.innerHTML=c.children;c.dom=f.firstChild;c.domSize=f.childNodes.length;c=z.createDocumentFragment();for(var a;a=f.firstChild;)c.appendChild(a);return c}function l(c,a,e,g,d,n){if(a!==e&&(null!=a||null!=e))if(null==a)b(c,e,0,e.length,g,d,void 0);else if(null==e)u(a,0,a.length,e);else if(a.length===e.length&&null!=e[0]&&null==e[0].key)for(var f=0;f<a.length;f++)a[f]===e[f]||null==a[f]&&null==e[f]||(null==a[f]?r(c,k(e[f],g,n),p(a,f+1,d)):null==e[f]?u(a,f,f+1,e):h(c,a[f],e[f],g,p(a,f+1,d),!1,n));else{a:{if(null!=
|
||||
a.pool&&Math.abs(a.pool.length-e.length)<=Math.abs(a.length-e.length)&&(f=e[0]&&e[0].children&&e[0].children.length||0,Math.abs((a.pool[0]&&a.pool[0].children&&a.pool[0].children.length||0)-f)<=Math.abs((a[0]&&a[0].children&&a[0].children.length||0)-f))){f=!0;break a}f=!1}f&&(a=a.concat(a.pool));for(var A=0,l=0,q=a.length-1,B=e.length-1,w;q>=A&&B>=l;){var x=a[A],m=e[l];if(x!==m||f)if(null==x)A++;else if(null==m)l++;else if(x.key===m.key)A++,l++,h(c,x,m,g,p(a,A,d),f,n),f&&x.tag===m.tag&&r(c,v(x),d);
|
||||
else if(x=a[q],x!==m||f)if(null==x)q--;else if(null==m)l++;else if(x.key===m.key)h(c,x,m,g,p(a,q+1,d),f,n),(f||l<B)&&r(c,v(x),p(a,A,d)),q--,l++;else break;else q--,l++;else A++,l++}for(;q>=A&&B>=l;){x=a[q];m=e[B];if(x!==m||f)if(null==x)q--;else{if(null!=m)if(x.key===m.key)h(c,x,m,g,p(a,q+1,d),f,n),f&&x.tag===m.tag&&r(c,v(x),d),null!=x.dom&&(d=x.dom),q--;else{if(!w){w=a;var x=q,D={},z;for(z=0;z<x;z++){var t=w[z];null!=t&&(t=t.key,null!=t&&(D[t]=z))}w=D}null!=m&&(x=w[m.key],null!=x?(D=a[x],h(c,D,m,
|
||||
g,p(a,q+1,d),f,n),r(c,v(D),d),a[x].skip=!0,null!=D.dom&&(d=D.dom)):(m=k(m,g,void 0),r(c,m,d),d=m))}B--}else q--,B--;if(B<l)break}b(c,e,l,B+1,g,d,n);u(a,A,q+1,e)}}function h(a,f,e,b,u,q,w){var c=f.tag;if(c===e.tag){e.state=f.state;e.events=f.events;var A;var B;null!=e.attrs&&"function"===typeof e.attrs.onbeforeupdate&&(A=e.attrs.onbeforeupdate.call(e.state,e,f));"string"!==typeof e.tag&&"function"===typeof e.tag.onbeforeupdate&&(B=e.tag.onbeforeupdate.call(e.state,e,f));void 0===A&&void 0===B||A||
|
||||
B?A=!1:(e.dom=f.dom,e.domSize=f.domSize,e.instance=f.instance,A=!0);if(!A)if(null!=e.attrs&&M(e.attrs,e,b,q),"string"===typeof c)switch(c){case "#":f.children.toString()!==e.children.toString()&&(f.dom.nodeValue=e.children);e.dom=f.dom;break;case "<":f.children!==e.children?(v(f),r(a,d(e),u)):(e.dom=f.dom,e.domSize=f.domSize);break;case "[":l(a,f.children,e.children,b,u,w);f=0;b=e.children;e.dom=null;if(null!=b){for(var p=0;p<b.length;p++)a=b[p],null!=a&&null!=a.dom&&(null==e.dom&&(e.dom=a.dom),f+=
|
||||
a.domSize||1);1!==f&&(e.domSize=f)}break;default:a=w;u=e.dom=f.dom;switch(e.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===e.tag&&(null==e.attrs&&(e.attrs={}),null!=e.text&&(e.attrs.value=e.text,e.text=void 0));q=f.attrs;w=e.attrs;c=a;if(null!=w)for(p in w)n(e,p,q&&q[p],w[p],c);if(null!=q)for(p in q)null!=w&&p in w||("className"===p&&(p="class"),"o"!==p[0]||"n"!==p[1]||L(p)?"key"!==p&&e.dom.removeAttribute(p):F(e,p,void 0));null!=
|
||||
e.attrs&&null!=e.attrs.contenteditable?y(e):null!=f.text&&null!=e.text&&""!==e.text?f.text.toString()!==e.text.toString()&&(f.dom.firstChild.nodeValue=e.text):(null!=f.text&&(f.children=[m("#",void 0,void 0,f.text,void 0,f.dom.firstChild)]),null!=e.text&&(e.children=[m("#",void 0,void 0,e.text,void 0,void 0)]),l(u,f.children,e.children,b,null,a))}else e.instance=m.normalize(e.tag.view.call(e.state,e)),M(e.tag,e,b,q),null!=e.instance?(null==f.instance?r(a,k(e.instance,b,w),u):h(a,f.instance,e.instance,
|
||||
b,u,q,w),e.dom=e.instance.dom,e.domSize=e.instance.domSize):null!=f.instance?(g(f.instance,null),e.dom=void 0,e.domSize=0):(e.dom=f.dom,e.domSize=f.domSize)}else g(f,null),r(a,k(e,b,w),u)}function v(a){var c=a.domSize;if(null!=c||null==a.dom){var e=z.createDocumentFragment();if(0<c){for(a=a.dom;--c;)e.appendChild(a.nextSibling);e.insertBefore(a,e.firstChild)}return e}return a.dom}function p(a,f,e){for(;f<a.length;f++)if(null!=a[f]&&null!=a[f].dom)return a[f].dom;return e}function r(a,f,e){e&&e.parentNode?
|
||||
a.insertBefore(f,e):a.appendChild(f)}function y(a){var c=a.children;if(null!=c&&1===c.length&&"<"===c[0].tag)c=c[0].children,a.dom.innerHTML!==c&&(a.dom.innerHTML=c);else if(null!=a.text||null!=c&&0!==c.length)throw Error("Child node of a contenteditable must be trusted");}function u(a,f,e,b){for(;f<e;f++){var c=a[f];null!=c&&(c.skip?c.skip=!1:g(c,b))}}function q(a){var c=!1;return function(){c||(c=!0,a())}}function g(a,f){function c(){if(++d===b&&(w(a),a.dom)){var c=a.domSize||1;if(1<c)for(var e=
|
||||
a.dom;--c;){var g=e.nextSibling,h=g.parentNode;null!=h&&h.removeChild(g)}c=a.dom;e=c.parentNode;null!=e&&e.removeChild(c);if(c=null!=f&&null==a.domSize)c=a.attrs,c=!(null!=c&&(c.oncreate||c.onupdate||c.onbeforeremove||c.onremove));c&&"string"===typeof a.tag&&(f.pool?f.pool.push(a):f.pool=[a])}}var b=1,d=0;a.attrs&&a.attrs.onbeforeremove&&(b++,a.attrs.onbeforeremove.call(a.state,a,q(c)));"string"!==typeof a.tag&&a.tag.onbeforeremove&&(b++,a.tag.onbeforeremove.call(a.state,a,q(c)));c()}function w(a){a.attrs&&
|
||||
a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&a.tag.onremove&&a.tag.onremove.call(a.state,a);if(null!=a.instance)w(a.instance);else if(a=a.children,Array.isArray(a))for(var c=0;c<a.length;c++){var e=a[c];null!=e&&w(e)}}function n(a,f,e,b,d){var c=a.dom;if("key"!==f&&(e!==b||"value"===f||"checked"===f||"selectedIndex"===f||"selected"===f&&a.dom===z.activeElement||"object"===typeof b)&&"undefined"!==typeof b&&!L(f)){var g=f.indexOf(":");if(-1<g&&"xlink"===f.substr(0,g))c.setAttributeNS("http://www.w3.org/1999/xlink",
|
||||
f.slice(g+1),b);else if("o"===f[0]&&"n"===f[1]&&"function"===typeof b)F(a,f,b);else if("style"===f)if(a=e,a===b&&(c.style.cssText="",a=null),null==b)c.style.cssText="";else if("string"===typeof b)c.style.cssText=b;else{"string"===typeof a&&(c.style.cssText="");for(var h in b)c.style[h]=b[h];if(null!=a&&"string"!==typeof a)for(h in a)h in b||(c.style[h]="")}else f in c&&"href"!==f&&"list"!==f&&"form"!==f&&"width"!==f&&"height"!==f&&void 0===d?"input"===a.tag&&"value"===f&&a.dom.value===b&&a.dom===
|
||||
z.activeElement||"select"===a.tag&&"value"===f&&a.dom.value===b&&a.dom===z.activeElement||"option"===a.tag&&"value"===f&&a.dom.value===b||(c[f]=b):"boolean"===typeof b?b?c.setAttribute(f,""):c.removeAttribute(f):c.setAttribute("className"===f?"class":f,b)}}function L(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function F(a,b,e){var c=a.dom,f=function(a){var b=e.call(c,a);"function"===typeof C&&C.call(c,a);return b};if(b in c)c[b]=
|
||||
"function"===typeof e?f:null;else{var d=b.slice(2);void 0===a.events&&(a.events={});null!=a.events[b]&&c.removeEventListener(d,a.events[b],!1);"function"===typeof e&&(a.events[b]=f,c.addEventListener(d,a.events[b],!1))}}function t(a,b,e){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&e.push(a.oncreate.bind(b.state,b))}function M(a,b,e,d){d?t(a,b,e):"function"===typeof a.onupdate&&e.push(a.onupdate.bind(b.state,b))}function P(a,b){Object.keys(b).forEach(function(c){a[c]=
|
||||
b[c]})}var z=a.document,G=z.createDocumentFragment(),C;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],f=z.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);l(a,a.vnodes,m.normalizeChildren(b),c,null,void 0);a.vnodes=b;for(var d=0;d<c.length;d++)c[d]();z.activeElement!==f&&f.focus()},setEventCallback:function(a){return C=a}}}(window),Q=function(a){var b=0,k=null,d="function"===typeof requestAnimationFrame?
|
||||
requestAnimationFrame:setTimeout;return function(l){var h=Date.now();!0===l||0===b||16<=h-b?(b=h,a()):null===k&&(k=d(function(){k=null;a();b=Date.now()},16-(h-b)))}},R=function(a,b,k,d){d=Q(d);null!=b&&b.setEventCallback(function(a){!1!==a.redraw&&k.publish()});null!=k&&(a.redraw&&k.unsubscribe(a.redraw),k.subscribe(d));return a.redraw=d};t.mount=function(a,b){return function(k,d){if(null===d)a.render(k,[]),b.unsubscribe(k.redraw),delete k.redraw;else{if(null==d.view)throw Error("m.mount(element, component) expects a component, not a vnode");
|
||||
R(k,a,b,function(){a.render(k,m(d,void 0,void 0,void 0,void 0,void 0))})()}}}(N,J);var K=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var b={},k={},d=0;d<a.length;d++){var l=a[d].split("="),h=decodeURIComponent(l[0]),l=2===l.length?decodeURIComponent(l[1]):"";"true"===l?l=!0:"false"===l&&(l=!1);var m=h.split(/\]\[?|\[/),p=b;-1<h.indexOf("[")&&m.pop();for(var r=0;r<m.length;r++){var h=m[r],t=m[r+1],t=""==t||!isNaN(parseInt(t,10)),u=r===m.length-1;""===
|
||||
h&&(h=m.slice(0,r).join(),null==k[h]&&(k[h]=0),h=k[h]++);null==p[h]&&(p[h]=u?l:t?[]:{});p=p[h]}}return b},S=function(a){function b(b){var d=a.location[b].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===b&&"/"!==d[0]&&(d="/"+d);return d}function k(a){return function(){null==t&&(t=p(function(){t=null;a()}))}}function d(a,b,d){var g=a.indexOf("?"),h=a.indexOf("#"),k=-1<g?g:-1<h?h:a.length;if(-1<g){var g=K(a.slice(g+1,-1<h?h:a.length)),l;for(l in g)b[l]=g[l]}if(-1<h)for(l in b=K(a.slice(h+
|
||||
1)),b)d[l]=b[l];return a.slice(0,k)}function l(){switch(r.charAt(0)){case "#":return b("hash").slice(r.length);case "?":return b("search").slice(r.length)+b("hash");default:return b("pathname").slice(r.length)+b("search")+b("hash")}}function h(b,h,g){var k={},n={};b=d(b,k,n);if(null!=h){for(var l in h)k[l]=h[l];b=b.replace(/:([^\/]+)/g,function(a,b){delete k[b];return h[b]})}(l=C(k))&&(b+="?"+l);(n=C(n))&&(b+="#"+n);m?(g&&g.replace?a.history.replaceState(null,null,r+b):a.history.pushState(null,null,
|
||||
r+b),a.onpopstate()):a.location.href=r+b}var m="function"===typeof a.history.pushState,p="function"===typeof setImmediate?setImmediate:setTimeout,r="#!",t;return{setPrefix:function(a){r=a},getPath:l,setPath:h,defineRoutes:function(b,h,g){function q(){var a=l(),k={},m=d(a,k,k),q;for(q in b){var p=new RegExp("^"+q.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(p.test(m)){m.replace(p,function(){for(var d=q.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),l=0;l<d.length;l++)k[d[l].replace(/:|\./g,
|
||||
"")]=decodeURIComponent(g[l]);h(b[q],k,a,q)});return}}g(a,k)}m?a.onpopstate=k(q):"#"===r.charAt(0)&&(a.onhashchange=q);q();return q},link:function(a){a.dom.setAttribute("href",r+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(r)&&(a=a.slice(r.length)),h(a,void 0,void 0))}}}};t.route=function(a,b){function k(a){return a}var d=S(a),l,h,t,p,r,y={view:function(){return[t(m(h,null,p,void 0,
|
||||
void 0,void 0))]}},u=function(a,g,m){h="div";t=k;p=null;b(a,y);d.defineRoutes(m,function(b,d,g){var m="function"!==typeof b.view,n=k,q=l=function(k){q===l&&(l=null,h=null!=k?k:m?"div":b,t=n,p=d,r=g,a.redraw(!0))},u=function(){q()};m&&("function"===typeof b.render&&(n=b.render.bind(b)),"function"===typeof b.onmatch&&(u=b.onmatch));u.call(b,q,d,g)},function(){d.setPath(g,null,{replace:!0})})};u.link=d.link;u.prefix=d.setPrefix;u.set=d.setPath;u.get=function(){return r};return u}(window,t.mount);t.withAttr=
|
||||
function(a,b,k){return function(d){return b.call(k||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};t.render=N.render;t.redraw=J.publish;t.request=I.request;t.jsonp=I.jsonp;t.parseQueryString=K;t.buildQueryString=C;t.version="1.0.0-rc.5";"undefined"!==typeof module?module.exports=t:window.m=t};
|
||||
new function(){function r(a,c,h,d,g,l){return{tag:a,key:c,attrs:h,children:d,text:g,dom:l,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function y(a){if(null==a||"string"!==typeof a&&null==a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===I[a]){for(var c,h,d=[],g={};c=P.exec(a);){var l=c[1],m=c[2];""===l&&""!==m?h=m:"#"===l?g.id=m:"."===l?d.push(m):"["===c[3][0]&&((l=c[6])&&(l=l.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),
|
||||
"class"===c[4]?d.push(l):g[c[4]]=l||!0)}0<d.length&&(g.className=d.join(" "));I[a]=function(a,c){var l=!1,b,d,v=a.className||a["class"],u;for(u in g)a[u]=g[u];void 0!==v&&(void 0!==a["class"]&&(a["class"]=void 0,a.className=v),void 0!==g.className&&(a.className=g.className+" "+v));for(u in a)if("key"!==u){l=!0;break}Array.isArray(c)&&1==c.length&&null!=c[0]&&"#"===c[0].tag?d=c[0].children:b=c;return r(h||"div",a.key,l?a:void 0,b,d,void 0)}}var A;null==arguments[1]||"object"===typeof arguments[1]&&
|
||||
void 0===arguments[1].tag&&!Array.isArray(arguments[1])?(A=arguments[1],d=2):d=1;if(arguments.length===d+1)c=Array.isArray(arguments[d])?arguments[d]:[arguments[d]];else for(c=[];d<arguments.length;d++)c.push(arguments[d]);return"string"===typeof a?I[a](A||{},r.normalizeChildren(c)):r(a,A&&A.key,A||{},r.normalizeChildren(c),void 0,void 0)}function Q(a){var c=0,h=null,d="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var g=Date.now();0===c||16<=g-c?(c=g,
|
||||
a()):null===h&&(h=d(function(){h=null;a();c=Date.now()},16-(g-c)))}}r.normalize=function(a){return Array.isArray(a)?r("[",void 0,void 0,r.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?r("#",void 0,void 0,a,void 0,void 0):a};r.normalizeChildren=function(a){for(var c=0;c<a.length;c++)a[c]=r.normalize(a[c]);return a};var P=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,I={};y.trust=function(a){null==a&&(a="");return r("<",void 0,void 0,a,void 0,void 0)};
|
||||
y.fragment=function(a,c){return r("[",a.key,a,r.normalizeChildren(c),void 0,void 0)};var t=function(a){function c(a,b){return function v(c){var k;try{if(!b||null==c||"object"!==typeof c&&"function"!==typeof c||"function"!==typeof(k=c.then))G(function(){b||0!==a.length||console.error("Possible unhandled promise rejection:",c);for(var d=0;d<a.length;d++)a[d](c);g.length=0;l.length=0;n.state=b;n.retry=function(){v(c)}});else{if(c===d)throw new TypeError("Promise can't be resolved w/ itself");h(k.bind(c))}}catch(C){A(C)}}}
|
||||
function h(a){function b(b){return function(a){0<c++||b(a)}}var c=0,d=b(A);try{a(b(m),d)}catch(u){d(u)}}if(!(this instanceof t))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");var d=this,g=[],l=[],m=c(g,!0),A=c(l,!1),n=d._instance={resolvers:g,rejectors:l},G="function"===typeof setImmediate?setImmediate:setTimeout;h(a)};t.prototype.then=function(a,c){function h(a,c,h,k){c.push(function(b){if("function"!==typeof a)h(b);else try{g(a(b))}catch(M){l&&
|
||||
l(M)}});"function"===typeof d.retry&&k===d.state&&d.retry()}var d=this._instance,g,l,m=new t(function(a,c){g=a;l=c});h(a,d.resolvers,g,!0);h(c,d.rejectors,l,!1);return m};t.prototype["catch"]=function(a){return this.then(null,a)};t.resolve=function(a){return a instanceof t?a:new t(function(c){c(a)})};t.reject=function(a){return new t(function(c,h){h(a)})};t.all=function(a){return new t(function(c,h){var d=a.length,g=0,l=[];if(0===a.length)c([]);else for(var m=0;m<a.length;m++)(function(m){function n(a){g++;
|
||||
l[m]=a;g===d&&c(l)}null==a[m]||"object"!==typeof a[m]&&"function"!==typeof a[m]||"function"!==typeof a[m].then?n(a[m]):a[m].then(n,h)})(m)})};t.race=function(a){return new t(function(c,h){for(var d=0;d<a.length;d++)a[d].then(c,h)})};"undefined"===typeof Promise&&("undefined"!==typeof window?window.Promise=t:"undefined"!==typeof global&&(global.Promise=t));var E=function(a){function c(a,d){if(Array.isArray(d))for(var g=0;g<d.length;g++)c(a+"["+g+"]",d[g]);else if("[object Object]"===Object.prototype.toString.call(d))for(g in d)c(a+
|
||||
"["+g+"]",d[g]);else h.push(encodeURIComponent(a)+(null!=d&&""!==d?"="+encodeURIComponent(d):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var h=[],d;for(d in a)c(d,a[d]);return h.join("&")},K=function(a,c){function h(){function b(){0===--a&&"function"===typeof k&&k()}var a=0;return function u(c){var d=c.then;c.then=function(){a++;var g=d.apply(c,arguments);g.then(b,function(a){b();throw a;});return u(g)};return c}}function d(b,a){if("string"===typeof b){var c=b;b=a||{};null==
|
||||
b.url&&(b.url=c)}return b}function g(b,a){if(null==a)return b;for(var c=b.match(/:[^\/]+/gi)||[],d=0;d<c.length;d++){var g=c[d].slice(1);null!=a[g]&&(b=b.replace(c[d],a[g]),delete a[g])}return b}function l(a,c){var b=E(c);if(""!==b){var d=0>a.indexOf("?")?"?":"&";a+=d+b}return a}function m(a){try{return""!==a?JSON.parse(a):null}catch(M){throw Error(a);}}function A(a){return a.responseText}function n(a,c){if("function"===typeof a)if(Array.isArray(c))for(var b=0;b<c.length;b++)c[b]=new a(c[b]);else return new a(c);
|
||||
return c}var r=0,k;return{request:function(b,k){var v=h();b=d(b,k);var u=new c(function(c,d){null==b.method&&(b.method="GET");b.method=b.method.toUpperCase();var h="boolean"===typeof b.useBody?b.useBody:"GET"!==b.method&&"TRACE"!==b.method;"function"!==typeof b.serialize&&(b.serialize="undefined"!==typeof FormData&&b.data instanceof FormData?function(a){return a}:JSON.stringify);"function"!==typeof b.deserialize&&(b.deserialize=m);"function"!==typeof b.extract&&(b.extract=A);b.url=g(b.url,b.data);
|
||||
h?b.data=b.serialize(b.data):b.url=l(b.url,b.data);var k=new a.XMLHttpRequest;k.open(b.method,b.url,"boolean"===typeof b.async?b.async:!0,"string"===typeof b.user?b.user:void 0,"string"===typeof b.password?b.password:void 0);b.serialize===JSON.stringify&&h&&k.setRequestHeader("Content-Type","application/json; charset=utf-8");b.deserialize===m&&k.setRequestHeader("Accept","application/json, text/*");b.withCredentials&&(k.withCredentials=b.withCredentials);"function"===typeof b.config&&(k=b.config(k,
|
||||
b)||k);k.onreadystatechange=function(){if(4===k.readyState)try{var a=b.extract!==A?b.extract(k,b):b.deserialize(b.extract(k,b));if(200<=k.status&&300>k.status||304===k.status)c(n(b.type,a));else{var g=Error(k.responseText),h;for(h in a)g[h]=a[h];d(g)}}catch(F){d(F)}};h&&null!=b.data?k.send(b.data):k.send()});return!0===b.background?u:v(u)},jsonp:function(b,k){var m=h();b=d(b,k);var u=new c(function(c,d){var k=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+r++,h=a.document.createElement("script");
|
||||
a[k]=function(d){h.parentNode.removeChild(h);c(n(b.type,d));delete a[k]};h.onerror=function(){h.parentNode.removeChild(h);d(Error("JSONP request failed"));delete a[k]};null==b.data&&(b.data={});b.url=g(b.url,b.data);b.data[b.callbackKey||"callback"]=k;h.src=l(b.url,b.data);a.document.documentElement.appendChild(h)});return!0===b.background?u:m(u)},setCompletionCallback:function(a){k=a}}}(window,"undefined"!==typeof Promise?Promise:t),O=function(a){function c(e,f,a,b,c,d,g){for(;a<b;a++){var q=f[a];
|
||||
null!=q&&n(e,h(q,c,g),d)}}function h(e,f,a){var q=e.tag;null!=e.attrs&&J(e.attrs,e,f);if("string"===typeof q)switch(q){case "#":return e.dom=B.createTextNode(e.children);case "<":return d(e);case "[":var b=B.createDocumentFragment();null!=e.children&&(q=e.children,c(b,q,0,q.length,f,null,a));e.dom=b.firstChild;e.domSize=b.childNodes.length;return b;default:var g=e.tag;switch(e.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var k=(q=e.attrs)&&
|
||||
q.is,g=a?k?B.createElementNS(a,g,{is:k}):B.createElementNS(a,g):k?B.createElement(g,{is:k}):B.createElement(g);e.dom=g;if(null!=q)for(b in k=a,q)u(e,b,null,q[b],k);null!=e.attrs&&null!=e.attrs.contenteditable?G(e):(null!=e.text&&(""!==e.text?g.textContent=e.text:e.children=[r("#",void 0,void 0,e.text,void 0,void 0)]),null!=e.children&&(b=e.children,c(g,b,0,b.length,f,null,a),f=e.attrs,"select"===e.tag&&null!=f&&("value"in f&&u(e,"value",null,f.value,void 0),"selectedIndex"in f&&u(e,"selectedIndex",
|
||||
null,f.selectedIndex,void 0))));return g}else{e.state||(e.state={});E(e.state,e.tag);b=e.tag.view;if(null!=b.reentrantLock)e=N;else if(b.reentrantLock=!0,J(e.tag,e,f),e.instance=r.normalize(b.call(e.state,e)),b.reentrantLock=null,null!=e.instance){if(e.instance===e)throw Error("A view cannot return the vnode it received as arguments");f=h(e.instance,f,a);e.dom=e.instance.dom;e.domSize=null!=e.dom?e.instance.domSize:0;e=f}else e.domSize=0,e=N;return e}}function d(e){var f={caption:"table",thead:"table",
|
||||
tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(e.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",f=B.createElement(f);f.innerHTML=e.children;e.dom=f.firstChild;e.domSize=f.childNodes.length;e=B.createDocumentFragment();for(var a;a=f.firstChild;)e.appendChild(a);return e}function g(e,f,a,b,d,g){if(f!==a&&(null!=f||null!=a))if(null==f)c(e,a,0,a.length,b,d,void 0);else if(null==a)k(f,0,f.length,a);else{for(var q=!1,w=0;w<a.length;w++)if(null!=a[w]){q=null==a[w].key;
|
||||
break}if(f.length===a.length&&q)for(w=0;w<f.length;w++)f[w]!==a[w]&&(null==f[w]?n(e,h(a[w],b,g),A(f,w+1,d)):null==a[w]?k(f,w,w+1,a):l(e,f[w],a[w],b,A(f,w+1,d),!1,g));else{a:{if(null!=f.pool&&Math.abs(f.pool.length-a.length)<=Math.abs(f.length-a.length)&&(q=a[0]&&a[0].children&&a[0].children.length||0,Math.abs((f.pool[0]&&f.pool[0].children&&f.pool[0].children.length||0)-q)<=Math.abs((f[0]&&f[0].children&&f[0].children.length||0)-q))){q=!0;break a}q=!1}q&&(f=f.concat(f.pool));for(var u=w=0,z=f.length-
|
||||
1,v=a.length-1,D;z>=w&&v>=u;){var x=f[w],p=a[u];if(x!==p||q)if(null==x)w++;else if(null==p)u++;else if(x.key===p.key)w++,u++,l(e,x,p,b,A(f,w,d),q,g),q&&x.tag===p.tag&&n(e,m(x),d);else if(x=f[z],x!==p||q)if(null==x)z--;else if(null==p)u++;else if(x.key===p.key)l(e,x,p,b,A(f,z+1,d),q,g),(q||u<v)&&n(e,m(x),A(f,w,d)),z--,u++;else break;else z--,u++;else w++,u++}for(;z>=w&&v>=u;){x=f[z];p=a[v];if(x!==p||q)if(null==x)z--;else{if(null!=p)if(x.key===p.key)l(e,x,p,b,A(f,z+1,d),q,g),q&&x.tag===p.tag&&n(e,m(x),
|
||||
d),null!=x.dom&&(d=x.dom),z--;else{if(!D){D=f;var x=z,r={},C;for(C=0;C<x;C++){var t=D[C];null!=t&&(t=t.key,null!=t&&(r[t]=C))}D=r}null!=p&&(x=D[p.key],null!=x?(r=f[x],l(e,r,p,b,A(f,z+1,d),q,g),n(e,m(r),d),f[x].skip=!0,null!=r.dom&&(d=r.dom)):(p=h(p,b,void 0),n(e,p,d),d=p))}v--}else z--,v--;if(v<u)break}c(e,a,u,v+1,b,d,g);k(f,w,z+1,a)}}}function l(a,f,b,c,k,z,v){var e=f.tag;if(e===b.tag){b.state=f.state;b.events=f.events;var q;var w;null!=b.attrs&&"function"===typeof b.attrs.onbeforeupdate&&(q=b.attrs.onbeforeupdate.call(b.state,
|
||||
b,f));"string"!==typeof b.tag&&"function"===typeof b.tag.onbeforeupdate&&(w=b.tag.onbeforeupdate.call(b.state,b,f));void 0===q&&void 0===w||q||w?q=!1:(b.dom=f.dom,b.domSize=f.domSize,b.instance=f.instance,q=!0);if(!q)if(null!=b.attrs&&y(b.attrs,b,c,z),"string"===typeof e)switch(e){case "#":f.children.toString()!==b.children.toString()&&(f.dom.nodeValue=b.children);b.dom=f.dom;break;case "<":f.children!==b.children?(m(f),n(a,d(b),k)):(b.dom=f.dom,b.domSize=f.domSize);break;case "[":g(a,f.children,
|
||||
b.children,c,k,v);f=0;c=b.children;b.dom=null;if(null!=c){for(var p=0;p<c.length;p++)a=c[p],null!=a&&null!=a.dom&&(null==b.dom&&(b.dom=a.dom),f+=a.domSize||1);1!==f&&(b.domSize=f)}break;default:a=v;k=b.dom=f.dom;switch(b.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===b.tag&&(null==b.attrs&&(b.attrs={}),null!=b.text&&(b.attrs.value=b.text,b.text=void 0));z=f.attrs;v=b.attrs;e=a;if(null!=v)for(p in v)u(b,p,z&&z[p],v[p],e);if(null!=
|
||||
z)for(p in z)null!=v&&p in v||("className"===p&&(p="class"),"o"!==p[0]||"n"!==p[1]||D(p)?"key"!==p&&b.dom.removeAttribute(p):C(b,p,void 0));null!=b.attrs&&null!=b.attrs.contenteditable?G(b):null!=f.text&&null!=b.text&&""!==b.text?f.text.toString()!==b.text.toString()&&(f.dom.firstChild.nodeValue=b.text):(null!=f.text&&(f.children=[r("#",void 0,void 0,f.text,void 0,f.dom.firstChild)]),null!=b.text&&(b.children=[r("#",void 0,void 0,b.text,void 0,void 0)]),g(k,f.children,b.children,c,null,a))}else b.instance=
|
||||
r.normalize(b.tag.view.call(b.state,b)),y(b.tag,b,c,z),null!=b.instance?(null==f.instance?n(a,h(b.instance,c,v),k):l(a,f.instance,b.instance,c,k,z,v),b.dom=b.instance.dom,b.domSize=b.instance.domSize):null!=f.instance?(t(f.instance,null),b.dom=void 0,b.domSize=0):(b.dom=f.dom,b.domSize=f.domSize)}else t(f,null),n(a,h(b,c,v),k)}function m(b){var a=b.domSize;if(null!=a||null==b.dom){var e=B.createDocumentFragment();if(0<a){for(b=b.dom;--a;)e.appendChild(b.nextSibling);e.insertBefore(b,e.firstChild)}return e}return b.dom}
|
||||
function A(b,a,c){for(;a<b.length;a++)if(null!=b[a]&&null!=b[a].dom)return b[a].dom;return c}function n(b,a,c){c&&c.parentNode?b.insertBefore(a,c):b.appendChild(a)}function G(b){var a=b.children;if(null!=a&&1===a.length&&"<"===a[0].tag)a=a[0].children,b.dom.innerHTML!==a&&(b.dom.innerHTML=a);else if(null!=b.text||null!=a&&0!==a.length)throw Error("Child node of a contenteditable must be trusted");}function k(b,a,c,d){for(;a<c;a++){var e=b[a];null!=e&&(e.skip?e.skip=!1:t(e,d))}}function b(b){var a=
|
||||
!1;return function(){a||(a=!0,b())}}function t(a,f){function e(){if(++d===c&&(v(a),a.dom)){var b=a.domSize||1;if(1<b)for(var e=a.dom;--b;){var g=e.nextSibling,k=g.parentNode;null!=k&&k.removeChild(g)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=f&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(f.pool?f.pool.push(a):f.pool=[a])}}var c=1,d=0;a.attrs&&a.attrs.onbeforeremove&&(c++,a.attrs.onbeforeremove.call(a.state,
|
||||
a,b(e)));"string"!==typeof a.tag&&a.tag.onbeforeremove&&(c++,a.tag.onbeforeremove.call(a.state,a,b(e)));e()}function v(a){a.attrs&&a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&a.tag.onremove&&a.tag.onremove.call(a.state,a);if(null!=a.instance)v(a.instance);else if(a=a.children,Array.isArray(a))for(var b=0;b<a.length;b++){var e=a[b];null!=e&&v(e)}}function u(a,b,c,d,g){var e=a.dom;if("key"!==b&&(c!==d||"value"===b||"checked"===b||"selectedIndex"===b||"selected"===b&&
|
||||
a.dom===B.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!D(b)){var f=b.indexOf(":");if(-1<f&&"xlink"===b.substr(0,f))e.setAttributeNS("http://www.w3.org/1999/xlink",b.slice(f+1),d);else if("o"===b[0]&&"n"===b[1]&&"function"===typeof d)C(a,b,d);else if("style"===b)if(a=c,a===d&&(e.style.cssText="",a=null),null==d)e.style.cssText="";else if("string"===typeof d)e.style.cssText=d;else{"string"===typeof a&&(e.style.cssText="");for(var k in d)e.style[k]=d[k];if(null!=a&&"string"!==typeof a)for(k in a)k in
|
||||
d||(e.style[k]="")}else b in e&&"href"!==b&&"list"!==b&&"form"!==b&&"width"!==b&&"height"!==b&&void 0===g?"input"===a.tag&&"value"===b&&a.dom.value===d&&a.dom===B.activeElement||"select"===a.tag&&"value"===b&&a.dom.value===d&&a.dom===B.activeElement||"option"===a.tag&&"value"===b&&a.dom.value===d||(e[b]=d):"boolean"===typeof d?d?e.setAttribute(b,""):e.removeAttribute(b):e.setAttribute("className"===b?"class":b,d)}}function D(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===
|
||||
a||"onbeforeupdate"===a}function C(a,b,c){var d=a.dom,e="function"!==typeof F?c:function(a){var b=c.call(d,a);F.call(d,a);return b};if(b in d)d[b]="function"===typeof c?e:null;else{var f=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==e&&(null!=a.events[b]&&d.removeEventListener(f,a.events[b],!1),"function"===typeof c&&(a.events[b]=e,d.addEventListener(f,a.events[b],!1)))}}function J(a,b,c){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&c.push(a.oncreate.bind(b.state,
|
||||
b))}function y(a,b,c,d){d?J(a,b,c):"function"===typeof a.onupdate&&c.push(a.onupdate.bind(b.state,b))}function E(a,b){Object.keys(b).forEach(function(c){a[c]=b[c]})}var B=a.document,N=B.createDocumentFragment(),F;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=B.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);g(a,a.vnodes,r.normalizeChildren(b),c,null,void 0);a.vnodes=b;for(var e=
|
||||
0;e<c.length;e++)c[e]();B.activeElement!==d&&d.focus()},setEventCallback:function(a){return F=a}}},H=function(a){function c(a){a=d.indexOf(a);-1<a&&d.splice(a,2)}function h(){for(var a=1;a<d.length;a+=2)d[a]()}a=O(a);a.setEventCallback(function(a){!1!==a.redraw&&h()});var d=[];return{subscribe:function(a,h){c(a);d.push(a,Q(h))},unsubscribe:c,redraw:h,render:a.render}}(window);K.setCompletionCallback(H.redraw);y.mount=function(a){return function(c,h){if(null===h)a.render(c,[]),a.unsubscribe(c);else{if(null==
|
||||
h.view)throw Error("m.mount(element, component) expects a component, not a vnode");a.subscribe(c,function(){a.render(c,r(h))});a.redraw()}}}(H);var L=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var c={},h={},d=0;d<a.length;d++){var g=a[d].split("="),l=decodeURIComponent(g[0]),g=2===g.length?decodeURIComponent(g[1]):"";"true"===g?g=!0:"false"===g&&(g=!1);var m=l.split(/\]\[?|\[/),r=c;-1<l.indexOf("[")&&m.pop();for(var n=0;n<m.length;n++){var l=m[n],
|
||||
t=m[n+1],t=""==t||!isNaN(parseInt(t,10)),k=n===m.length-1;""===l&&(l=m.slice(0,n).join(),null==h[l]&&(h[l]=0),l=h[l]++);null==r[l]&&(r[l]=k?g:t?[]:{});r=r[l]}}return c},R=function(a){function c(c){var b=a.location[c].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===c&&"/"!==b[0]&&(b="/"+b);return b}function h(a){return function(){null==t&&(t=r(function(){t=null;a()}))}}function d(a,b,c){var d=a.indexOf("?"),g=a.indexOf("#"),k=-1<d?d:-1<g?g:a.length;if(-1<d){var d=L(a.slice(d+1,
|
||||
-1<g?g:a.length)),h;for(h in d)b[h]=d[h]}if(-1<g)for(h in b=L(a.slice(g+1)),b)c[h]=b[h];return a.slice(0,k)}function g(){switch(n.charAt(0)){case "#":return c("hash").slice(n.length);case "?":return c("search").slice(n.length)+c("hash");default:return c("pathname").slice(n.length)+c("search")+c("hash")}}function l(c,b,g){var k={},h={};c=d(c,k,h);if(null!=b){for(var l in b)k[l]=b[l];c=c.replace(/:([^\/]+)/g,function(a,c){delete k[c];return b[c]})}(l=E(k))&&(c+="?"+l);(h=E(h))&&(c+="#"+h);m?(g&&g.replace?
|
||||
a.history.replaceState(null,null,n+c):a.history.pushState(null,null,n+c),a.onpopstate(!0)):a.location.href=n+c}var m="function"===typeof a.history.pushState,r="function"===typeof setImmediate?setImmediate:setTimeout,n="#!",t;return{setPrefix:function(a){n=a},getPath:g,setPath:l,defineRoutes:function(c,b,l){function k(){var a=g(),h={},k=d(a,h,h),m;for(m in c){var n=new RegExp("^"+m.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(n.test(k)){k.replace(n,function(){for(var d=
|
||||
m.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),k=0;k<d.length;k++)h[d[k].replace(/:|\./g,"")]=decodeURIComponent(g[k]);b(c[m],h,a,m)});return}}l(a,h)}m?a.onpopstate=h(k):"#"===n.charAt(0)&&(a.onhashchange=k);k()},link:function(a){a.dom.setAttribute("href",n+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(n)&&(a=a.slice(n.length)),l(a,void 0,void 0))}}}};y.route=function(a,c){var h=
|
||||
R(a),d=function(a){return a},g,l,m,t,n,y=function(a,b,y){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var k=function(a,b,c,h){g=a;l=b;m=c;t=h;n=null;g.render=a.render||d;u()},u=function(){null!=g&&c.render(a,g.render(r(l,m.key,m)))};h.defineRoutes(y,function(a,b,c){a.view?k({},a,b,c):a.onmatch?null!=n?k(a,l,b,c):(n=function(d){k(a,d,b,c)},a.onmatch(function(a){null!=n&&n(a)},b,c)):k(a,"div",b,c)},function(){h.setPath(b)});c.subscribe(a,u)};y.set=h.setPath;
|
||||
y.get=function(){return t};y.prefix=h.setPrefix;y.link=h.link;return y}(window,H);y.withAttr=function(a,c,h){return function(d){return c.call(h||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};var S=O(window);y.render=S.render;y.redraw=H.redraw;y.request=K.request;y.jsonp=K.jsonp;y.parseQueryString=L;y.buildQueryString=E;y.version="1.0.0-rc.6";"undefined"!==typeof module?module.exports=y:window.m=y};
|
||||
3
mount.js
3
mount.js
|
|
@ -1,4 +1,3 @@
|
|||
var renderService = require("./render")
|
||||
var redrawService = require("./redraw")
|
||||
|
||||
module.exports = require("./api/mount")(renderService, redrawService)
|
||||
module.exports = require("./api/mount")(redrawService)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mithril",
|
||||
"version": "1.0.0-rc.5",
|
||||
"version": "1.0.0-rc.6",
|
||||
"description": "A framework for building brilliant applications",
|
||||
"author": "Leo Horie",
|
||||
"license": "MIT",
|
||||
|
|
@ -12,13 +12,15 @@
|
|||
"build-browser": "node bundler/cli browser.js -o mithril.js",
|
||||
"build-min": "node bundler/cli browser.js -o mithril.min.js -m",
|
||||
"lintdocs": "node docs/lint",
|
||||
"gendocs": "node docs/generate",
|
||||
"lint": "eslint .",
|
||||
"test": "node ospec/bin/ospec",
|
||||
"cover": "istanbul cover --print both ospec/bin/ospec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^2.10.2",
|
||||
"istanbul": "^0.4.3"
|
||||
"istanbul": "^0.4.3",
|
||||
"marked": "^0.3.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"tag": "rewrite"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
module.exports = require("./api/pubsub")()
|
||||
module.exports = require("./api/redraw")(window)
|
||||
|
|
@ -124,9 +124,16 @@ module.exports = function($window) {
|
|||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
|
||||
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
||||
else {
|
||||
if (old.length === vnodes.length && vnodes[0] != null && vnodes[0].key == null) {
|
||||
var isUnkeyed = false
|
||||
for (var i = 0; i < vnodes.length; i++) {
|
||||
if (vnodes[i] != null) {
|
||||
isUnkeyed = vnodes[i].key == null
|
||||
break
|
||||
}
|
||||
}
|
||||
if (old.length === vnodes.length && isUnkeyed) {
|
||||
for (var i = 0; i < old.length; i++) {
|
||||
if (old[i] === vnodes[i] || old[i] == null && vnodes[i] == null) continue
|
||||
if (old[i] === vnodes[i]) continue
|
||||
else if (old[i] == null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling))
|
||||
else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
|
||||
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
|
||||
|
|
@ -508,15 +515,16 @@ module.exports = function($window) {
|
|||
//event
|
||||
function updateEvent(vnode, key, value) {
|
||||
var element = vnode.dom
|
||||
var callback = function(e) {
|
||||
var callback = typeof onevent !== "function" ? value : function(e) {
|
||||
var result = value.call(element, e)
|
||||
if (typeof onevent === "function") onevent.call(element, e)
|
||||
onevent.call(element, e)
|
||||
return result
|
||||
}
|
||||
if (key in element) element[key] = typeof value === "function" ? callback : null
|
||||
else {
|
||||
var eventName = key.slice(2)
|
||||
if (vnode.events === undefined) vnode.events = {}
|
||||
if (vnode.events[key] === callback) return
|
||||
if (vnode.events[key] != null) element.removeEventListener(eventName, vnode.events[key], false)
|
||||
if (typeof value === "function") {
|
||||
vnode.events[key] = callback
|
||||
|
|
|
|||
|
|
@ -879,4 +879,42 @@ o.spec("updateNodes", function() {
|
|||
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("null stays in place", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
var remove = o.spy()
|
||||
var vnodes = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var temp = [null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var updated = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
|
||||
render(root, vnodes)
|
||||
var before = vnodes[1].dom
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
var after = updated[1].dom
|
||||
|
||||
o(before).equals(after)
|
||||
o(create.callCount).equals(1)
|
||||
o(update.callCount).equals(2)
|
||||
o(remove.callCount).equals(0)
|
||||
})
|
||||
o("null stays in place if not first", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
var remove = o.spy()
|
||||
var vnodes = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var temp = [{tag: "b"}, null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var updated = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
|
||||
render(root, vnodes)
|
||||
var before = vnodes[2].dom
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
var after = updated[2].dom
|
||||
|
||||
o(before).equals(after)
|
||||
o(create.callCount).equals(1)
|
||||
o(update.callCount).equals(2)
|
||||
o(remove.callCount).equals(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,33 +5,40 @@ var buildQueryString = require("../querystring/build")
|
|||
module.exports = function($window, Promise) {
|
||||
var callbackCount = 0
|
||||
|
||||
var count = 0
|
||||
var oncompletion
|
||||
function setCompletionCallback(callback) {oncompletion = callback}
|
||||
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
||||
function finalizer() {
|
||||
var count = 0
|
||||
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
||||
|
||||
function finalize(promise) {
|
||||
var then = promise.then
|
||||
promise.then = function() {
|
||||
count++
|
||||
var next = then.apply(promise, arguments)
|
||||
next.then(complete, function(e) {
|
||||
complete()
|
||||
throw e
|
||||
})
|
||||
return finalize(next)
|
||||
return function finalize(promise) {
|
||||
var then = promise.then
|
||||
promise.then = function() {
|
||||
count++
|
||||
var next = then.apply(promise, arguments)
|
||||
next.then(complete, function(e) {
|
||||
complete()
|
||||
throw e
|
||||
})
|
||||
return finalize(next)
|
||||
}
|
||||
return promise
|
||||
}
|
||||
return promise
|
||||
}
|
||||
function normalize(args, extra) {
|
||||
if (typeof args === "string") {
|
||||
var url = args
|
||||
args = extra || {}
|
||||
if (args.url == null) args.url = url
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
function request(args, extra) {
|
||||
return finalize(new Promise(function(resolve, reject) {
|
||||
if (typeof args === "string") {
|
||||
var url = args
|
||||
args = extra || {}
|
||||
if (args.url == null) args.url = url
|
||||
}
|
||||
var finalize = finalizer()
|
||||
args = normalize(args, extra)
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
if (args.method == null) args.method = "GET"
|
||||
args.method = args.method.toUpperCase()
|
||||
|
||||
|
|
@ -79,11 +86,15 @@ module.exports = function($window, Promise) {
|
|||
|
||||
if (useBody && (args.data != null)) xhr.send(args.data)
|
||||
else xhr.send()
|
||||
}))
|
||||
})
|
||||
return args.background === true ? promise : finalize(promise)
|
||||
}
|
||||
|
||||
function jsonp(args) {
|
||||
return finalize(new Promise(function(resolve, reject) {
|
||||
function jsonp(args, extra) {
|
||||
var finalize = finalizer()
|
||||
args = normalize(args, extra)
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
|
||||
var script = $window.document.createElement("script")
|
||||
$window[callbackName] = function(data) {
|
||||
|
|
@ -101,7 +112,8 @@ module.exports = function($window, Promise) {
|
|||
args.data[args.callbackKey || "callback"] = callbackName
|
||||
script.src = assemble(args.url, args.data)
|
||||
$window.document.documentElement.appendChild(script)
|
||||
}))
|
||||
})
|
||||
return args.background === true? promise : finalize(promise)
|
||||
}
|
||||
|
||||
function interpolate(url, data) {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ var Promise = require("../../promise/promise")
|
|||
var parseQueryString = require("../../querystring/parse")
|
||||
|
||||
o.spec("jsonp", function() {
|
||||
var mock, jsonp, spy
|
||||
var mock, jsonp, spy, complete
|
||||
o.beforeEach(function() {
|
||||
mock = xhrMock()
|
||||
jsonp = new Request(mock, Promise).jsonp
|
||||
var requestService = Request(mock, Promise)
|
||||
jsonp = requestService.jsonp
|
||||
complete = o.spy()
|
||||
requestService.setCompletionCallback(complete)
|
||||
})
|
||||
|
||||
o("works", function(done) {
|
||||
|
|
@ -24,6 +27,20 @@ o.spec("jsonp", function() {
|
|||
o(data).deepEquals({a: 1})
|
||||
}).then(done)
|
||||
})
|
||||
o("first argument can be a string aliasing url property", function(done){
|
||||
var s = new Date
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
var queryData = parseQueryString(request.query)
|
||||
return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 1}) + ")"}
|
||||
}
|
||||
})
|
||||
jsonp("/item").then(function(data) {
|
||||
o(data).deepEquals({a: 1})
|
||||
}).then(function() {
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("works w/ other querystring params", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
|
|
@ -47,6 +64,64 @@ o.spec("jsonp", function() {
|
|||
o(data).deepEquals({a: 2})
|
||||
}).then(done)
|
||||
})
|
||||
o("works w/ custom callbackKey", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
var queryData = parseQueryString(request.query)
|
||||
return {status: 200, responseText: queryData["cb"] + "(" + JSON.stringify({a: 2}) + ")"}
|
||||
}
|
||||
})
|
||||
jsonp({url: "/item", callbackKey: "cb"}).then(function(data) {
|
||||
o(data).deepEquals({a: 2})
|
||||
}).then(done)
|
||||
})
|
||||
o("requests don't block each other", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
var queryData = parseQueryString(request.query)
|
||||
return {status: 200, responseText: queryData["callback"] + "([])"}
|
||||
}
|
||||
})
|
||||
jsonp("/item").then(function() {
|
||||
return jsonp("/item")
|
||||
})
|
||||
jsonp("/item").then(function() {
|
||||
return jsonp("/item")
|
||||
})
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(4)
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
o("requests trigger finally once with a chained then", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
var queryData = parseQueryString(request.query)
|
||||
return {status: 200, responseText: queryData["callback"] + "([])"}
|
||||
}
|
||||
})
|
||||
var promise = jsonp("/item")
|
||||
promise.then(function() {}).then(function() {})
|
||||
promise.then(function() {}).then(function() {})
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(1)
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
o("requests does not trigger finally when background: true", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
var queryData = parseQueryString(request.query)
|
||||
return {status: 200, responseText: queryData["callback"] + "([])"}
|
||||
}
|
||||
})
|
||||
var promise = jsonp("/item", {background: true}).then(function() {})
|
||||
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(0)
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
o("handles error", function(done) {
|
||||
jsonp({url: "/item", callbackKey: "cb"}).catch(function(e) {
|
||||
o(e.message).equals("JSONP request failed")
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ var Request = require("../../request/request")
|
|||
var Promise = require("../../promise/promise")
|
||||
|
||||
o.spec("xhr", function() {
|
||||
var mock, xhr
|
||||
var mock, xhr, complete
|
||||
o.beforeEach(function() {
|
||||
mock = xhrMock()
|
||||
xhr = new Request(mock, Promise).request
|
||||
var requestService = Request(mock, Promise)
|
||||
xhr = requestService.request
|
||||
complete = o.spy()
|
||||
requestService.setCompletionCallback(complete)
|
||||
})
|
||||
|
||||
o.spec("success", function() {
|
||||
|
|
@ -295,6 +298,50 @@ o.spec("xhr", function() {
|
|||
o(typeof xhr.send).equals("function")
|
||||
}
|
||||
})
|
||||
o("requests don't block each other", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
return {status: 200, responseText: "[]"}
|
||||
}
|
||||
})
|
||||
xhr("/item").then(function() {
|
||||
return xhr("/item")
|
||||
})
|
||||
xhr("/item").then(function() {
|
||||
return xhr("/item")
|
||||
})
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(4)
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
o("requests trigger finally once with a chained then", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
return {status: 200, responseText: "[]"}
|
||||
}
|
||||
})
|
||||
var promise = xhr("/item")
|
||||
promise.then(function() {}).then(function() {})
|
||||
promise.then(function() {}).then(function() {})
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(1)
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
o("requests does not trigger finally when background: true", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
return {status: 200, responseText: "[]"}
|
||||
}
|
||||
})
|
||||
var promise = xhr("/item", {background: true}).then(function() {})
|
||||
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(0)
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
o.spec("failure", function() {
|
||||
o("rejects on server error", function(done) {
|
||||
|
|
|
|||
4
route.js
4
route.js
|
|
@ -1,3 +1,3 @@
|
|||
var mount = require("./mount")
|
||||
var redrawService = require("./redraw")
|
||||
|
||||
module.exports = require("./api/router")(window, mount)
|
||||
module.exports = require("./api/router")(window, redrawService)
|
||||
|
|
@ -73,16 +73,12 @@ module.exports = function($window) {
|
|||
if (supportsPushState) {
|
||||
if (options && options.replace) $window.history.replaceState(null, null, prefix + path)
|
||||
else $window.history.pushState(null, null, prefix + path)
|
||||
$window.onpopstate()
|
||||
$window.onpopstate(true)
|
||||
}
|
||||
else $window.location.href = prefix + path
|
||||
}
|
||||
|
||||
function defineRoutes(routes, resolve, reject) {
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
|
||||
function resolveRoute() {
|
||||
var path = getPath()
|
||||
var params = {}
|
||||
|
|
@ -106,7 +102,10 @@ module.exports = function($window) {
|
|||
|
||||
reject(path, params)
|
||||
}
|
||||
return resolveRoute
|
||||
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
}
|
||||
|
||||
function link(vnode) {
|
||||
|
|
|
|||
|
|
@ -284,16 +284,6 @@ o.spec("Router.defineRoutes", function() {
|
|||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("replays", function() {
|
||||
$window.location.href = prefix + "/test"
|
||||
var replay = router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail)
|
||||
replay()
|
||||
|
||||
o(onRouteChange.callCount).equals(2)
|
||||
o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"])
|
||||
o(onFail.callCount).equals(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ o.spec("api", function() {
|
|||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
o.beforeEach(function() {
|
||||
var mock = browserMock()
|
||||
if (typeof global !== "undefined") global.window = mock, global.document = mock.document
|
||||
if (typeof global !== "undefined") global.window = mock
|
||||
m = require("../mithril")
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue