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-const": "error",
|
||||||
"prefer-reflect": "off",
|
"prefer-reflect": "off",
|
||||||
"prefer-rest-params": "off",
|
"prefer-rest-params": "off",
|
||||||
"prefer-spread": "error",
|
"prefer-spread": "off",
|
||||||
"prefer-template": "off",
|
"prefer-template": "off",
|
||||||
"quote-props": "off",
|
"quote-props": "off",
|
||||||
"quotes": [
|
"quotes": [
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ install:
|
||||||
- npm install
|
- npm install
|
||||||
- npm install @alrra/travis-scripts@^3.0.1
|
- 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 a successful build create bundles & commit back to the repo
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
|
|
@ -29,8 +35,7 @@ after_success:
|
||||||
--path-encrypted-key "./.deploy.enc"
|
--path-encrypted-key "./.deploy.enc"
|
||||||
|
|
||||||
# Build & commit changes
|
# Build & commit changes
|
||||||
$(npm bin)/commit-changes --commands "npm run build" \
|
$(npm bin)/commit-changes --commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \
|
||||||
--commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \
|
|
||||||
--branch "$BRANCH"
|
--branch "$BRANCH"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Mithril.js - A framework for building brilliant applications
|
# 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.
|
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
|
## 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
|
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
|
|
||||||
}
|
|
||||||
18
api/mount.js
18
api/mount.js
|
|
@ -1,23 +1,21 @@
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
var Vnode = require("../render/vnode")
|
var Vnode = require("../render/vnode")
|
||||||
var autoredraw = require("../api/autoredraw")
|
|
||||||
|
|
||||||
module.exports = function(renderer, pubsub) {
|
module.exports = function(redrawService) {
|
||||||
return function(root, component) {
|
return function(root, component) {
|
||||||
if (component === null) {
|
if (component === null) {
|
||||||
renderer.render(root, [])
|
redrawService.render(root, [])
|
||||||
pubsub.unsubscribe(root.redraw)
|
redrawService.unsubscribe(root)
|
||||||
delete root.redraw
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||||
|
|
||||||
var run = autoredraw(root, renderer, pubsub, function() {
|
var run = function() {
|
||||||
renderer.render(root, Vnode(component, undefined, undefined, undefined, undefined, undefined))
|
redrawService.render(root, Vnode(component))
|
||||||
})
|
}
|
||||||
|
redrawService.subscribe(root, run)
|
||||||
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 Vnode = require("../render/vnode")
|
||||||
var coreRouter = require("../router/router")
|
var coreRouter = require("../router/router")
|
||||||
|
|
||||||
module.exports = function($window, mount) {
|
module.exports = function($window, redrawService) {
|
||||||
var router = coreRouter($window)
|
var routeService = coreRouter($window)
|
||||||
var currentResolve, currentComponent, currentRender, currentArgs, currentPath
|
|
||||||
|
|
||||||
var RouteComponent = {view: function() {
|
var identity = function(v) {return v}
|
||||||
return [currentRender(Vnode(currentComponent, null, currentArgs, undefined, undefined, undefined))]
|
var resolver, component, attrs, currentPath, resolve
|
||||||
}}
|
|
||||||
function defaultRender(vnode) {
|
|
||||||
return vnode
|
|
||||||
}
|
|
||||||
var route = function(root, defaultRoute, routes) {
|
var route = function(root, defaultRoute, routes) {
|
||||||
currentComponent = "div"
|
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||||
currentRender = defaultRender
|
var update = function(routeResolver, comp, params, path) {
|
||||||
currentArgs = null
|
resolver = routeResolver, component = comp, attrs = params, currentPath = path, resolve = null
|
||||||
|
resolver.render = routeResolver.render || identity
|
||||||
mount(root, RouteComponent)
|
render()
|
||||||
|
}
|
||||||
router.defineRoutes(routes, function(payload, args, path) {
|
var render = function() {
|
||||||
var isResolver = typeof payload.view !== "function"
|
if (resolver != null) redrawService.render(root, resolver.render(Vnode(component, attrs.key, attrs)))
|
||||||
var render = defaultRender
|
}
|
||||||
|
routeService.defineRoutes(routes, function(payload, params, path) {
|
||||||
var resolve = currentResolve = function (component) {
|
if (payload.view) update({}, payload, params, path)
|
||||||
if (resolve !== currentResolve) return
|
else {
|
||||||
currentResolve = null
|
if (payload.onmatch) {
|
||||||
|
if (resolve != null) update(payload, component, params, path)
|
||||||
currentComponent = component != null ? component : isResolver ? "div" : payload
|
else {
|
||||||
currentRender = render
|
resolve = function(resolved) {
|
||||||
currentArgs = args
|
update(payload, resolved, params, path)
|
||||||
currentPath = path
|
}
|
||||||
|
payload.onmatch(function(resolved) {
|
||||||
root.redraw(true)
|
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() {
|
}, function() {
|
||||||
router.setPath(defaultRoute, null, {replace: true})
|
routeService.setPath(defaultRoute)
|
||||||
})
|
})
|
||||||
|
redrawService.subscribe(root, render)
|
||||||
}
|
}
|
||||||
route.link = router.link
|
route.set = routeService.setPath
|
||||||
route.prefix = router.setPrefix
|
|
||||||
route.set = router.setPath
|
|
||||||
route.get = function() {return currentPath}
|
route.get = function() {return currentPath}
|
||||||
|
route.prefix = routeService.setPrefix
|
||||||
|
route.link = routeService.link
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<script src="../../test-utils/xhrMock.js"></script>
|
<script src="../../test-utils/xhrMock.js"></script>
|
||||||
<script src="../../test-utils/browserMock.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/vnode.js"></script>
|
||||||
<script src="../../render/trust.js"></script>
|
<script src="../../render/trust.js"></script>
|
||||||
<script src="../../render/fragment.js"></script>
|
<script src="../../render/fragment.js"></script>
|
||||||
|
|
@ -24,15 +24,11 @@
|
||||||
<script src="../../querystring/parse.js"></script>
|
<script src="../../querystring/parse.js"></script>
|
||||||
<script src="../../request/request.js"></script>
|
<script src="../../request/request.js"></script>
|
||||||
<script src="../../router/router.js"></script>
|
<script src="../../router/router.js"></script>
|
||||||
<script src="../../api/throttle.js"></script>
|
<script src="../../api/redraw.js"></script>
|
||||||
<script src="../../api/pubsub.js"></script>
|
|
||||||
<script src="../../api/autoredraw.js"></script>
|
|
||||||
<script src="../../api/mount.js"></script>
|
<script src="../../api/mount.js"></script>
|
||||||
<script src="../../api/router.js"></script>
|
<script src="../../api/router.js"></script>
|
||||||
|
|
||||||
<script src="./test-throttle.js"></script>
|
<script src="./test-redraw.js"></script>
|
||||||
<script src="./test-pubsub.js"></script>
|
|
||||||
<script src="./test-autoredraw.js"></script>
|
|
||||||
<script src="./test-mount.js"></script>
|
<script src="./test-mount.js"></script>
|
||||||
<script src="./test-router.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 m = require("../../render/hyperscript")
|
||||||
var coreRenderer = require("../../render/render")
|
var coreRenderer = require("../../render/render")
|
||||||
var apiPubSub = require("../../api/pubsub")
|
var apiRedraw = require("../../api/redraw")
|
||||||
var apiMounter = require("../../api/mount")
|
var apiMounter = require("../../api/mount")
|
||||||
|
|
||||||
o.spec("mount", function() {
|
o.spec("mount", function() {
|
||||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||||
var $window, root, redraw, mount, render
|
var $window, root, redrawService, mount, render
|
||||||
|
|
||||||
o.beforeEach(function() {
|
o.beforeEach(function() {
|
||||||
$window = domMock()
|
$window = domMock()
|
||||||
|
|
||||||
root = $window.document.body
|
root = $window.document.body
|
||||||
|
|
||||||
redraw = apiPubSub()
|
redrawService = apiRedraw($window)
|
||||||
mount = apiMounter(coreRenderer($window), redraw)
|
mount = apiMounter(redrawService)
|
||||||
render = coreRenderer($window).render
|
render = coreRenderer($window).render
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -42,18 +42,16 @@ o.spec("mount", function() {
|
||||||
o(root.firstChild.nodeName).equals("DIV")
|
o(root.firstChild.nodeName).equals("DIV")
|
||||||
})
|
})
|
||||||
|
|
||||||
o("mounting null deletes `redraw` from `root`", function() {
|
o("mounting null unmounts", function() {
|
||||||
mount(root, {
|
mount(root, {
|
||||||
view : function() {
|
view : function() {
|
||||||
return m("div")
|
return m("div")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
o(typeof root.redraw).equals('function')
|
|
||||||
|
|
||||||
mount(root, null)
|
mount(root, null)
|
||||||
|
|
||||||
o(typeof root.redraw).equals('undefined')
|
o(root.childNodes.length).equals(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
o("redraws on events", function(done) {
|
o("redraws on events", function(done) {
|
||||||
|
|
@ -161,7 +159,7 @@ o.spec("mount", function() {
|
||||||
|
|
||||||
o("event handlers can skip redraw", function(done) {
|
o("event handlers can skip redraw", function(done) {
|
||||||
var onupdate = o.spy()
|
var onupdate = o.spy()
|
||||||
var oninit = o.spy()
|
var oninit = o.spy()
|
||||||
var e = $window.document.createEvent("MouseEvents")
|
var e = $window.document.createEvent("MouseEvents")
|
||||||
|
|
||||||
e.initEvent("click", true, true)
|
e.initEvent("click", true, true)
|
||||||
|
|
@ -197,8 +195,8 @@ o.spec("mount", function() {
|
||||||
mount(root, {
|
mount(root, {
|
||||||
view : function() {
|
view : function() {
|
||||||
return m("div", {
|
return m("div", {
|
||||||
oninit : oninit,
|
oninit: oninit,
|
||||||
onupdate : onupdate
|
onupdate: onupdate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -206,7 +204,7 @@ o.spec("mount", function() {
|
||||||
o(oninit.callCount).equals(1)
|
o(oninit.callCount).equals(1)
|
||||||
o(onupdate.callCount).equals(0)
|
o(onupdate.callCount).equals(0)
|
||||||
|
|
||||||
redraw.publish()
|
redrawService.redraw()
|
||||||
|
|
||||||
// Wrapped to give time for the rate-limited redraw to fire
|
// Wrapped to give time for the rate-limited redraw to fire
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
|
@ -215,4 +213,26 @@ o.spec("mount", function() {
|
||||||
done()
|
done()
|
||||||
}, FRAME_BUDGET)
|
}, 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 m = require("../../render/hyperscript")
|
||||||
var coreRenderer = require("../../render/render")
|
var coreRenderer = require("../../render/render")
|
||||||
var apiPubSub = require("../../api/pubsub")
|
var apiRedraw = require("../../api/redraw")
|
||||||
var apiRouter = require("../../api/router")
|
var apiRouter = require("../../api/router")
|
||||||
var apiMounter = require("../../api/mount")
|
|
||||||
|
|
||||||
o.spec("route", function() {
|
o.spec("route", function() {
|
||||||
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
||||||
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
||||||
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
||||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||||
var $window, root, redraw, mount, route
|
var $window, root, redrawService, route
|
||||||
|
|
||||||
o.beforeEach(function() {
|
o.beforeEach(function() {
|
||||||
$window = browserMock(env)
|
$window = browserMock(env)
|
||||||
|
|
||||||
root = $window.document.body
|
root = $window.document.body
|
||||||
|
|
||||||
redraw = apiPubSub()
|
redrawService = apiRedraw($window)
|
||||||
mount = apiMounter(coreRenderer($window), redraw)
|
route = apiRouter($window, redrawService)
|
||||||
route = apiRouter($window, mount)
|
|
||||||
route.prefix(prefix)
|
route.prefix(prefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -59,7 +57,7 @@ o.spec("route", function() {
|
||||||
|
|
||||||
o(view.callCount).equals(1)
|
o(view.callCount).equals(1)
|
||||||
|
|
||||||
redraw.publish(true)
|
redrawService.redraw()
|
||||||
|
|
||||||
o(view.callCount).equals(2)
|
o(view.callCount).equals(2)
|
||||||
|
|
||||||
|
|
@ -122,15 +120,15 @@ o.spec("route", function() {
|
||||||
|
|
||||||
o(oninit.callCount).equals(1)
|
o(oninit.callCount).equals(1)
|
||||||
|
|
||||||
redraw.publish(true)
|
redrawService.redraw()
|
||||||
|
|
||||||
o(onupdate.callCount).equals(1)
|
o(onupdate.callCount).equals(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
o("redraws on events", function(done) {
|
o("redraws on events", function(done) {
|
||||||
var onupdate = o.spy()
|
var onupdate = o.spy()
|
||||||
var oninit = o.spy()
|
var oninit = o.spy()
|
||||||
var onclick = o.spy()
|
var onclick = o.spy()
|
||||||
var e = $window.document.createEvent("MouseEvents")
|
var e = $window.document.createEvent("MouseEvents")
|
||||||
|
|
||||||
e.initEvent("click", true, true)
|
e.initEvent("click", true, true)
|
||||||
|
|
@ -167,8 +165,8 @@ o.spec("route", function() {
|
||||||
|
|
||||||
o("event handlers can skip redraw", function(done) {
|
o("event handlers can skip redraw", function(done) {
|
||||||
var onupdate = o.spy()
|
var onupdate = o.spy()
|
||||||
var oninit = o.spy()
|
var oninit = o.spy()
|
||||||
var onclick = o.spy()
|
var onclick = o.spy()
|
||||||
var e = $window.document.createEvent("MouseEvents")
|
var e = $window.document.createEvent("MouseEvents")
|
||||||
|
|
||||||
e.initEvent("click", true, true)
|
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"
|
$window.location.href = prefix + "/abc"
|
||||||
route(root, "/abc", {
|
route(root, "/abc", {
|
||||||
"/:id" : {
|
"/:id" : resolver
|
||||||
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
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
o(matchCount).equals(1)
|
o(matchCount).equals(1)
|
||||||
|
|
@ -403,7 +404,7 @@ o.spec("route", function() {
|
||||||
o(matchCount).equals(1)
|
o(matchCount).equals(1)
|
||||||
o(renderCount).equals(1)
|
o(renderCount).equals(1)
|
||||||
|
|
||||||
redraw.publish(true)
|
redrawService.redraw()
|
||||||
|
|
||||||
o(matchCount).equals(1)
|
o(matchCount).equals(1)
|
||||||
o(renderCount).equals(2)
|
o(renderCount).equals(2)
|
||||||
|
|
@ -505,7 +506,7 @@ o.spec("route", function() {
|
||||||
o(view.callCount).equals(1)
|
o(view.callCount).equals(1)
|
||||||
o(onmatch.callCount).equals(1)
|
o(onmatch.callCount).equals(1)
|
||||||
|
|
||||||
redraw.publish(true)
|
redrawService.redraw()
|
||||||
|
|
||||||
o(view.callCount).equals(2)
|
o(view.callCount).equals(2)
|
||||||
o(onmatch.callCount).equals(1)
|
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){
|
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 + "/"
|
$window.location.href = prefix + "/"
|
||||||
route(root, '/', {
|
route(root, '/', {
|
||||||
"/":{
|
"/": {
|
||||||
onmatch: onmatch,
|
onmatch: onmatch,
|
||||||
render: function(){return m("div")}
|
render: render
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
o(onmatch.callCount).equals(1)
|
o(onmatch.callCount).equals(1)
|
||||||
|
o(render.callCount).equals(1)
|
||||||
|
|
||||||
route.set(route.get())
|
route.set(route.get())
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
o(onmatch.callCount).equals(2)
|
o(onmatch.callCount).equals(2)
|
||||||
|
o(render.callCount).equals(2)
|
||||||
|
|
||||||
done()
|
done()
|
||||||
}, FRAME_BUDGET)
|
}, FRAME_BUDGET)
|
||||||
|
|
@ -610,6 +614,53 @@ o.spec("route", function() {
|
||||||
done()
|
done()
|
||||||
}, 30)
|
}, 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)}
|
try {return JSON.parse(json)} catch (e) {throw new Error("invalid JSON: " + json)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var error
|
||||||
function run(input, output) {
|
function run(input, output) {
|
||||||
try {
|
try {
|
||||||
var modules = {}
|
var modules = {}
|
||||||
|
|
@ -31,8 +32,9 @@ function run(input, output) {
|
||||||
def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
|
def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
|
||||||
if (def[0] === ",") def = "\nvar ", pre = "\n"
|
if (def[0] === ",") def = "\nvar ", pre = "\n"
|
||||||
var dependency = resolve(filepath, filename)
|
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]))
|
var localUUID = uuid // global uuid can update from nested `process` call, ensure same id is used on declaration and consumption
|
||||||
modules[dependency] = rest ? "_" + uuid : variable
|
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++
|
uuid++
|
||||||
return code + rest
|
return code + rest
|
||||||
})
|
})
|
||||||
|
|
@ -56,8 +58,11 @@ function run(input, output) {
|
||||||
var code = read(filepath)
|
var code = read(filepath)
|
||||||
// if there's a syntax error, report w/ proper stack trace
|
// if there's a syntax error, report w/ proper stack trace
|
||||||
try {new Function(code)} catch (e) {
|
try {new Function(code)} catch (e) {
|
||||||
proc.exec("node " + filename, function(error) {
|
proc.exec("node " + filepath, function(e) {
|
||||||
if (error !== null) console.log("\x1b[31m" + error.message)
|
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}"
|
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) {
|
catch (e) {
|
||||||
console.error(e.message)
|
console.error(e.message)
|
||||||
|
|
|
||||||
|
|
@ -273,6 +273,30 @@ o.spec("bundler", function() {
|
||||||
remove("d.js")
|
remove("d.js")
|
||||||
remove("out.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() {
|
o("reuses binding if possible", function() {
|
||||||
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
|
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
|
||||||
write("b.js", `var d = require("./d")\nmodule.exports = function() {return d + 1}`)
|
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
|
# API
|
||||||
|
|
||||||
- [m](hyperscript.md)
|
### Cheatsheet
|
||||||
- [m.render](render.md)
|
|
||||||
- [m.mount](mount.md)
|
Here are examples for the most commonly used methods. If a method is not listed below, it's meant for advanced usage.
|
||||||
- [m.route](route.md)
|
|
||||||
- [m.route.set](route.md#routeset)
|
#### m(selector, attrs, children) - [docs](hyperscript.md)
|
||||||
- [m.route.get](route.md#routeget)
|
|
||||||
- [m.route.prefix](route.md#routeprefix)
|
```javascript
|
||||||
- [m.route.link](route.md#routelink)
|
m("div.class#id", {title: "title"}, ["children"])
|
||||||
- [m.request](request.md)
|
```
|
||||||
- [m.jsonp](jsonp.md)
|
|
||||||
- [m.parseQueryString](parseQueryString.md)
|
---
|
||||||
- [m.buildQueryString](buildQueryString.md)
|
|
||||||
- [m.withAttr](withAttr.md)
|
#### m.mount(element, component) - [docs](mount.md)
|
||||||
- [m.trust](trust.md)
|
|
||||||
- [m.fragment](fragment.md)
|
```javascript
|
||||||
- [m.redraw](redraw.md)
|
var state = {
|
||||||
- [m.version](version.md)
|
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)
|
# buildQueryString(object)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [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)`
|
`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.
|
`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.prop` removed](#mprop-removed)
|
||||||
- [`m.component` removed](#mcomponent-removed)
|
- [`m.component` removed](#mcomponent-removed)
|
||||||
- [`config` function](#config-function)
|
- [`config` function](#config-function)
|
||||||
|
|
@ -15,14 +23,20 @@
|
||||||
- [`m.route` and anchor tags](#mroute-and-anchor-tags)
|
- [`m.route` and anchor tags](#mroute-and-anchor-tags)
|
||||||
- [Reading/writing the current route](#readingwriting-the-current-route)
|
- [Reading/writing the current route](#readingwriting-the-current-route)
|
||||||
- [Accessing route params](#accessing-route-params)
|
- [Accessing route params](#accessing-route-params)
|
||||||
|
- [Preventing unmounting](#preventing-unmounting)
|
||||||
- [`m.request`](#mrequest)
|
- [`m.request`](#mrequest)
|
||||||
|
- [`m.sync` removed](#msync-removed)
|
||||||
- [`xlink` namespace required](#xlink-namespace-required)
|
- [`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
|
## `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`
|
### `v0.2.x`
|
||||||
|
|
||||||
|
|
@ -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
|
## 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`
|
### `v0.2.x`
|
||||||
|
|
||||||
|
|
@ -441,7 +488,13 @@ setTimeout(function() {
|
||||||
}, 1000)
|
}, 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`
|
### `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
|
## `xlink` namespace required
|
||||||
|
|
@ -492,3 +543,39 @@ m("svg",
|
||||||
m("image[xlink:href='image.gif']")
|
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
|
#### 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).
|
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
|
```javascript
|
||||||
// PREFER
|
// 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.
|
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
|
```javascript
|
||||||
// PREFER
|
// 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.
|
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
|
// calling a second time does not modify DOM
|
||||||
m.render(document.body, m(Component, {greeting: "hello"}))
|
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?
|
## 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?
|
## 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.
|
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?
|
## 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.
|
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
|
# Examples
|
||||||
|
|
||||||
|
Here are some examples of Mithril in action
|
||||||
|
|
||||||
- [Animation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/animation/mosaic.html)
|
- [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)
|
- [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)
|
- [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)
|
- 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)
|
- [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)
|
- [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)`
|
`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)
|
# m(selector, attributes, children)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Flexibility](#flexibility)
|
- [Flexibility](#flexibility)
|
||||||
- [CSS selectors](#css-selectors)
|
- [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)`
|
`vnode = m(selector, attributes, children)`
|
||||||
|
|
||||||
Argument | Type | Required | Description
|
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
|
`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)
|
`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)
|
**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
|
# 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
|
### NPM
|
||||||
|
|
||||||
#### Quick start
|
#### Quick start
|
||||||
|
|
||||||
```
|
```bash
|
||||||
#install
|
# 1) install
|
||||||
npm install mithril@rewrite --save
|
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": {
|
# "scripts": {
|
||||||
# "build": "bundle index.js --output app.js --watch"
|
# "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
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step by step
|
#### 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.
|
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`.
|
```bash
|
||||||
- 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
|
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:
|
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:
|
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
|
```markup
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -147,3 +160,10 @@ m.render(document.body, "hello world")
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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)
|
# jsonp(options)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Typical usage](#typical-usage)
|
- [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
|
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.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.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.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`)
|
`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) {
|
"GET /api/v1/todos": function(request) {
|
||||||
return {status: 200, responseText: JSON.stringify([])}
|
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) {
|
"POST /api/v1/upload": function(request) {
|
||||||
return {status: 200, responseText: JSON.stringify([])}
|
return {status: 200, responseText: JSON.stringify([])}
|
||||||
},
|
},
|
||||||
|
|
@ -132,11 +135,11 @@ function traverseDirectory(pathname, callback) {
|
||||||
|
|
||||||
//run
|
//run
|
||||||
traverseDirectory("./docs", function(pathname) {
|
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) {
|
fs.readFile(pathname, "utf8", function(err, data) {
|
||||||
if (err) console.log(err)
|
if (err) console.log(err)
|
||||||
else lint(pathname, data)
|
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)
|
# mount(root, component)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Performance considerations](#performance-considerations)
|
- [Performance considerations](#performance-considerations)
|
||||||
- [Differences from m.render](#differences-from-m-render)
|
- [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)`
|
`m.mount(element, component)`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
# parseQueryString(string)
|
# parseQueryString(string)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [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)`
|
`object = m.parseQueryString(string)`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,34 @@
|
||||||
# Promise(executor)
|
# Promise(executor)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
- [Static members](#static-members)
|
- [Signature](#signature)
|
||||||
- [Promise.resolve](#promiseresolve)
|
- [Static members](#static-members)
|
||||||
- [Promise.reject](#promisereject)
|
- [Promise.resolve](#promiseresolve)
|
||||||
- [Promise.all](#promiseall)
|
- [Promise.reject](#promisereject)
|
||||||
- [Promise.race](#promiserace)
|
- [Promise.all](#promiseall)
|
||||||
- [Instance members](#static-members)
|
- [Promise.race](#promiserace)
|
||||||
- [promise.then](#promisethen)
|
- [Instance members](#instance-members)
|
||||||
- [promise.catch](#promisecatch)
|
- [promise.then](#promisethen)
|
||||||
|
- [promise.catch](#promisecatch)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Promise chaining](#promise-chaining)
|
- [Promise chaining](#promise-chaining)
|
||||||
- [Promise absorption](#promise-absorption)
|
- [Promise absorption](#promise-absorption)
|
||||||
- [Error handling](#error-handling)
|
- [Error handling](#error-handling)
|
||||||
- [Shorthands](#shorthands)
|
- [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)`
|
`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.
|
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`
|
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.
|
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.
|
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()
|
# redraw()
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [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()`
|
`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.
|
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.
|
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)
|
# render(element, vnodes)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Why Virtual DOM](#why-virtual-dom)
|
- [Why Virtual DOM](#why-virtual-dom)
|
||||||
- [Differences from other API methods](#differences-from-other-api-methods)
|
- [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)`
|
`m.render(element, vnodes)`
|
||||||
|
|
||||||
|
|
@ -52,6 +64,8 @@ Another difference is that `m.render` method expects a [vnode](vnodes.md) (or a
|
||||||
|
|
||||||
### Standalone usage
|
### 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.
|
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).
|
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)
|
# request(options)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Typical usage](#typical-usage)
|
- [Typical usage](#typical-usage)
|
||||||
- [Loading icons and error messages](#loading-icons-and-error-messages)
|
- [Loading icons and error messages](#loading-icons-and-error-messages)
|
||||||
|
|
@ -10,12 +11,31 @@
|
||||||
- [Monitoring progress](#monitoring-progress)
|
- [Monitoring progress](#monitoring-progress)
|
||||||
- [Casting response to a type](#casting-response-to-a-type)
|
- [Casting response to a type](#casting-response-to-a-type)
|
||||||
- [Non-JSON responses](#non-json-responses)
|
- [Non-JSON responses](#non-json-responses)
|
||||||
|
- [Retrieving response details](#retrieving-response-details)
|
||||||
- [Why JSON instead of HTML](#why-json-instead-of-html)
|
- [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)`
|
`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.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.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.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
|
**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)
|
[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`.
|
[`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.
|
- `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` 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` 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:
|
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.
|
- 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.
|
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)
|
# route(root, defaultRoute, routes)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
- [Static members](#static-members)
|
- [Signature](#signature)
|
||||||
- [route.set](#routeset)
|
- [Static members](#static-members)
|
||||||
- [route.get](#routeget)
|
- [route.set](#routeset)
|
||||||
- [route.prefix](#routeprefix)
|
- [route.get](#routeget)
|
||||||
- [route.link](#routelink)
|
- [route.prefix](#routeprefix)
|
||||||
- [RouteResolver](#routeresolver)
|
- [route.link](#routelink)
|
||||||
- [routeResolver.onmatch](#routeresolveronmatch)
|
- [RouteResolver](#routeresolver)
|
||||||
- [routeResolver.render](#routeresolverrender)
|
- [routeResolver.onmatch](#routeresolveronmatch)
|
||||||
|
- [routeResolver.render](#routeresolverrender)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Typical usage](#typical-usage)
|
- [Typical usage](#typical-usage)
|
||||||
- [Navigating to different routes](#navigating-to-different-routes)
|
- [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)`
|
`m.route(root, defaultRoute, routes)`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
# stream()
|
# stream()
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
- [Static members](#static-members)
|
- [Signature](#signature)
|
||||||
- [stream.combine](#streamcombine)
|
- [Static members](#static-members)
|
||||||
- [stream.merge](#streammerge)
|
- [stream.combine](#streamcombine)
|
||||||
- [stream.HALT](#streamhalt)
|
- [stream.merge](#streammerge)
|
||||||
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
- [stream.HALT](#streamhalt)
|
||||||
- [Instance members](#static-members)
|
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||||
- [stream.map](#streammap)
|
- [Instance members](#static-members)
|
||||||
- [stream.end](#streamend)
|
- [stream.map](#streammap)
|
||||||
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
- [stream.end](#streamend)
|
||||||
- [stream["fantasy-land/map"]](#streamfantasy-landmap)
|
- [stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||||
- [stream["fantasy-land/ap"]](#streamfantasy-landap)
|
- [stream["fantasy-land/map"]](#streamfantasy-landmap)
|
||||||
|
- [stream["fantasy-land/ap"]](#streamfantasy-landap)
|
||||||
- [Basic usage](#basic-usage)
|
- [Basic usage](#basic-usage)
|
||||||
- [Streams as variables](#streams-as-variables)
|
- [Streams as variables](#streams-as-variables)
|
||||||
- [Bidirectional bindings](#bidirectional-bindings)
|
- [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
|
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
|
# 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:
|
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
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Good testing practices
|
### Good testing practices
|
||||||
|
|
||||||
Generally speaking, there are two ways to write tests: upfront and after the fact.
|
Generally speaking, there are two ways to write tests: upfront and after the fact.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# trust(html)
|
# trust(html)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Security considerations](#security-considerations)
|
- [Security considerations](#security-considerations)
|
||||||
- [Scripts that do not run](#scripts-that-do-not-run)
|
- [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)`
|
`vnode = m.trust(html)`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
# version
|
# version
|
||||||
|
|
||||||
- [API](#api)
|
- [Signature](#signature)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### API
|
### Signature
|
||||||
|
|
||||||
`m.version`
|
`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.
|
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.
|
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)
|
# withAttr(attrName, callback)
|
||||||
|
|
||||||
- [API](#api)
|
- [Description](#description)
|
||||||
- [How to use](#how-to-use)
|
- [Signature](#signature)
|
||||||
|
- [How it works](#how-it-works)
|
||||||
- [Predictable event target](#predictable-event-target)
|
- [Predictable event target](#predictable-event-target)
|
||||||
- [Attributes and properties](#attributes-and-properties)
|
- [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?)`
|
`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
|
```javascript
|
||||||
// standalone usage
|
// 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()
|
this.update()
|
||||||
},
|
},
|
||||||
update: function() {
|
update: function() {
|
||||||
|
requestAnimationFrame(function() {self.update()})
|
||||||
|
|
||||||
var self = this
|
var self = this
|
||||||
self.databases = ENV.generateData().toArray()
|
self.databases = ENV.generateData().toArray()
|
||||||
setTimeout(function() {self.update()}, ENV.timeout)
|
|
||||||
|
|
||||||
if (renderStage === 0) {
|
if (renderStage === 0) {
|
||||||
renderStage = 1
|
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() {
|
function update() {
|
||||||
|
requestAnimationFrame(update)
|
||||||
|
|
||||||
data = ENV.generateData().toArray()
|
data = ENV.generateData().toArray()
|
||||||
|
|
||||||
perfMonitor.startProfile("render")
|
perfMonitor.startProfile("render")
|
||||||
m.redraw()
|
m.redraw()
|
||||||
perfMonitor.endProfile("render")
|
perfMonitor.endProfile("render")
|
||||||
|
|
||||||
setTimeout(update, ENV.timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
|
|
|
||||||
|
|
@ -38,14 +38,13 @@ var DBMon = React.createClass({
|
||||||
|
|
||||||
var root = document.getElementById("app")
|
var root = document.getElementById("app")
|
||||||
function update() {
|
function update() {
|
||||||
|
requestAnimationFrame(update)
|
||||||
|
|
||||||
data = ENV.generateData().toArray()
|
data = ENV.generateData().toArray()
|
||||||
|
|
||||||
perfMonitor.startProfile("render")
|
perfMonitor.startProfile("render")
|
||||||
ReactDOM.render(h(DBMon, null), root)
|
ReactDOM.render(h(DBMon, null), root)
|
||||||
|
|
||||||
perfMonitor.endProfile("render")
|
perfMonitor.endProfile("render")
|
||||||
|
|
||||||
setTimeout(update, ENV.timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
|
|
|
||||||
|
|
@ -4,48 +4,48 @@ perfMonitor.startMemMonitor()
|
||||||
perfMonitor.initProfiler("render")
|
perfMonitor.initProfiler("render")
|
||||||
|
|
||||||
var vm = new Vue({
|
var vm = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
databases: [],
|
databases: [],
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
update: function () {
|
update: function () {
|
||||||
this.databases = ENV.generateData().toArray()
|
requestAnimationFrame(this.update.bind(this))
|
||||||
setTimeout(this.update.bind(this), ENV.timeout)
|
this.databases = ENV.generateData().toArray()
|
||||||
|
|
||||||
if (renderStage === 0) {
|
if (renderStage === 0) {
|
||||||
renderStage = 1
|
renderStage = 1
|
||||||
perfMonitor.startProfile('render')
|
perfMonitor.startProfile('render')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
updated: function () {
|
updated: function () {
|
||||||
if (renderStage === 1) {
|
if (renderStage === 1) {
|
||||||
renderStage = 0
|
renderStage = 0
|
||||||
perfMonitor.endProfile('render')
|
perfMonitor.endProfile('render')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: '<div>' +
|
template: '<div>' +
|
||||||
'<table class="table table-striped latest-data">' +
|
'<table class="table table-striped latest-data">' +
|
||||||
'<tbody>' +
|
'<tbody>' +
|
||||||
'<tr v-for="db of databases">' +
|
'<tr v-for="db of databases">' +
|
||||||
'<td class="dbname">{{ db.dbname }}</td>' +
|
'<td class="dbname">{{ db.dbname }}</td>' +
|
||||||
'<td class="query-count">' +
|
'<td class="query-count">' +
|
||||||
'<span v-bind:class="[ db.lastSample.countClassName ]">' +
|
'<span v-bind:class="[ db.lastSample.countClassName ]">' +
|
||||||
'{{ db.lastSample.nbQueries}}' +
|
'{{ db.lastSample.nbQueries}}' +
|
||||||
'</span>' +
|
'</span>' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'<td v-for="q of db.lastSample.topFiveQueries" v-bind:class="[ q.elapsedClassName ]">' +
|
'<td v-for="q of db.lastSample.topFiveQueries" v-bind:class="[ q.elapsedClassName ]">' +
|
||||||
'{{ q.formatElapsed }}' +
|
'{{ q.formatElapsed }}' +
|
||||||
'<div class="popover left">' +
|
'<div class="popover left">' +
|
||||||
'<div className="popover-content">{{ q.query }}</div>' +
|
'<div className="popover-content">{{ q.query }}</div>' +
|
||||||
'<div className="arrow"></div>' +
|
'<div className="arrow"></div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'</tr>' +
|
'</tr>' +
|
||||||
'</tbody>' +
|
'</tbody>' +
|
||||||
'</table>' +
|
'</table>' +
|
||||||
'</div>',
|
'</div>',
|
||||||
})
|
})
|
||||||
|
|
||||||
vm.update()
|
vm.update()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<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="../ENV.js"></script>
|
||||||
<script src="https://localvoid.github.io/perf-monitor/0.1/perf-monitor.js"></script>
|
<script src="https://localvoid.github.io/perf-monitor/0.1/perf-monitor.js"></script>
|
||||||
<script src="app.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 requestService = require("./request")
|
||||||
var redrawService = require("./redraw")
|
var redrawService = require("./redraw")
|
||||||
|
|
||||||
requestService.setCompletionCallback(redrawService.publish)
|
requestService.setCompletionCallback(redrawService.redraw)
|
||||||
|
|
||||||
m.mount = require("./mount")
|
m.mount = require("./mount")
|
||||||
m.route = require("./route")
|
m.route = require("./route")
|
||||||
m.withAttr = require("./util/withAttr")
|
m.withAttr = require("./util/withAttr")
|
||||||
m.render = require("./render").render
|
m.render = require("./render").render
|
||||||
m.redraw = redrawService.publish
|
m.redraw = redrawService.redraw
|
||||||
m.request = requestService.request
|
m.request = requestService.request
|
||||||
m.jsonp = requestService.jsonp
|
m.jsonp = requestService.jsonp
|
||||||
m.parseQueryString = require("./querystring/parse")
|
m.parseQueryString = require("./querystring/parse")
|
||||||
|
|
|
||||||
354
mithril.js
354
mithril.js
|
|
@ -202,30 +202,37 @@ var buildQueryString = function(object) {
|
||||||
}
|
}
|
||||||
var _8 = function($window, Promise) {
|
var _8 = function($window, Promise) {
|
||||||
var callbackCount = 0
|
var callbackCount = 0
|
||||||
var count = 0
|
|
||||||
var oncompletion
|
var oncompletion
|
||||||
function setCompletionCallback(callback) {oncompletion = callback}
|
function setCompletionCallback(callback) {oncompletion = callback}
|
||||||
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
function finalizer() {
|
||||||
function finalize(promise0) {
|
var count = 0
|
||||||
var then0 = promise0.then
|
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
||||||
promise0.then = function() {
|
return function finalize(promise0) {
|
||||||
count++
|
var then0 = promise0.then
|
||||||
var next = then0.apply(promise0, arguments)
|
promise0.then = function() {
|
||||||
next.then(complete, function(e) {
|
count++
|
||||||
complete()
|
var next = then0.apply(promise0, arguments)
|
||||||
throw e
|
next.then(complete, function(e) {
|
||||||
})
|
complete()
|
||||||
return finalize(next)
|
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) {
|
function request(args, extra) {
|
||||||
return finalize(new Promise(function(resolve, reject) {
|
var finalize = finalizer()
|
||||||
if (typeof args === "string") {
|
args = normalize(args, extra)
|
||||||
var url = args
|
var promise0 = new Promise(function(resolve, reject) {
|
||||||
args = extra || {}
|
|
||||||
if (args.url == null) args.url = url
|
|
||||||
}
|
|
||||||
if (args.method == null) args.method = "GET"
|
if (args.method == null) args.method = "GET"
|
||||||
args.method = args.method.toUpperCase()
|
args.method = args.method.toUpperCase()
|
||||||
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
|
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)
|
if (useBody && (args.data != null)) xhr.send(args.data)
|
||||||
else xhr.send()
|
else xhr.send()
|
||||||
}))
|
})
|
||||||
|
return args.background === true ? promise0 : finalize(promise0)
|
||||||
}
|
}
|
||||||
function jsonp(args) {
|
function jsonp(args, extra) {
|
||||||
return finalize(new Promise(function(resolve, reject) {
|
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 callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
|
||||||
var script = $window.document.createElement("script")
|
var script = $window.document.createElement("script")
|
||||||
$window[callbackName] = function(data) {
|
$window[callbackName] = function(data) {
|
||||||
|
|
@ -286,7 +297,8 @@ var _8 = function($window, Promise) {
|
||||||
args.data[args.callbackKey || "callback"] = callbackName
|
args.data[args.callbackKey || "callback"] = callbackName
|
||||||
script.src = assemble(args.url, args.data)
|
script.src = assemble(args.url, args.data)
|
||||||
$window.document.documentElement.appendChild(script)
|
$window.document.documentElement.appendChild(script)
|
||||||
}))
|
})
|
||||||
|
return args.background === true? promise0 : finalize(promise0)
|
||||||
}
|
}
|
||||||
function interpolate(url, data) {
|
function interpolate(url, data) {
|
||||||
if (data == null) return url
|
if (data == null) return url
|
||||||
|
|
@ -327,22 +339,7 @@ var _8 = function($window, Promise) {
|
||||||
return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
|
return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
|
||||||
}
|
}
|
||||||
var requestService = _8(window, PromisePolyfill)
|
var requestService = _8(window, PromisePolyfill)
|
||||||
var _11 = function() {
|
var coreRenderer = function($window) {
|
||||||
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 $doc = $window.document
|
var $doc = $window.document
|
||||||
var $emptyFragment = $doc.createDocumentFragment()
|
var $emptyFragment = $doc.createDocumentFragment()
|
||||||
var onevent
|
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 (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
|
||||||
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
||||||
else {
|
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++) {
|
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 (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 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)
|
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++) {
|
for (var i = 0; i < end; i++) {
|
||||||
var vnode = vnodes[i]
|
var vnode = vnodes[i]
|
||||||
if (vnode != null) {
|
if (vnode != null) {
|
||||||
var key1 = vnode.key
|
var key2 = vnode.key
|
||||||
if (key1 != null) map[key1] = i
|
if (key2 != null) map[key2] = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
|
|
@ -747,34 +751,34 @@ var _13 = function($window) {
|
||||||
}
|
}
|
||||||
//attrs2
|
//attrs2
|
||||||
function setAttrs(vnode, attrs2, ns) {
|
function setAttrs(vnode, attrs2, ns) {
|
||||||
for (var key1 in attrs2) {
|
for (var key2 in attrs2) {
|
||||||
setAttr(vnode, key1, null, attrs2[key1], ns)
|
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
|
var element = vnode.dom
|
||||||
if (key1 === "key" || (old === value && !isFormAttribute(vnode, key1)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key1)) return
|
if (key2 === "key" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return
|
||||||
var nsLastIndex = key1.indexOf(":")
|
var nsLastIndex = key2.indexOf(":")
|
||||||
if (nsLastIndex > -1 && key1.substr(0, nsLastIndex) === "xlink") {
|
if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") {
|
||||||
element.setAttributeNS("http://www.w3.org/1999/xlink", key1.slice(nsLastIndex + 1), value)
|
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 (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
|
||||||
else if (key1 === "style") updateStyle(element, old, value)
|
else if (key2 === "style") updateStyle(element, old, value)
|
||||||
else if (key1 in element && !isAttribute(key1) && ns === undefined) {
|
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
|
//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
|
//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
|
//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
|
if (vnode.tag === "option" && key2 === "value" && vnode.dom.value === value) return
|
||||||
element[key1] = value
|
element[key2] = value
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (typeof value === "boolean") {
|
if (typeof value === "boolean") {
|
||||||
if (value) element.setAttribute(key1, "")
|
if (value) element.setAttribute(key2, "")
|
||||||
else element.removeAttribute(key1)
|
else element.removeAttribute(key2)
|
||||||
}
|
}
|
||||||
else element.setAttribute(key1 === "className" ? "class" : key1, value)
|
else element.setAttribute(key2 === "className" ? "class" : key2, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function setLateAttrs(vnode) {
|
function setLateAttrs(vnode) {
|
||||||
|
|
@ -786,16 +790,16 @@ var _13 = function($window) {
|
||||||
}
|
}
|
||||||
function updateAttrs(vnode, old, attrs2, ns) {
|
function updateAttrs(vnode, old, attrs2, ns) {
|
||||||
if (attrs2 != null) {
|
if (attrs2 != null) {
|
||||||
for (var key1 in attrs2) {
|
for (var key2 in attrs2) {
|
||||||
setAttr(vnode, key1, old && old[key1], attrs2[key1], ns)
|
setAttr(vnode, key2, old && old[key2], attrs2[key2], ns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
for (var key1 in old) {
|
for (var key2 in old) {
|
||||||
if (attrs2 == null || !(key1 in attrs2)) {
|
if (attrs2 == null || !(key2 in attrs2)) {
|
||||||
if (key1 === "className") key1 = "class"
|
if (key2 === "className") key2 = "class"
|
||||||
if (key1[0] === "o" && key1[1] === "n" && !isLifecycleMethod(key1)) updateEvent(vnode, key1, undefined)
|
if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined)
|
||||||
else if (key1 !== "key") vnode.dom.removeAttribute(key1)
|
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 style === "string") element.style.cssText = style
|
||||||
else {
|
else {
|
||||||
if (typeof old === "string") element.style.cssText = ""
|
if (typeof old === "string") element.style.cssText = ""
|
||||||
for (var key1 in style) {
|
for (var key2 in style) {
|
||||||
element.style[key1] = style[key1]
|
element.style[key2] = style[key2]
|
||||||
}
|
}
|
||||||
if (old != null && typeof old !== "string") {
|
if (old != null && typeof old !== "string") {
|
||||||
for (var key1 in old) {
|
for (var key2 in old) {
|
||||||
if (!(key1 in style)) element.style[key1] = ""
|
if (!(key2 in style)) element.style[key2] = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//event
|
//event
|
||||||
function updateEvent(vnode, key1, value) {
|
function updateEvent(vnode, key2, value) {
|
||||||
var element = vnode.dom
|
var element = vnode.dom
|
||||||
var callback = function(e) {
|
var callback = typeof onevent !== "function" ? value : function(e) {
|
||||||
var result = value.call(element, e)
|
var result = value.call(element, e)
|
||||||
if (typeof onevent === "function") onevent.call(element, e)
|
onevent.call(element, e)
|
||||||
return result
|
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 {
|
else {
|
||||||
var eventName = key1.slice(2)
|
var eventName = key2.slice(2)
|
||||||
if (vnode.events === undefined) vnode.events = {}
|
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") {
|
if (typeof value === "function") {
|
||||||
vnode.events[key1] = callback
|
vnode.events[key2] = callback
|
||||||
element.addEventListener(eventName, vnode.events[key1], false)
|
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.")
|
if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
|
||||||
var hooks = []
|
var hooks = []
|
||||||
var active = $doc.activeElement
|
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 (dom.vnodes == null) dom.textContent = ""
|
||||||
if (!Array.isArray(vnodes)) vnodes = [vnodes]
|
if (!Array.isArray(vnodes)) vnodes = [vnodes]
|
||||||
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
|
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
|
||||||
|
|
@ -886,79 +891,89 @@ var _13 = function($window) {
|
||||||
}
|
}
|
||||||
return {render: render, setEventCallback: setEventCallback}
|
return {render: render, setEventCallback: setEventCallback}
|
||||||
}
|
}
|
||||||
var renderService = _13(window)
|
function throttle(callback) {
|
||||||
var throttle = function(callback1) {
|
|
||||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||||
var time = 16
|
var time = 16
|
||||||
var last = 0, pending = null
|
var last = 0, pending = null
|
||||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||||
return function(synchronous) {
|
return function() {
|
||||||
var now = Date.now()
|
var now = Date.now()
|
||||||
if (synchronous === true || last === 0 || now - last >= time) {
|
if (last === 0 || now - last >= time) {
|
||||||
last = now
|
last = now
|
||||||
callback1()
|
callback()
|
||||||
}
|
}
|
||||||
else if (pending === null) {
|
else if (pending === null) {
|
||||||
pending = timeout(function() {
|
pending = timeout(function() {
|
||||||
pending = null
|
pending = null
|
||||||
callback1()
|
callback()
|
||||||
last = Date.now()
|
last = Date.now()
|
||||||
}, time - (now - last))
|
}, time - (now - last))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var autoredraw = function(root, renderer, pubsub, callback0) {
|
var _11 = function($window) {
|
||||||
var run1 = throttle(callback0)
|
var renderService = coreRenderer($window)
|
||||||
if (renderer != null) {
|
renderService.setEventCallback(function(e) {
|
||||||
renderer.setEventCallback(function(e) {
|
if (e.redraw !== false) redraw()
|
||||||
if (e.redraw !== false) pubsub.publish()
|
})
|
||||||
})
|
|
||||||
|
var callbacks = []
|
||||||
|
function subscribe(key1, callback) {
|
||||||
|
unsubscribe(key1)
|
||||||
|
callbacks.push(key1, throttle(callback))
|
||||||
}
|
}
|
||||||
if (pubsub != null) {
|
function unsubscribe(key1) {
|
||||||
if (root.redraw) pubsub.unsubscribe(root.redraw)
|
var index = callbacks.indexOf(key1)
|
||||||
pubsub.subscribe(run1)
|
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) {
|
return function(root, component) {
|
||||||
if (component === null) {
|
if (component === null) {
|
||||||
renderer.render(root, [])
|
redrawService0.render(root, [])
|
||||||
pubsub.unsubscribe(root.redraw)
|
redrawService0.unsubscribe(root)
|
||||||
delete root.redraw
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
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))
|
var run0 = function() {
|
||||||
})
|
redrawService0.render(root, Vnode(component))
|
||||||
run0()
|
}
|
||||||
|
redrawService0.subscribe(root, run0)
|
||||||
|
redrawService0.redraw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.mount = _17(renderService, redrawService)
|
m.mount = _16(redrawService)
|
||||||
var mount = m.mount
|
|
||||||
var parseQueryString = function(string) {
|
var parseQueryString = function(string) {
|
||||||
if (string === "" || string == null) return {}
|
if (string === "" || string == null) return {}
|
||||||
if (string.charAt(0) === "?") string = string.slice(1)
|
if (string.charAt(0) === "?") string = string.slice(1)
|
||||||
var entries = string.split("&"), data0 = {}, counters = {}
|
var entries = string.split("&"), data0 = {}, counters = {}
|
||||||
for (var i = 0; i < entries.length; i++) {
|
for (var i = 0; i < entries.length; i++) {
|
||||||
var entry = entries[i].split("=")
|
var entry = entries[i].split("=")
|
||||||
var key3 = decodeURIComponent(entry[0])
|
var key5 = decodeURIComponent(entry[0])
|
||||||
var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
|
var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
|
||||||
if (value === "true") value = true
|
if (value === "true") value = true
|
||||||
else if (value === "false") value = false
|
else if (value === "false") value = false
|
||||||
var levels = key3.split(/\]\[?|\[/)
|
var levels = key5.split(/\]\[?|\[/)
|
||||||
var cursor = data0
|
var cursor = data0
|
||||||
if (key3.indexOf("[") > -1) levels.pop()
|
if (key5.indexOf("[") > -1) levels.pop()
|
||||||
for (var j = 0; j < levels.length; j++) {
|
for (var j = 0; j < levels.length; j++) {
|
||||||
var level = levels[j], nextLevel = levels[j + 1]
|
var level = levels[j], nextLevel = levels[j + 1]
|
||||||
var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
|
var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
|
||||||
var isValue = j === levels.length - 1
|
var isValue = j === levels.length - 1
|
||||||
if (level === "") {
|
if (level === "") {
|
||||||
var key3 = levels.slice(0, j).join()
|
var key5 = levels.slice(0, j).join()
|
||||||
if (counters[key3] == null) counters[key3] = 0
|
if (counters[key5] == null) counters[key5] = 0
|
||||||
level = counters[key3]++
|
level = counters[key5]++
|
||||||
}
|
}
|
||||||
if (cursor[level] == null) {
|
if (cursor[level] == null) {
|
||||||
cursor[level] = isValue ? value : isNumber ? [] : {}
|
cursor[level] = isValue ? value : isNumber ? [] : {}
|
||||||
|
|
@ -973,7 +988,7 @@ var coreRouter = function($window) {
|
||||||
var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
|
var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||||
var prefix1 = "#!"
|
var prefix1 = "#!"
|
||||||
function setPrefix(value) {prefix1 = value}
|
function setPrefix(value) {prefix1 = value}
|
||||||
function normalize(fragment0) {
|
function normalize1(fragment0) {
|
||||||
var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
|
var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
|
||||||
if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
|
if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
|
||||||
return data
|
return data
|
||||||
|
|
@ -995,27 +1010,27 @@ var coreRouter = function($window) {
|
||||||
if (queryIndex > -1) {
|
if (queryIndex > -1) {
|
||||||
var queryEnd = hashIndex > -1 ? hashIndex : path.length
|
var queryEnd = hashIndex > -1 ? hashIndex : path.length
|
||||||
var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd))
|
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) {
|
if (hashIndex > -1) {
|
||||||
var hashParams = parseQueryString(path.slice(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)
|
return path.slice(0, pathEnd)
|
||||||
}
|
}
|
||||||
function getPath() {
|
function getPath() {
|
||||||
var type2 = prefix1.charAt(0)
|
var type2 = prefix1.charAt(0)
|
||||||
switch (type2) {
|
switch (type2) {
|
||||||
case "#": return normalize("hash").slice(prefix1.length)
|
case "#": return normalize1("hash").slice(prefix1.length)
|
||||||
case "?": return normalize("search").slice(prefix1.length) + normalize("hash")
|
case "?": return normalize1("search").slice(prefix1.length) + normalize1("hash")
|
||||||
default: return normalize("pathname").slice(prefix1.length) + normalize("search") + normalize("hash")
|
default: return normalize1("pathname").slice(prefix1.length) + normalize1("search") + normalize1("hash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function setPath(path, data, options) {
|
function setPath(path, data, options) {
|
||||||
var queryData = {}, hashData = {}
|
var queryData = {}, hashData = {}
|
||||||
path = parsePath(path, queryData, hashData)
|
path = parsePath(path, queryData, hashData)
|
||||||
if (data != null) {
|
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) {
|
path = path.replace(/:([^\/]+)/g, function(match2, token) {
|
||||||
delete queryData[token]
|
delete queryData[token]
|
||||||
return data[token]
|
return data[token]
|
||||||
|
|
@ -1028,15 +1043,11 @@ var coreRouter = function($window) {
|
||||||
if (supportsPushState) {
|
if (supportsPushState) {
|
||||||
if (options && options.replace) $window.history.replaceState(null, null, prefix1 + path)
|
if (options && options.replace) $window.history.replaceState(null, null, prefix1 + path)
|
||||||
else $window.history.pushState(null, null, prefix1 + path)
|
else $window.history.pushState(null, null, prefix1 + path)
|
||||||
$window.onpopstate()
|
$window.onpopstate(true)
|
||||||
}
|
}
|
||||||
else $window.location.href = prefix1 + path
|
else $window.location.href = prefix1 + path
|
||||||
}
|
}
|
||||||
function defineRoutes(routes, resolve0, reject) {
|
function defineRoutes(routes, resolve, reject) {
|
||||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
|
||||||
else if (prefix1.charAt(0) === "#") $window.onhashchange = resolveRoute
|
|
||||||
resolveRoute()
|
|
||||||
|
|
||||||
function resolveRoute() {
|
function resolveRoute() {
|
||||||
var path = getPath()
|
var path = getPath()
|
||||||
var params = {}
|
var params = {}
|
||||||
|
|
@ -1051,14 +1062,17 @@ var coreRouter = function($window) {
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
||||||
}
|
}
|
||||||
resolve0(routes[route0], params, path, route0)
|
resolve(routes[route0], params, path, route0)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reject(path, params)
|
reject(path, params)
|
||||||
}
|
}
|
||||||
return resolveRoute
|
|
||||||
|
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||||
|
else if (prefix1.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||||
|
resolveRoute()
|
||||||
}
|
}
|
||||||
function link(vnode2) {
|
function link(vnode2) {
|
||||||
vnode2.dom.setAttribute("href", prefix1 + vnode2.attrs.href)
|
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}
|
return {setPrefix: setPrefix, getPath: getPath, setPath: setPath, defineRoutes: defineRoutes, link: link}
|
||||||
}
|
}
|
||||||
var _23 = function($window, mount0) {
|
var _20 = function($window, redrawService0) {
|
||||||
var router = coreRouter($window)
|
var routeService = 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 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)
|
|
||||||
}
|
|
||||||
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)
|
var identity = function(v) {return v}
|
||||||
|
var resolver, component, attrs3, currentPath, resolve
|
||||||
|
var route = function(root, defaultRoute, routes) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
router.setPath(defaultRoute, null, {replace: true})
|
routeService.setPath(defaultRoute)
|
||||||
})
|
})
|
||||||
|
redrawService0.subscribe(root, render1)
|
||||||
}
|
}
|
||||||
route.link = router.link
|
route.set = routeService.setPath
|
||||||
route.prefix = router.setPrefix
|
|
||||||
route.set = router.setPath
|
|
||||||
route.get = function() {return currentPath}
|
route.get = function() {return currentPath}
|
||||||
|
route.prefix = routeService.setPrefix
|
||||||
|
route.link = routeService.link
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
m.route = _23(window, mount)
|
m.route = _20(window, redrawService)
|
||||||
m.withAttr = function(attrName, callback2, context) {
|
m.withAttr = function(attrName, callback0, context) {
|
||||||
return function(e) {
|
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
|
var _27 = coreRenderer(window)
|
||||||
m.redraw = redrawService.publish
|
m.render = _27.render
|
||||||
|
m.redraw = redrawService.redraw
|
||||||
m.request = requestService.request
|
m.request = requestService.request
|
||||||
m.jsonp = requestService.jsonp
|
m.jsonp = requestService.jsonp
|
||||||
m.parseQueryString = parseQueryString
|
m.parseQueryString = parseQueryString
|
||||||
m.buildQueryString = buildQueryString
|
m.buildQueryString = buildQueryString
|
||||||
m.version = "1.0.0-rc.5"
|
m.version = "1.0.0-rc.6"
|
||||||
if (typeof module !== "undefined") module["exports"] = m
|
if (typeof module !== "undefined") module["exports"] = m
|
||||||
else window.m = 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,"\\")),
|
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"===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]&&
|
"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])?(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===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,
|
||||||
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=
|
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)};
|
||||||
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");
|
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)}}}
|
||||||
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,
|
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&&
|
||||||
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,
|
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++;
|
||||||
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]);
|
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+
|
||||||
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""!==
|
"["+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==
|
||||||
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"!==
|
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);
|
||||||
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&&
|
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);
|
||||||
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)}};
|
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,
|
||||||
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)}))},
|
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");
|
||||||
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=
|
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];
|
||||||
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=
|
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)&&
|
||||||
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=
|
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",
|
||||||
!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);
|
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",
|
||||||
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!=
|
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;
|
||||||
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);
|
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-
|
||||||
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,
|
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),
|
||||||
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||
|
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?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+=
|
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,
|
||||||
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!=
|
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!=
|
||||||
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,
|
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=
|
||||||
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?
|
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}
|
||||||
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=
|
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=
|
||||||
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&&
|
!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.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",
|
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&&
|
||||||
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===
|
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
|
||||||
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]=
|
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"===
|
||||||
"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]=
|
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[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?
|
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=
|
||||||
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");
|
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==
|
||||||
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.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],
|
||||||
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+
|
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)),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,
|
-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?
|
||||||
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,
|
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=
|
||||||
"")]=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,
|
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=
|
||||||
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=
|
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;
|
||||||
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};
|
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")
|
var redrawService = require("./redraw")
|
||||||
|
|
||||||
module.exports = require("./api/mount")(renderService, redrawService)
|
module.exports = require("./api/mount")(redrawService)
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mithril",
|
"name": "mithril",
|
||||||
"version": "1.0.0-rc.5",
|
"version": "1.0.0-rc.6",
|
||||||
"description": "A framework for building brilliant applications",
|
"description": "A framework for building brilliant applications",
|
||||||
"author": "Leo Horie",
|
"author": "Leo Horie",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -12,13 +12,15 @@
|
||||||
"build-browser": "node bundler/cli browser.js -o mithril.js",
|
"build-browser": "node bundler/cli browser.js -o mithril.js",
|
||||||
"build-min": "node bundler/cli browser.js -o mithril.min.js -m",
|
"build-min": "node bundler/cli browser.js -o mithril.min.js -m",
|
||||||
"lintdocs": "node docs/lint",
|
"lintdocs": "node docs/lint",
|
||||||
|
"gendocs": "node docs/generate",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"test": "node ospec/bin/ospec",
|
"test": "node ospec/bin/ospec",
|
||||||
"cover": "istanbul cover --print both ospec/bin/ospec"
|
"cover": "istanbul cover --print both ospec/bin/ospec"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^2.10.2",
|
"eslint": "^2.10.2",
|
||||||
"istanbul": "^0.4.3"
|
"istanbul": "^0.4.3",
|
||||||
|
"marked": "^0.3.6"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"tag": "rewrite"
|
"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 (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
|
||||||
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
||||||
else {
|
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++) {
|
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 (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 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)
|
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
|
||||||
|
|
@ -508,15 +515,16 @@ module.exports = function($window) {
|
||||||
//event
|
//event
|
||||||
function updateEvent(vnode, key, value) {
|
function updateEvent(vnode, key, value) {
|
||||||
var element = vnode.dom
|
var element = vnode.dom
|
||||||
var callback = function(e) {
|
var callback = typeof onevent !== "function" ? value : function(e) {
|
||||||
var result = value.call(element, e)
|
var result = value.call(element, e)
|
||||||
if (typeof onevent === "function") onevent.call(element, e)
|
onevent.call(element, e)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
if (key in element) element[key] = typeof value === "function" ? callback : null
|
if (key in element) element[key] = typeof value === "function" ? callback : null
|
||||||
else {
|
else {
|
||||||
var eventName = key.slice(2)
|
var eventName = key.slice(2)
|
||||||
if (vnode.events === undefined) vnode.events = {}
|
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 (vnode.events[key] != null) element.removeEventListener(eventName, vnode.events[key], false)
|
||||||
if (typeof value === "function") {
|
if (typeof value === "function") {
|
||||||
vnode.events[key] = callback
|
vnode.events[key] = callback
|
||||||
|
|
|
||||||
|
|
@ -879,4 +879,42 @@ o.spec("updateNodes", function() {
|
||||||
|
|
||||||
o(onupdate.callCount).equals(0)
|
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) {
|
module.exports = function($window, Promise) {
|
||||||
var callbackCount = 0
|
var callbackCount = 0
|
||||||
|
|
||||||
var count = 0
|
|
||||||
var oncompletion
|
var oncompletion
|
||||||
function setCompletionCallback(callback) {oncompletion = callback}
|
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) {
|
return function finalize(promise) {
|
||||||
var then = promise.then
|
var then = promise.then
|
||||||
promise.then = function() {
|
promise.then = function() {
|
||||||
count++
|
count++
|
||||||
var next = then.apply(promise, arguments)
|
var next = then.apply(promise, arguments)
|
||||||
next.then(complete, function(e) {
|
next.then(complete, function(e) {
|
||||||
complete()
|
complete()
|
||||||
throw e
|
throw e
|
||||||
})
|
})
|
||||||
return finalize(next)
|
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) {
|
function request(args, extra) {
|
||||||
return finalize(new Promise(function(resolve, reject) {
|
var finalize = finalizer()
|
||||||
if (typeof args === "string") {
|
args = normalize(args, extra)
|
||||||
var url = args
|
|
||||||
args = extra || {}
|
|
||||||
if (args.url == null) args.url = url
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var promise = new Promise(function(resolve, reject) {
|
||||||
if (args.method == null) args.method = "GET"
|
if (args.method == null) args.method = "GET"
|
||||||
args.method = args.method.toUpperCase()
|
args.method = args.method.toUpperCase()
|
||||||
|
|
||||||
|
|
@ -79,11 +86,15 @@ module.exports = function($window, Promise) {
|
||||||
|
|
||||||
if (useBody && (args.data != null)) xhr.send(args.data)
|
if (useBody && (args.data != null)) xhr.send(args.data)
|
||||||
else xhr.send()
|
else xhr.send()
|
||||||
}))
|
})
|
||||||
|
return args.background === true ? promise : finalize(promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonp(args) {
|
function jsonp(args, extra) {
|
||||||
return finalize(new Promise(function(resolve, reject) {
|
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 callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
|
||||||
var script = $window.document.createElement("script")
|
var script = $window.document.createElement("script")
|
||||||
$window[callbackName] = function(data) {
|
$window[callbackName] = function(data) {
|
||||||
|
|
@ -101,7 +112,8 @@ module.exports = function($window, Promise) {
|
||||||
args.data[args.callbackKey || "callback"] = callbackName
|
args.data[args.callbackKey || "callback"] = callbackName
|
||||||
script.src = assemble(args.url, args.data)
|
script.src = assemble(args.url, args.data)
|
||||||
$window.document.documentElement.appendChild(script)
|
$window.document.documentElement.appendChild(script)
|
||||||
}))
|
})
|
||||||
|
return args.background === true? promise : finalize(promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
function interpolate(url, data) {
|
function interpolate(url, data) {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,13 @@ var Promise = require("../../promise/promise")
|
||||||
var parseQueryString = require("../../querystring/parse")
|
var parseQueryString = require("../../querystring/parse")
|
||||||
|
|
||||||
o.spec("jsonp", function() {
|
o.spec("jsonp", function() {
|
||||||
var mock, jsonp, spy
|
var mock, jsonp, spy, complete
|
||||||
o.beforeEach(function() {
|
o.beforeEach(function() {
|
||||||
mock = xhrMock()
|
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) {
|
o("works", function(done) {
|
||||||
|
|
@ -24,6 +27,20 @@ o.spec("jsonp", function() {
|
||||||
o(data).deepEquals({a: 1})
|
o(data).deepEquals({a: 1})
|
||||||
}).then(done)
|
}).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) {
|
o("works w/ other querystring params", function(done) {
|
||||||
mock.$defineRoutes({
|
mock.$defineRoutes({
|
||||||
"GET /item": function(request) {
|
"GET /item": function(request) {
|
||||||
|
|
@ -47,6 +64,64 @@ o.spec("jsonp", function() {
|
||||||
o(data).deepEquals({a: 2})
|
o(data).deepEquals({a: 2})
|
||||||
}).then(done)
|
}).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) {
|
o("handles error", function(done) {
|
||||||
jsonp({url: "/item", callbackKey: "cb"}).catch(function(e) {
|
jsonp({url: "/item", callbackKey: "cb"}).catch(function(e) {
|
||||||
o(e.message).equals("JSONP request failed")
|
o(e.message).equals("JSONP request failed")
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,13 @@ var Request = require("../../request/request")
|
||||||
var Promise = require("../../promise/promise")
|
var Promise = require("../../promise/promise")
|
||||||
|
|
||||||
o.spec("xhr", function() {
|
o.spec("xhr", function() {
|
||||||
var mock, xhr
|
var mock, xhr, complete
|
||||||
o.beforeEach(function() {
|
o.beforeEach(function() {
|
||||||
mock = xhrMock()
|
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() {
|
o.spec("success", function() {
|
||||||
|
|
@ -295,6 +298,50 @@ o.spec("xhr", function() {
|
||||||
o(typeof xhr.send).equals("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.spec("failure", function() {
|
||||||
o("rejects on server error", function(done) {
|
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 (supportsPushState) {
|
||||||
if (options && options.replace) $window.history.replaceState(null, null, prefix + path)
|
if (options && options.replace) $window.history.replaceState(null, null, prefix + path)
|
||||||
else $window.history.pushState(null, null, prefix + path)
|
else $window.history.pushState(null, null, prefix + path)
|
||||||
$window.onpopstate()
|
$window.onpopstate(true)
|
||||||
}
|
}
|
||||||
else $window.location.href = prefix + path
|
else $window.location.href = prefix + path
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineRoutes(routes, resolve, reject) {
|
function defineRoutes(routes, resolve, reject) {
|
||||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
|
||||||
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
|
||||||
resolveRoute()
|
|
||||||
|
|
||||||
function resolveRoute() {
|
function resolveRoute() {
|
||||||
var path = getPath()
|
var path = getPath()
|
||||||
var params = {}
|
var params = {}
|
||||||
|
|
@ -106,7 +102,10 @@ module.exports = function($window) {
|
||||||
|
|
||||||
reject(path, params)
|
reject(path, params)
|
||||||
}
|
}
|
||||||
return resolveRoute
|
|
||||||
|
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||||
|
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||||
|
resolveRoute()
|
||||||
}
|
}
|
||||||
|
|
||||||
function link(vnode) {
|
function link(vnode) {
|
||||||
|
|
|
||||||
|
|
@ -284,16 +284,6 @@ o.spec("Router.defineRoutes", function() {
|
||||||
done()
|
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)
|
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||||
o.beforeEach(function() {
|
o.beforeEach(function() {
|
||||||
var mock = browserMock()
|
var mock = browserMock()
|
||||||
if (typeof global !== "undefined") global.window = mock, global.document = mock.document
|
if (typeof global !== "undefined") global.window = mock
|
||||||
m = require("../mithril")
|
m = require("../mithril")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue