Skip to content

Commit

Permalink
Clean up m.route.Link (#2768)
Browse files Browse the repository at this point in the history
* Simpler m.route.Link documentation. Fixes #2767

* Remove redundant HTML encoding from markdown docs

* Warn about m.route.Link immunity to event handler API.

* Integrate @JAForbes review corrections

* Example code typo in docs/route.md

Co-authored-by: Matias Kinnunen <github@mtsknn.fi>

Co-authored-by: Matias Kinnunen <github@mtsknn.fi>
  • Loading branch information
2 people authored and StephanHoyer committed May 16, 2022
1 parent e6568a7 commit d0fa668
Showing 1 changed file with 26 additions and 74 deletions.
100 changes: 26 additions & 74 deletions docs/route.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Argument | Type | Required | D
---------------------- | ---------------------------------------- | -------- | ---
`root` | `Element` | Yes | A DOM element that will be the parent node to the subtree
`defaultRoute` | `String` | Yes | The route to redirect to if the current URL does not match a route. Note, this is not the initial route. Initial route will be your address bar's url.
`routes` | <code>Object&#x3c;String,Component&vert;RouteResolver&#x3e;</code> | Yes | An object whose keys are route strings and values are either components or a [RouteResolver](#routeresolver)
`routes` | `Object<String,Component|RouteResolver>` | Yes | An object whose keys are route strings and values are either components or a [RouteResolver](#routeresolver)
**returns** | | | Returns `undefined`

[How to read signatures](signatures.md)
Expand Down Expand Up @@ -125,94 +125,46 @@ This is a simple property, so you can both read it and write to it.

##### m.route.Link

This component can create a dynamic routable link:
This component creates a dynamic routed link. Its essential function is to produce `a` links with local `href`s transformed to take account of the [route prefix](#mrouteprefix).

```javascript
m(m.route.Link, {href: "/test"})
```

Using `m.route.Link` causes the link to behave as a router link - clicking it navigates to the route specified in `href`, instead of navigating away from the current page to the URL specified in `href`.
m(m.route.Link, {href: "/foo"}, "foo")

You can also set the `options` passed to `m.route.set` when the link is clicked by passing the `options` attribute:

```javascript
m(m.route.Link, {href: "/test", options: {replace: true}})
// Unless m.route.prefix has changed from the default strategy, render to:
// <a href="#!/foo">foo</a>
```

You can pass other attributes, too, and you can also specify the tag name used.

```javascript
m(m.route.Link, {
// Any hyperscript selector is valid here - it's literally passed as the
// first parameter to `m`.
selector: "span",
options: {replace: true},
params: {key: "value"},
href: "/test",
disabled: false,
class: "nav-link",
"data-foo": 1,
// and other attributes
}, "link name")
```

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", {
href: "#!/test",
onclick: function(e) {
// ...
},
disabled: false, // Only if you specify it
class: "nav-link",
"data-foo": 1,
// and other attributes
})
```
Links accept a selection of special attributes:
* `selector` is what would be passed as the first argument to [`m`](hyperscript.md): any selector is valid, including non-`a` elements.
* `params` & `options` are the arguments with the same names as defined in [`m.route.set`](#mrouteset).
* `disabled`, if true, disables routing behaviour and any bound `onclick` handler, and attaches a `data-disabled="true"` attribute for accessibility hints; if the element is an `a`, the `href` is removed.

You can also prevent navigation by, in an `onclick` handler, invoking `e.preventDefault()` or returning `false`. This is the same way you block other events, so it's pretty natural.
*Routing behaviour cannot be prevented using the event handling API: use `disabled` instead.*

```javascript
m(m.route.Link, {
href: "/test",
onclick: function(e) {
// Do things...
if (notReady()) e.preventDefault()
}
href: "/foo",
selector: "button.large",
disabled: true,
params: {key: "value"},
options: {replace: true},
}, "link name")
```

This supports full accessibility for both `a` and `button`, via a `disabled` attribute. This ensures [no `href` attribute or `onclick` handler is set](https://css-tricks.com/how-to-disable-links/) and that an `"aria-disabled": "true"` attribute *is* set. If you are passing an `onclick` handler already, that's dropped. (You can work around this by adding it directly in a [lifecycle hook](lifecycle-methods.md).) The `disabled` attribute is itself proxied to the element or component, so you can disable routed `<button>`s and the like.

```javascript
// This does the right thing and the accessible thing for you.
m(m.route.Link, {disabled: disabled, href: "/test"}, "disabled")

// It renders to this hyperscript, assuming the prefix is the default one:
m("a", {
href: "#!/test",
disabled: disabled,
"aria-disabled": disabled ? "true" : false,
onclick: disabled ? null : function(e) {
// ...
},
})
// Renders to:
// <button disabled aria-disabled="true" class="large">link name</button>
```

Do note that this doesn't also disable pointer events for you - you have to do that yourself through CSS - this only does the JS part. Also, the removal of `href` *can* break certain style sheets - if you're relying on this to style disabled links, you may need to update your stylesheets accordingly. Chances are, you're probably just looking it up via `a`, `.some-class`, or `#some-id`, and if you are, you're already good to go. If you're using `[href]` or `:link`, in most cases you can just remove them and it'll still work - it's pretty common to over-specify selectors. If you can't do either, check for both `[href]`/`:link` *and* the non-standard `[disabled]` attribute that was implicitly forwarded to the component.

`vnode = m(m.route.Link, attributes, children)`

Argument | Type | Required | Description
--------------------- | ------------------------------------ | -------- | ---
`attributes.href` | `Object` | Yes | The target route to navigate to.
`attributes.selector` | <code>String&vert;Object&vert;Function</code> | 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.disabled` | `Object` | No | This disables the link, so clicking on it doesn't route anywhere.
`attributes` | `Object` | No | Other attributes to apply to the returned vnode may be passed.
`children` | <code>Array&#x3c;Vnode&#x3e;&vert;String&vert;Number&vert;Boolean</code> | No | Child [vnodes](vnodes.md) for this link.
`attributes.disabled` | `Boolean` | No | Disables the element accessibly.
`attributes.selector` | `String|Object|Function` | No | A selector for [`m`](hyperscript.md), defaults to `"a"`.
`attributes.options` | `Object` | No | Sets the `options` passed to [`m.route.set`](#mrouteset).
`attributes.params` | `Object` | No | Sets the `params` passed to [`m.route.set`](#mrouteset).
`attributes` | `Object` | No | Any other attributes to be forwarded to `m`.
`children` | `Array<Vnode>|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link.
**returns** | `Vnode` | | A [vnode](vnodes.md).

##### m.route.param
Expand All @@ -228,7 +180,7 @@ Retrieves a route parameter from the last fully resolved route. A route paramete
Argument | Type | Required | Description
----------------- | --------------- | -------- | ---
`key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`)
**returns** | <code>String&vert;Object</code> | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys
**returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys

Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.param()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument.

Expand Down Expand Up @@ -270,7 +222,7 @@ Argument | Type | Description
`args` | `Object` | The [routing parameters](#routing-parameters)
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
`route` | `String` | The router path requested by the last routing action, excluding interpolated routing parameter values
**returns** | <code>Component&vert;Promise&#x3c;Component&#x3e;&vert;undefined</code> | Returns a component or a promise that resolves to a component
**returns** | `Component|Promise<Component>|undefined` | Returns a component or a promise that resolves to a component

If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`.

Expand All @@ -286,7 +238,7 @@ Argument | Type | Description
------------------- | -------------------- | -----------
`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"`
`vnode.attrs` | `Object` | A map of URL parameter values
**returns** | <code>Array&#x3c;Vnode&#x3e;&vert;Vnode</code> | The [vnodes](vnodes.md) to be rendered
**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#reinitializing-views-with-single-child-keyed-fragments), since it ends up rendering to something like `[m(Component, {key: m.route.param("key"), ...})]`.

Expand Down

0 comments on commit d0fa668

Please sign in to comment.