Merge branch 'next'

This commit is contained in:
Isiah Meadows 2019-07-16 21:55:31 -04:00
commit 17f2ab2645
15 changed files with 378 additions and 496 deletions

View file

@ -1,82 +0,0 @@
---
name: "\U0001F64B Question"
about: Ask a question about Mithril
title: ''
labels: question
assignees: ''
---
<!-- Provide a general summary of your question in the "Title" above -->
<!--
Provide the exact version of Mithril you're experiencing these issues with. This
matters, even if it's really old like version 0.1.0.
-->
**Mithril version:**
<!--
Provide the name and version of both the browser and operating system you're
experiencing these issues with. If it's multiple, feel free to list multiple.
This matters, even if it's super ancient like IE 6 on Windows XP.
-->
**Browser and OS:**
<!--
Optional: Provide a link to your project, if it happens to be open source or if
you created a repo somewhere that we can look into further. If it's multiple
projects, feel free to list them all.
-->
**Project:**
## Code
<!--
What did you try? Please be specific here. If you'd prefer, replace this code
block with a link to a code playground like any of these:
- Flems <https://flems.io/mithril> (stores everything in URL hash)
- JSFiddle <https://jsfiddle.net>
- CodePen <https://codepen.io>
- JSBin <https://jsbin.com>
- Plunker <https://plnkr.co>
- Glitch <https://glitch.com> (supports backend)
- CodeSandbox <https://codesandbox.io> (supports backend)
Or if it's a remote development project on your own server, feel free to provide
that if it's serving unminified code we can look at.
-->
```javascript
// Code
```
## Expected Behavior
<!--
What did you expect to happen?
- An alert to pop up?
- A specific thing to be logged?
-->
## Current Behavior
<!--
What actually happened?
- The alert never showed?
- The wrong thing was logged?
-->
## Steps to Reproduce
<!--
What steps need to be taken to reproduce this behavior? Please include things
like specific data that need typed in, specific buttons that need clicked, and
so on.
-->
1.
2.
3.
4.
## Context
<!--
How is this issue affecting you? What are you trying to do? Providing us context
helps us reach a conclusion that best fits your particular needs.
-->

View file

@ -1,102 +0,0 @@
---
name: "\U0001F41B Bug"
about: Report a bug in Mithril
title: ''
labels: bug
assignees: isiahmeadows
---
<!-- Provide a general summary of your issue in the "Title" above -->
<!--
Provide the exact version of Mithril you're experiencing these issues with. This
matters, even if it's really old like version 0.1.0. Do note that bugs in older
versions are commonly fixed in newer versions, so you should try to test it
against the latest version if you can.
-->
**Mithril version:**
<!--
Provide the name and version of both the browser and operating system you're
experiencing these issues with. If it's multiple, feel free to list multiple.
This matters, even if it's super ancient like IE 6 on Windows XP.
-->
**Browser and OS:**
<!--
Optional: Provide a link to your project, if it happens to be open source or if
you created a repo somewhere that we can look into further. If it's multiple
projects, feel free to list them all.
-->
**Project:**
## Code
<!--
What did you try? What code is causing the unexpected behavior? Make sure to
try to reduce your code as best as you can while still reproducing the bug, so
we can more accurately determine the cause. Ideally, it should just be a bunch
of Mithril calls with virtually no logic at all, but it's sufficient to just
remove unrelated network calls, attributes, and the like.
In addition, make sure the bug still persists with the latest version of
Mithril. If it's an older version, the bug may have already been fixed.
If you'd prefer, replace this code block with a link to a code playground like
any of these:
- Flems <https://flems.io/mithril> (stores everything in URL hash)
- JSFiddle <https://jsfiddle.net>
- CodePen <https://codepen.io>
- JSBin <https://jsbin.com>
- Plunker <https://plnkr.co>
- Glitch <https://glitch.com> (supports backend)
- CodeSandbox <https://codesandbox.io> (supports backend)
Or if it's a remote development project on your own server, feel free to provide
that if it's serving unminified code we can look at.
If it's a closed-source repo, it's okay to censor names and pull out irrelevant
logic - we'd rather not sign NDAs just to see the code you're having trouble
with. We do still need code of some kind that triggers the bug you're running
into.
-->
```javascript
// Code
```
## Steps to Reproduce
<!--
What steps need to be taken to reproduce this behavior? Please include things
like specific data that need typed in, specific buttons that need clicked, and
so on.
-->
1.
2.
3.
4.
## Expected Behavior
<!--
What did you expect to happen?
- An alert to pop up?
- A specific thing to be logged?
Please be very specific here.
-->
## Current Behavior
<!--
What actually happened?
- The alert never showed?
- The wrong thing was logged?
Please be very specific here.
-->
## Context
<!--
Optional: How is this issue affecting you? What are you trying to do? Providing
us context helps us reach a solution that best fits your particular needs.
-->

View file

@ -1,71 +0,0 @@
---
name: "\U0001F680 Feature or Enhancement"
about: Suggest an idea or feature for Mithril
title: ''
labels: enhancement
assignees: isiahmeadows
---
<!-- Provide a general summary of your suggestion in the "Title" above -->
<!--
Optional: Provide the exact version of Mithril you're experiencing issues with.
This could matter, even if it's really old like version 0.1.0. Do note that bugs
in older versions are commonly fixed in newer versions and that newer versions
do end up with a lot more features than older versions, so it's unlikely we'll
add new features to older versions like 0.1.x.
-->
**Mithril version:**
<!--
Optional: Provide the name and version of both the browser and operating system
you're running Mithril on. If it's multiple, feel free to list multiple. This
could matter, even if it's super ancient like IE 6 on Windows XP.
-->
**Browser and OS:**
<!--
Optional: Provide a link to your project, if it happens to be open source or if
you created a repo somewhere that we can look into further. If it's multiple
projects, feel free to list them all.
-->
**Project:**
<!-- Required -->
**Is this something you're interested in implementing yourself?**
### Description
<!--
What exactly are you suggesting? Is it a particular missing feature? An odd
design choice you think could be improved? This doesn't need to be a concrete,
fully-fledged proposal, but it does need to be clear - it's hard to act on
suggestions that are too vague or generic.
-->
### Why
<!--
Why is this important to you? How would you use it? We need to know what
problems it would solve in the real world and what benefits it would bring, for
both you and other potential users, so we know how we should prioritize it and
so we can see if a better solution might exist.
-->
### Possible Implementation
<!--
Optional: How might this be implemented? This is optional, but it helps us put
the size and cost of the feature into perspective. Simpler features to implement
can often be justified by just being helpful, but big, complex features could
require a massive benefit to pay for their size, scale, and complexity.
(This is why the discussion on a context API similar to React's got so
contentious - it's right on that line where it could go either way on the
cost/benefit ratio for us.)
-->
### Open Questions
<!--
Optional: What things still need discussed? If there are certain details you
aren't sure about, this could help inform discussion. Open questions like these
are precisely what shaped our sync vs async redraw API to be what they are for
v2.
-->

View file

@ -1,102 +0,0 @@
---
name: "\U0001F41BBug report"
about: Report a bug in Mithril
title: ''
labels: bug
assignees: isiahmeadows
---
<!-- Provide a general summary of your issue in the "Title" above -->
<!--
Provide the exact version of Mithril you're experiencing these issues with. This
matters, even if it's really old like version 0.1.0. Do note that bugs in older
versions are commonly fixed in newer versions, so you should try to test it
against the latest version if you can.
-->
**Mithril version:**
<!--
Provide the name and version of both the browser and operating system you're
experiencing these issues with. If it's multiple, feel free to list multiple.
This matters, even if it's super ancient like IE 6 on Windows XP.
-->
**Browser and OS:**
<!--
Optional: Provide a link to your project, if it happens to be open source or if
you created a repo somewhere that we can look into further. If it's multiple
projects, feel free to list them all.
-->
**Project:**
## Code
<!--
What did you try? What code is causing the unexpected behavior? Make sure to
try to reduce your code as best as you can while still reproducing the bug, so
we can more accurately determine the cause. Ideally, it should just be a bunch
of Mithril calls with virtually no logic at all, but it's sufficient to just
remove unrelated network calls, attributes, and the like.
In addition, make sure the bug still persists with the latest version of
Mithril. If it's an older version, the bug may have already been fixed.
If you'd prefer, replace this code block with a link to a code playground like
any of these:
- Flems <https://flems.io/mithril> (stores everything in URL hash)
- JSFiddle <https://jsfiddle.net>
- CodePen <https://codepen.io>
- JSBin <https://jsbin.com>
- Plunker <https://plnkr.co>
- Glitch <https://glitch.com> (supports backend)
- CodeSandbox <https://codesandbox.io> (supports backend)
Or if it's a remote development project on your own server, feel free to provide
that if it's serving unminified code we can look at.
If it's a closed-source repo, it's okay to censor names and pull out irrelevant
logic - we'd rather not sign NDAs just to see the code you're having trouble
with. We do still need code of some kind that triggers the bug you're running
into.
-->
```javascript
// Code
```
## Steps to Reproduce
<!--
What steps need to be taken to reproduce this behavior? Please include things
like specific data that need typed in, specific buttons that need clicked, and
so on.
-->
1.
2.
3.
4.
## Expected Behavior
<!--
What did you expect to happen?
- An alert to pop up?
- A specific thing to be logged?
Please be very specific here.
-->
## Current Behavior
<!--
What actually happened?
- The alert never showed?
- The wrong thing was logged?
Please be very specific here.
-->
## Context
<!--
Optional: How is this issue affecting you? What are you trying to do? Providing
us context helps us reach a solution that best fits your particular needs.
-->

View file

@ -1,5 +1,5 @@
---
name: "\U0001F41BBug"
name: "🐛 Bug"
about: Report a bug in Mithril
title: ''
labels: bug

View file

@ -1,5 +1,5 @@
---
name: "\U0001F680Feature or Enhancement"
name: "🚀 Feature or Enhancement"
about: Suggest an idea or feature for Mithril
title: ''
labels: enhancement

View file

@ -1,5 +1,5 @@
---
name: "\U0001F64BQuestion"
name: "🙋‍♀️ Question"
about: Ask a question about Mithril
title: ''
labels: question

View file

@ -189,14 +189,16 @@ module.exports = function($window, mountRedraw) {
// Remove these so they don't get overwritten
var attrs = {}, onclick, href
assign(attrs, vnode.attrs)
attrs.component = null
attrs.options = null
attrs.key = null
// The first two are internal, but the rest are magic attributes
// that need censored to not screw up rendering.
attrs.selector = attrs.options = attrs.key = attrs.oninit =
attrs.oncreate = attrs.onbeforeupdate = attrs.onupdate =
attrs.onbeforeremove = attrs.onremove = null
// Do this now so we can get the most current `href` and `disabled`.
// Those attributes may also be specified in the selector, and we
// should honor that.
var child = m(vnode.attrs.component || "a", attrs, vnode.children)
var child = m(vnode.attrs.selector || "a", attrs, vnode.children)
// Let's provide a *right* way to disable a route link, rather than
// letting people screw up accessibility on accident.
@ -235,17 +237,18 @@ module.exports = function($window, mountRedraw) {
// link target, etc. Nope, this isn't just for blind people.
if (
// Skip if `onclick` prevented default
result === false || !e.defaultPrevented &&
result !== false && !e.defaultPrevented &&
// Ignore everything but left clicks
(e.button === 0 || e.which === 0 || e.which === 1) &&
// Let the browser handle `target=_blank`, etc.
(!e.currentTarget.target || e.currentTarget.target === "_self") &&
// No modifier keys
!e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey
) return
e.preventDefault()
e.redraw = false
route.set(href, null, options)
) {
e.preventDefault()
e.redraw = false
route.set(href, null, options)
}
}
}
return child

View file

@ -465,6 +465,7 @@ o.spec("route", function() {
o(oninit.callCount).equals(1)
root.firstChild.dispatchEvent(e)
throttleMock.fire()
// Wrapped to ensure no redraw fired
return waitCycles(1).then(function() {
@ -476,6 +477,7 @@ o.spec("route", function() {
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
e.button = 0
$window.location.href = prefix + "/"
route(root, "/", {
@ -496,7 +498,7 @@ o.spec("route", function() {
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
root.firstChild.dispatchEvent(e)
throttleMock.fire()
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
})
@ -505,6 +507,7 @@ o.spec("route", function() {
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
e.button = 0
$window.location.href = prefix + "/"
route(root, "/", {
@ -550,6 +553,64 @@ o.spec("route", function() {
o(root.firstChild.firstChild.nodeValue).equals("text")
})
o("route.Link keeps magic attributes from being double-called", function() {
$window = browserMock(env)
var render = coreRenderer($window)
route = apiRouter(null, null)
route.prefix = prefix
root = $window.document.body
var oninit = o.spy()
var oncreate = o.spy()
var onbeforeupdate = o.spy()
var onupdate = o.spy()
var onbeforeremove = o.spy()
var onremove = o.spy()
render(root, m(route.Link, {
href: "/test",
oninit: oninit,
oncreate: oncreate,
onbeforeupdate: onbeforeupdate,
onupdate: onupdate,
onbeforeremove: onbeforeremove,
onremove: onremove,
}, "text"))
o(oninit.callCount).equals(1)
o(oncreate.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
o(onupdate.callCount).equals(0)
o(onbeforeremove.callCount).equals(0)
o(onremove.callCount).equals(0)
render(root, m(route.Link, {
href: "/test",
oninit: oninit,
oncreate: oncreate,
onbeforeupdate: onbeforeupdate,
onupdate: onupdate,
onbeforeremove: onbeforeremove,
onremove: onremove,
}, "text"))
o(oninit.callCount).equals(1)
o(oncreate.callCount).equals(1)
o(onbeforeupdate.callCount).equals(1)
o(onupdate.callCount).equals(1)
o(onbeforeremove.callCount).equals(0)
o(onremove.callCount).equals(0)
render(root, [])
o(oninit.callCount).equals(1)
o(oncreate.callCount).equals(1)
o(onbeforeupdate.callCount).equals(1)
o(onupdate.callCount).equals(1)
o(onbeforeremove.callCount).equals(1)
o(onremove.callCount).equals(1)
})
o("route.Link can render other tag without routes or dom access", function() {
$window = browserMock(env)
var render = coreRenderer($window)
@ -557,7 +618,7 @@ o.spec("route", function() {
route.prefix = prefix
root = $window.document.body
render(root, m(route.Link, {component: "button", href: "/test", foo: "bar"}, "text"))
render(root, m(route.Link, {selector: "button", href: "/test", foo: "bar"}, "text"))
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("BUTTON")
@ -577,7 +638,7 @@ o.spec("route", function() {
route.prefix = prefix
root = $window.document.body
render(root, m(route.Link, {component: "button[href=/test]", foo: "bar"}, "text"))
render(root, m(route.Link, {selector: "button[href=/test]", foo: "bar"}, "text"))
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("BUTTON")
@ -670,6 +731,139 @@ o.spec("route", function() {
o(root.firstChild.firstChild.nodeValue).equals("text")
})
o("route.Link doesn't redraw on wrong button", function() {
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
e.button = 10
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
view: lock(function() {
return m(route.Link, {href: "/test"})
})
},
"/test" : {
view : lock(function() {
return m("div")
})
}
})
var slash = prefix[0] === "/" ? "" : "/"
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
root.firstChild.dispatchEvent(e)
throttleMock.fire()
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
})
o("route.Link doesn't redraw on preventDefault", function() {
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
e.button = 0
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
view: lock(function() {
return m(route.Link, {
href: "/test",
onclick: function(e) {
e.preventDefault()
}
})
})
},
"/test" : {
view : lock(function() {
return m("div")
})
}
})
var slash = prefix[0] === "/" ? "" : "/"
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
root.firstChild.dispatchEvent(e)
throttleMock.fire()
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
})
o("route.Link doesn't redraw on preventDefault in handleEvent", function() {
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
e.button = 0
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
view: lock(function() {
return m(route.Link, {
href: "/test",
onclick: {
handleEvent: function(e) {
e.preventDefault()
}
}
})
})
},
"/test" : {
view : lock(function() {
return m("div")
})
}
})
var slash = prefix[0] === "/" ? "" : "/"
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
root.firstChild.dispatchEvent(e)
throttleMock.fire()
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
})
o("route.Link doesn't redraw on return false", function() {
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
e.button = 0
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
view: lock(function() {
return m(route.Link, {
href: "/test",
onclick: function() {
return false
}
})
})
},
"/test" : {
view : lock(function() {
return m("div")
})
}
})
var slash = prefix[0] === "/" ? "" : "/"
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
root.firstChild.dispatchEvent(e)
throttleMock.fire()
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
})
o("accepts RouteResolver with onmatch that returns Component", function() {
var matchCount = 0
var renderCount = 0

View file

@ -2,6 +2,7 @@
- [What are keys](#what-are-keys)
- [How to use](#how-to-use)
- [Single-child keyed fragments](#single-child-keyed-fragments)
- [Debugging key related issues](#debugging-key-related-issues)
---
@ -75,7 +76,9 @@ function correctUserList(users) {
}
```
Also, you might want to reinitialize a component. You can use the common pattern of a single-child keyed fragment where you change the key to destroy and reinitialize the element.
#### Single-child keyed fragments
Sometimes, you might want to reinitialize a component on command. You can use the common pattern of a single-child keyed fragment where you change the key to destroy and reinitialize the element.
```javascript
function ResettableToggle() {
@ -96,6 +99,20 @@ function ResettableToggle() {
}
```
You can also bind it to a known identity, for things like item views where you need to fetch a remote resource based on an ID. It's usually simpler than implementing all the logic to diff the ID and re-fetch a resource if it changes.
```javascript
function Page() {
return {
view: function() {
return m(Layout, [
[m(ItemView, {key: m.route.param("id")})],
])
}
}
}
```
---
### Debugging key related issues
@ -120,7 +137,7 @@ users.map(function(u) {
If you refactor the code and make a user component, the key must be moved out of the component and put on the component itself, since it is now the immediate child of the array.
```javascript
// AVOID
// AVOID - doesn't work
var User = {
view: function(vnode) {
return m("div", { key: vnode.attrs.user.id }, [
@ -137,7 +154,7 @@ users.map(function(u) {
#### Avoid wrapping keyed elements in arrays
Arrays are [vnodes](vnodes.md), and therefore keyable. You should not wrap arrays around keyed elements
Arrays are [vnodes](vnodes.md), and therefore keyable. You should not wrap arrays around keyed elements outside [single-child keyed fragments](#single-child-keyed-fragments).
```javascript
// AVOID

View file

@ -144,7 +144,7 @@ You can pass other attributes, too, and you can also specify the tag name used.
m(m.route.Link, {
// Any hyperscript selector is valid here - it's literally passed as the
// first parameter to `m`.
component: "span",
selector: "span",
options: {replace: true},
href: "/test",
disabled: false,
@ -154,7 +154,7 @@ m(m.route.Link, {
}, "link name")
```
Magic attributes used by this component (except `href` and `disabled`) *are* removed while proxying, so you won't have an odd `component="span"` or `options="[object Object]"` attribute show up in your link's DOM node. The above component renders to this hyperscript, assuming the prefix is the default `#!`:
Magic attributes used by this selector (except `href` and `disabled`) *are* removed while proxying, so you won't have an odd `selector="span"` or `options="[object Object]"` attribute show up in your link's DOM node. The above vnode renders to this hyperscript, assuming the prefix is the default `#!`:
```javascript
m("span", {
@ -202,15 +202,15 @@ Do note that this doesn't also disable pointer events for you - you have to do t
`vnode = m(m.route.Link, attributes, children)`
Argument | Type | Required | Description
---------------------- | ------------------------------------ | -------- | ---
`attributes.href` | `Object` | Yes | The target route to navigate to.
`attributes.component` | `String|Object|Function` | No | This sets the tag name to use. Must be a valid selector for [`m`](hyperscript.md) if given, defaults to `"a"`.
`attributes.options` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset).
`attributes.disabled` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset).
`attributes` | `Object` | No | Other attributes to apply to the returned vnode may be passed.
`children` | `Array<Vnode>|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link.
**returns** | `Vnode` | | A [vnode](vnodes.md).
Argument | Type | Required | Description
--------------------- | ------------------------------------ | -------- | ---
`attributes.href` | `Object` | Yes | The target route to navigate to.
`attributes.selector` | `String|Object|Function` | No | This sets the tag name to use. Must be a valid selector for [`m`](hyperscript.md) if given, defaults to `"a"`.
`attributes.options` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset).
`attributes.disabled` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset).
`attributes` | `Object` | No | Other attributes to apply to the returned vnode may be passed.
`children` | `Array<Vnode>|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link.
**returns** | `Vnode` | | A [vnode](vnodes.md).
##### m.route.param
@ -243,6 +243,15 @@ As a rule of thumb, RouteResolvers should be in the same file as the `m.route` c
`routeResolver = {onmatch, render}`
When using components, you could think of them as special sugar for this route resolver, assuming your component is `Home`:
```js
var routeResolver = {
onmatch: function() { return Home },
render: function(vnode) { return [vnode] },
}
```
##### routeResolver.onmatch
The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic, data preloading, redirection analytics tracking, etc)
@ -266,7 +275,7 @@ If `onmatch` returns a promise that gets rejected, the router redirects back to
##### routeResolver.render
The `render` method is called on every redraw for a matching route. It is similar to the `view` method in components and it exists to simplify [component composition](#wrapping-a-layout-component).
The `render` method is called on every redraw for a matching route. It is similar to the `view` method in components and it exists to simplify [component composition](#wrapping-a-layout-component). It also lets you escape from Mithril's normal behavior of replacing the entire subtree.
`vnode = routeResolve.render(vnode)`
@ -276,6 +285,8 @@ Argument | Type | Description
`vnode.attrs` | `Object` | A map of URL parameter values
**returns** | `Array<Vnode>|Vnode` | The [vnodes](vnodes.md) to be rendered
The `vnode` parameter is just `m(Component, m.route.param())` where `Component` is the resolved component for the route (after `routeResolver.onmatch`) and `m.route.param()` is as documented [here](#mrouteparam). If you omit this method, the default return value is `[vnode]`, wrapped in a fragment so you can use [key parameters](#key-parameter). Combined with a `:key` parameter, it becomes a [single-element keyed fragment](keys.md#single-child-keyed-fragments), since it ends up rendering to something like `[m(Component, {key: m.route.param("key"), ...})]`.
---
#### How it works
@ -352,7 +363,7 @@ m.route(document.body, "/", {
})
```
Here we specify two routes: `/` and `/page1`, which render their respective components when the user navigates to each URL. By default, the SPA router prefix is `#!`
Here we specify two routes: `/` and `/page1`, which render their respective components when the user navigates to each URL.
---
@ -364,6 +375,8 @@ You can also navigate programmatically, via `m.route.set(route)`. For example, `
When navigating between routes, the router prefix is handled for you. In other words, leave out the hashbang `#!` (or whatever prefix you set `m.route.prefix` to) when linking Mithril routes, including in both `m.route.set` and in `m.route.Link`.
Do note that when navigating between components, the entire subtree is replaced. Use [a route resolver with a `render` method](#routeresolverrender) if you want to just patch the subtree.
---
### Routing parameters

View file

@ -57,22 +57,22 @@ Functions with multiple arguments are denoted with parenthesis: `(String, Array)
### Component signatures
Components are denoted via calls to `m`, but with the selector argument set to a constant named in the relevant prose:
Components are denoted via calls to `m`, but with the initial selector argument set to a constant named in the relevant prose:
`vnode = m(m.route.Link, attributes, children)`
Argument | Type | Required | Description
---------------------- | ------------------------------------ | -------- | ---
`attributes.href` | `Object` | Yes | The target route to navigate to.
`attributes.component` | `String|Object|Function` | No | This sets the tag name to use. Must be a valid selector for [`m`](hyperscript.md) if given, defaults to `"a"`.
`attributes.options` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset).
`attributes` | `Object` | No | Other attributes to apply to the returned vnode may be passed.
`children` | `Array<Vnode>|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link.
**returns** | `Vnode` | | A [vnode](vnodes.md).
Argument | Type | Required | Description
--------------------- | ------------------------------------ | -------- | ---
`attributes.href` | `Object` | Yes | The target route to navigate to.
`attributes.selector` | `String|Object|Function` | No | This sets the tag name to use. Must be a valid selector for [`m`](hyperscript.md) if given, defaults to `"a"`.
`attributes.options` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset).
`attributes` | `Object` | No | Other attributes to apply to the returned vnode may be passed.
`children` | `Array<Vnode>|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link.
**returns** | `Vnode` | | A [vnode](vnodes.md).
Children here, if specified, are assumed to be able to be written as [splat arguments](#splats), unless otherwise specified in prose.
An element with no sensible children and/or attributes may elect to elide the relevant parameter entirely, so it might look closer to this:
An element with no sensible children and/or attributes may choose to elide the relevant parameter entirely:
`vnode = m(Component, attributes)`

View file

@ -76,7 +76,6 @@ Property | Type | Description
`state` | `Object?` | An object that is persisted between redraws. It is provided by the core engine when needed. In POJO component vnodes, the `state` inherits prototypically from the component object/class. In class component vnodes it is an instance of the class. In closure components it is the object returned by the closure.
`events` | `Object?` | An object that is persisted between redraws and that stores event handlers so that they can be removed using the DOM API. The `events` property is `undefined` if there are no event handlers defined. This property is only used internally by Mithril, do not use or modify it.
`instance` | `Object?` | For components, a storage location for the value returned by the `view`. This property is only used internally by Mithril, do not use or modify it.
`skip` | `Boolean` | This property is only used internally by Mithril when diffing keyed lists, do not use or modify it.
---

View file

@ -16,107 +16,124 @@ o.spec("event", function() {
}
})
function eventSpy(fn) {
function spy(e) {
spy.calls.push({
this: this, type: e.type,
target: e.target, currentTarget: e.currentTarget,
})
if (fn) return fn.apply(this, arguments)
}
spy.calls = []
return spy
}
o("handles onclick", function() {
var spy = o.spy()
var div = {tag: "div", attrs: {onclick: spy}}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [div])
div.dom.dispatchEvent(e)
o(spy.callCount).equals(1)
o(spy.this).equals(div.dom)
o(spy.args[0].type).equals("click")
o(spy.args[0].target).equals(div.dom)
o(redraw.callCount).equals(1)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.$defaultPrevented).equals(false)
o(e.$propagationStopped).equals(false)
})
o("handles onclick returning false", function() {
var spy = o.spy(function () { return false })
var div = {tag: "div", attrs: {onclick: spy}}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [div])
div.dom.dispatchEvent(e)
o(spy.callCount).equals(1)
o(spy.this).equals(div.dom)
o(spy.args[0].type).equals("click")
o(spy.args[0].target).equals(div.dom)
o(redraw.callCount).equals(1)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.$defaultPrevented).equals(true)
o(e.$propagationStopped).equals(true)
})
o("handles click EventListener object", function() {
var spy = o.spy()
var listener = {handleEvent: spy}
var div = {tag: "div", attrs: {onclick: listener}}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [div])
div.dom.dispatchEvent(e)
o(spy.callCount).equals(1)
o(spy.this).equals(listener)
o(spy.args[0].type).equals("click")
o(spy.args[0].target).equals(div.dom)
o(redraw.callCount).equals(1)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.$defaultPrevented).equals(false)
o(e.$propagationStopped).equals(false)
})
o("handles click EventListener object returning false", function() {
var spy = o.spy(function () { return false })
var listener = {handleEvent: spy}
var div = {tag: "div", attrs: {onclick: listener}}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [div])
div.dom.dispatchEvent(e)
o(spy.callCount).equals(1)
o(spy.this).equals(listener)
o(spy.args[0].type).equals("click")
o(spy.args[0].target).equals(div.dom)
o(redraw.callCount).equals(1)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.$defaultPrevented).equals(false)
o(e.$propagationStopped).equals(false)
})
o("handles propagated onclick", function() {
var spy = o.spy()
var child = {tag: "div"}
var parent = {tag: "div", attrs: {onclick: spy}, children: [child]}
var spyDiv = eventSpy()
var spyParent = eventSpy()
var div = {tag: "div", attrs: {onclick: spyDiv}}
var parent = {tag: "div", attrs: {onclick: spyParent}, children: [div]}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [parent])
child.dom.dispatchEvent(e)
div.dom.dispatchEvent(e)
o(spy.callCount).equals(1)
o(spy.this).equals(parent.dom)
o(spy.args[0].type).equals("click")
o(spy.args[0].target).equals(child.dom)
o(spyDiv.calls.length).equals(1)
o(spyDiv.calls[0].this).equals(div.dom)
o(spyDiv.calls[0].type).equals("click")
o(spyDiv.calls[0].target).equals(div.dom)
o(spyDiv.calls[0].currentTarget).equals(div.dom)
o(spyParent.calls.length).equals(1)
o(spyParent.calls[0].this).equals(parent.dom)
o(spyParent.calls[0].type).equals("click")
o(spyParent.calls[0].target).equals(div.dom)
o(spyParent.calls[0].currentTarget).equals(parent.dom)
o(redraw.callCount).equals(2)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.defaultPrevented).equals(false)
})
o("handles onclick returning false", function() {
var spyDiv = eventSpy(function() { return false })
var spyParent = eventSpy()
var div = {tag: "div", attrs: {onclick: spyDiv}}
var parent = {tag: "div", attrs: {onclick: spyParent}, children: [div]}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [parent])
div.dom.dispatchEvent(e)
o(spyDiv.calls.length).equals(1)
o(spyDiv.calls[0].this).equals(div.dom)
o(spyDiv.calls[0].type).equals("click")
o(spyDiv.calls[0].target).equals(div.dom)
o(spyDiv.calls[0].currentTarget).equals(div.dom)
o(spyParent.calls.length).equals(0)
o(redraw.callCount).equals(1)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.$defaultPrevented).equals(false)
o(e.$propagationStopped).equals(false)
o(e.defaultPrevented).equals(true)
})
o("handles click EventListener object", function() {
var spyDiv = eventSpy()
var spyParent = eventSpy()
var listenerDiv = {handleEvent: spyDiv}
var listenerParent = {handleEvent: spyParent}
var div = {tag: "div", attrs: {onclick: listenerDiv}}
var parent = {tag: "div", attrs: {onclick: listenerParent}, children: [div]}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [parent])
div.dom.dispatchEvent(e)
o(spyDiv.calls.length).equals(1)
o(spyDiv.calls[0].this).equals(listenerDiv)
o(spyDiv.calls[0].type).equals("click")
o(spyDiv.calls[0].target).equals(div.dom)
o(spyDiv.calls[0].currentTarget).equals(div.dom)
o(spyParent.calls.length).equals(1)
o(spyParent.calls[0].this).equals(listenerParent)
o(spyParent.calls[0].type).equals("click")
o(spyParent.calls[0].target).equals(div.dom)
o(spyParent.calls[0].currentTarget).equals(parent.dom)
o(redraw.callCount).equals(2)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.defaultPrevented).equals(false)
})
o("handles click EventListener object returning false", function() {
var spyDiv = eventSpy(function() { return false })
var spyParent = eventSpy()
var listenerDiv = {handleEvent: spyDiv}
var listenerParent = {handleEvent: spyParent}
var div = {tag: "div", attrs: {onclick: listenerDiv}}
var parent = {tag: "div", attrs: {onclick: listenerParent}, children: [div]}
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [parent])
div.dom.dispatchEvent(e)
o(spyDiv.calls.length).equals(1)
o(spyDiv.calls[0].this).equals(listenerDiv)
o(spyDiv.calls[0].type).equals("click")
o(spyDiv.calls[0].target).equals(div.dom)
o(spyDiv.calls[0].currentTarget).equals(div.dom)
o(spyParent.calls.length).equals(1)
o(spyParent.calls[0].this).equals(listenerParent)
o(spyParent.calls[0].type).equals("click")
o(spyParent.calls[0].target).equals(div.dom)
o(spyParent.calls[0].currentTarget).equals(parent.dom)
o(redraw.callCount).equals(2)
o(redraw.this).equals(undefined)
o(redraw.args.length).equals(0)
o(e.defaultPrevented).equals(false)
})
o("removes event", function() {

View file

@ -401,7 +401,7 @@ module.exports = function(options) {
e.preventDefault = function() {
prevented = true
}
Object.defineProperty(e, "$defaultPrevented", {
Object.defineProperty(e, "defaultPrevented", {
configurable: true,
get: function () { return prevented }
})
@ -409,10 +409,6 @@ module.exports = function(options) {
e.stopPropagation = function() {
stopped = true
}
Object.defineProperty(e, "$propagationStopped", {
configurable: true,
get: function () { return prevented }
})
e.eventPhase = 1
try {
for (var i = parents.length - 1; 0 <= i; i--) {