update docs
This commit is contained in:
parent
b9ce90765d
commit
ce0c30a235
11 changed files with 849 additions and 236 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# m(selector, attributes, children)
|
||||
|
||||
- [Signature](#signature)
|
||||
- [API](#api)
|
||||
- [How it works](#how-it-works)
|
||||
- [Flexibility](#flexibility)
|
||||
- [CSS selectors](#css-selectors)
|
||||
|
|
@ -13,11 +13,11 @@
|
|||
- [Keys](#keys)
|
||||
- [SVG and MathML](#svg-and-mathml)
|
||||
- [Making templates dynamic](#making-templates-dynamic)
|
||||
- [Avoid-anti-patterns](#avoid-anti-patterns)
|
||||
- [Avoid anti-patterns](#avoid-anti-patterns)
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
### API
|
||||
|
||||
`vnode = m(selector, attributes, children)`
|
||||
|
||||
|
|
@ -37,8 +37,6 @@ Argument | Type | Required | Descripti
|
|||
Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using javascript syntax. It accepts a `selector` string (required), an `attributes` object (optional) and a `children` array (optional).
|
||||
|
||||
```javascript
|
||||
var m = require("mithril")
|
||||
|
||||
m("div", {id: "box"}, "hello")
|
||||
|
||||
// equivalent HTML:
|
||||
|
|
@ -48,13 +46,13 @@ m("div", {id: "box"}, "hello")
|
|||
The `m()` function does not actually return a DOM element. Instead it returns a [virtual DOM node](vnodes.md), or *vnode*, which is a javascript object that represents the DOM element to be created.
|
||||
|
||||
```javascript
|
||||
//a vnode
|
||||
{tag: "div", attrs: {id: "box"}, children: [ /*...*/ ]}
|
||||
// a vnode
|
||||
var vnode = {tag: "div", attrs: {id: "box"}, children: [ /*...*/ ]}
|
||||
```
|
||||
|
||||
To transform a vnode into an actual DOM element, use the [`m.render()`](render.md) function:
|
||||
|
||||
```
|
||||
```javascript
|
||||
m.render(document.body, m("br")) // puts a <br> in <body>
|
||||
```
|
||||
|
||||
|
|
@ -67,14 +65,14 @@ Calling `m.render()` multiple times does **not** recreate the DOM tree from scra
|
|||
The `m()` function is both *polymorphic* and *variadic*. In other words, it's very flexible in what it expects as input parameters:
|
||||
|
||||
```javascript
|
||||
//simple tag
|
||||
// simple tag
|
||||
m("div") // <div></div>
|
||||
|
||||
//attributes and children are optional
|
||||
// attributes and children are optional
|
||||
m("a", {id: "b"}) // <a id="b"></a>
|
||||
m("span", "hello") // <span>hello</span>
|
||||
|
||||
//tag with child nodes
|
||||
// tag with child nodes
|
||||
m("ul", [ // <ul>
|
||||
m("li", "hello"), // <li>hello</li>
|
||||
m("li", "world"), // <li>world</li>
|
||||
|
|
@ -122,8 +120,8 @@ m("a.link[href=/]", {
|
|||
class: currentURL === "/" ? "selected" : ""
|
||||
}, "Home")
|
||||
|
||||
//equivalent HTML:
|
||||
<a href="/" class="link selected">Home</a>
|
||||
// equivalent HTML:
|
||||
// <a href="/" class="link selected">Home</a>
|
||||
```
|
||||
|
||||
If there are class names in both first and second arguments of `m()`, they are merged together as you would expect.
|
||||
|
|
@ -137,8 +135,8 @@ Mithril uses both the Javascript API and the DOM API (`setAttribute`) to resolve
|
|||
For example, in the Javascript API, the `readonly` attribute is called `element.readOnly` (notice the uppercase). In Mithril, all of the following are supported:
|
||||
|
||||
```javascript
|
||||
m("input", {readonly: true}) //lowercase
|
||||
m("input", {readOnly: true}) //uppercase
|
||||
m("input", {readonly: true}) // lowercase
|
||||
m("input", {readOnly: true}) // uppercase
|
||||
m("input[readonly]")
|
||||
m("input[readOnly]")
|
||||
```
|
||||
|
|
@ -165,7 +163,7 @@ Mithril does not attempt to add units to number values.
|
|||
|
||||
Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on<event>` property, such as `touchstart`
|
||||
|
||||
```
|
||||
```javascript
|
||||
function doSomething(e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
|
@ -192,7 +190,7 @@ m("select", {selectedIndex: 0}, [
|
|||
|
||||
Mithril fully supports SVG. Xlink is also supported, but unlike in pre-v1.0 versions of Mithril, must have the namespace explicitly defined:
|
||||
|
||||
```
|
||||
```javascript
|
||||
m("svg", [
|
||||
m("image[xlink:href='image.gif']")
|
||||
])
|
||||
|
|
@ -384,7 +382,7 @@ var BetterLabeledComponent = {
|
|||
|
||||
Javascript statements often require changing the naturally nested structure of an HTML tree, making the code more verbose and harder to understand. Constructing an virtual DOM tree procedurally can also potentially trigger expensive deoptimizations (such as an entire template being recreated from scratch)
|
||||
|
||||
```
|
||||
```javascript
|
||||
// AVOID
|
||||
var BadListComponent = {
|
||||
view: function(vnode) {
|
||||
|
|
|
|||
14
docs/keys.md
14
docs/keys.md
|
|
@ -3,6 +3,7 @@
|
|||
- [What are keys](#what-are-keys)
|
||||
- [How to use](#how-to-use)
|
||||
- [Debugging key related issues](#debugging-key-related-issues)
|
||||
- [Avoid anti-patterns](#avoid-anti-patterns)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ Keys must be placed on the virtual node that is an immediate child of the array.
|
|||
```javascript
|
||||
// AVOID
|
||||
users.map(function(u) {
|
||||
return m("div", [ //key should be in `div`
|
||||
return m("div", [ // key should be in `div`
|
||||
m("button", {key: u.id}, u.name)
|
||||
])
|
||||
})
|
||||
|
|
@ -107,7 +108,7 @@ var Button = {
|
|||
}
|
||||
users.map(function(u) {
|
||||
return m("div", [
|
||||
m(Button, {id: u.id}, u.name) //key should be here, not in component
|
||||
m(Button, {id: u.id}, u.name) // key should be here, not in component
|
||||
])
|
||||
})
|
||||
```
|
||||
|
|
@ -119,7 +120,7 @@ Arrays are [vnodes](vnodes.md), and therefore keyable. You should not wrap array
|
|||
```javascript
|
||||
// AVOID
|
||||
users.map(function(u) {
|
||||
return [ //fragment is a vnode, and therefore keyable
|
||||
return [ // fragment is a vnode, and therefore keyable
|
||||
m("button", {key: u.id}, u.name)
|
||||
]
|
||||
})
|
||||
|
|
@ -151,3 +152,10 @@ var things = [
|
|||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Avoid anti-patterns
|
||||
|
||||
#### Avoid using key as a counter
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ var ComponentWithHook = {
|
|||
}
|
||||
}
|
||||
|
||||
//Sample hook in vnode
|
||||
// Sample hook in vnode
|
||||
function initializeVnode() {
|
||||
console.log("initialize vnode")
|
||||
}
|
||||
|
||||
m(ComponentWithHook, {oninit: initializeVnode}})
|
||||
m(ComponentWithHook, {oninit: initializeVnode})
|
||||
```
|
||||
|
||||
All lifecyle methods receive the vnode as their first arguments, and have their `this` keyword bound to `vnode.state`.
|
||||
|
|
|
|||
530
docs/prop.md
Normal file
530
docs/prop.md
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
# prop()
|
||||
|
||||
- [API](#api)
|
||||
- [Static members](#static-members)
|
||||
- [prop.combine](#prop-combine)
|
||||
- [prop.reject](#prop-reject)
|
||||
- [Instance members](#static-members)
|
||||
- [stream.map](#stream-map)
|
||||
- [stream.end](#stream-end)
|
||||
- [stream.error](#stream-error)
|
||||
- [stream.catch](#stream-catch)
|
||||
- [stream.of](#stream-of)
|
||||
- [stream.ap](#stream-ap)
|
||||
- [Basic usage](#basic-usage)
|
||||
- [Streams vs promises](#streams-vs-promises)
|
||||
- [Chaining streams](#chaining-streams)
|
||||
- [Combining streams](#combining-streams)
|
||||
- [Absorbing streams](#absorbing-streams)
|
||||
- [Stream states](#stream-states)
|
||||
- [Handling errors](#handling-errors)
|
||||
|
||||
---
|
||||
|
||||
### API
|
||||
|
||||
Creates a stream
|
||||
|
||||
`stream = m.prop(value)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
`value` | `any` | No | If this argument is present, the value of the prop is set to it
|
||||
**returns** | `Stream` | | Returns a stream
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
#### Static members
|
||||
|
||||
##### prop.combine
|
||||
|
||||
Creates a computed stream that reactively updates if any of its upstreams are updated. See [combining streams](#combining-streams)
|
||||
|
||||
`stream = prop.combine(combiner, streams)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | ------------------------=-- | -------- | ---
|
||||
`combiner` | `(Stream..., Array) -> any` | Yes | See [combiner](#combiner) argument
|
||||
`streams` | `Array<Stream>` | Yes | A list of streams to be combined
|
||||
**returns** | `Stream` | | Returns a stream
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
###### combiner
|
||||
|
||||
Specifies how the value of a computed stream is generated. See [combining streams](#combining-streams)
|
||||
|
||||
`any = combiner(streams..., changed)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------ | -------------------- | -------- | ---
|
||||
`streams...` | splat of `Stream`s | No | Splat of zero or more streams that correspond to the streams passed as the second argument to [`prop.combine`](#prop-combine.md)
|
||||
`changed` | `Array<Stream>` | Yes | List of streams that were affected by an update
|
||||
**returns** | `any` | | Returns a computed value
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
##### prop.reject
|
||||
|
||||
Creates a stream in a error state. See [stream states](#stream-states)
|
||||
|
||||
`stream = m.prop.reject(value)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------ | -------------------- | -------- | ---
|
||||
`value` | `any` | Yes | The error value
|
||||
**returns** | `Stream` | | Returns a stream in an error state
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
#### Instance members
|
||||
|
||||
##### stream.map
|
||||
|
||||
Creates a dependent stream whose value is set to the result of the callback function. See [chaining streams](#chaining-streams)
|
||||
|
||||
This method exists to conform to [Fantasy Land's Applicative specification](https://github.com/fantasyland/fantasy-land)
|
||||
|
||||
`dependentStream = m.prop().map(callback)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------ | -------------------- | -------- | ---
|
||||
`callback` | `any -> any` | Yes | The error value
|
||||
**returns** | `Stream` | | Returns a stream in an error state
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
##### stream.end
|
||||
|
||||
A co-dependent stream that unregisters dependent streams when set to true. See [ended state](#ended-state).
|
||||
|
||||
`endStream = m.prop().end`
|
||||
|
||||
##### stream.error
|
||||
|
||||
A co-dependent stream that is set if the stream is in an errored state. See [handling errors](#handling-errors).
|
||||
|
||||
`errorStream = m.prop().error`
|
||||
|
||||
##### stream.catch
|
||||
|
||||
Returns an active stream whose value is equal to the return value of `catch`'s callback.
|
||||
|
||||
`stream = m.prop().catch(callback)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------ | -------------------- | -------- | ---
|
||||
`callback` | `any -> any` | Yes | A callback whose return value becomes the value of the stream returned by `catch`
|
||||
**returns** | `Stream` | | Returns a stream
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
##### stream.of
|
||||
|
||||
This method is functionally identical to `m.prop`. It exists to conform to [Fantasy Land's Applicative specification](https://github.com/fantasyland/fantasy-land)
|
||||
|
||||
`stream = m.prop().of(value)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
`value` | `any` | No | If this argument is present, the value of the prop is set to it
|
||||
**returns** | `Stream` | | Returns a stream
|
||||
|
||||
##### stream.ap
|
||||
|
||||
The name of this method stands for `apply`. If a stream has a function as its value, calling `ap` will call the function with the value of the input stream as its argument, and it will return another stream whose value is the result of the function call. This method exists to conform to [Fantasy Land's Applicative specification](https://github.com/fantasyland/fantasy-land)
|
||||
|
||||
`errorStream = m.prop().ap(value)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
`value` | `Stream` | Yes | If this argument is present, the value of the prop is set to it
|
||||
**returns** | `Stream` | | Returns a stream
|
||||
|
||||
---
|
||||
|
||||
### Basic usage
|
||||
|
||||
`m.prop()` returns a stream. At its most basic level, a stream works similar to a variable or a getter-setter property: it can hold state, which can be modified.
|
||||
|
||||
```javascript
|
||||
var username = m.prop("John")
|
||||
console.log(username()) // logs "John"
|
||||
|
||||
username("John Doe")
|
||||
console.log(username()) // logs "John Doe"
|
||||
```
|
||||
|
||||
The main difference is that a stream is a function, and therefore can be composed into higher order functions.
|
||||
|
||||
```javascript
|
||||
var users = m.prop()
|
||||
|
||||
// request users from a server using the fetch API
|
||||
fetch("/api/users")
|
||||
.then(function(response) {return response.json()})
|
||||
.then(users)
|
||||
```
|
||||
|
||||
In the example above, the `users` stream is populated with the response data when the request resolves. It can also be populated from other higher order functions, such as [`m.withAttr`](withAttr.md)
|
||||
|
||||
```javascript
|
||||
// a stream
|
||||
var user = m.prop("")
|
||||
|
||||
// a bi-directional binding to the stream
|
||||
m("input", {
|
||||
oninput: m.withAttr("value", user),
|
||||
value: user()
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, when the user types in the input, the `user` stream is updated to the value of the input field.
|
||||
|
||||
---
|
||||
|
||||
### Streams vs promiss
|
||||
|
||||
Mithril streams have many similarities to [ES6 promises](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise):
|
||||
|
||||
- streams can be [chained](#chaining-streams) (analogous to `promise.then(callback)`)
|
||||
- streams can be [absorbed by other streams](#absorbing-streams) (analogous to `Promise.resolve(promise)`)
|
||||
- streams have [composable error handling semantics](#handling-errors) (analogous to `promise.catch`)
|
||||
|
||||
These semantic similarities are designed to make it easy to migrate from promise-based asynchronous code to stream-based code.
|
||||
|
||||
For example, here's some sample promise-based code:
|
||||
|
||||
```javascript
|
||||
fetch("/api/users", {method: "GET"}).then(function(response) {return response.json()})
|
||||
.then(function(users) {
|
||||
if (users.length === 0) return Promise.reject("No users found")
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.log(e)
|
||||
})
|
||||
```
|
||||
|
||||
And here's equivalent stream-based code:
|
||||
|
||||
```javascript
|
||||
m.request({url: "/api/users", method: "GET"})
|
||||
.map(function(users) {
|
||||
if (users.length === 0) return m.prop.reject("No users found")
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.log(e)
|
||||
})
|
||||
```
|
||||
|
||||
Aside from the syntax differences between the [`fetch API`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [`m.request()`](request.md) in the first line of each snippet above, the only other syntax difference is that streams use the method `map` to chain, instead of `then`.
|
||||
|
||||
#### Differences
|
||||
|
||||
In most use cases, streams can be used as replacements for promises without much effort. However, because streams are more powerful, they have some important differences.
|
||||
|
||||
Promises are *immutable*; in other words, a promise can only ever resolve to one value. Streams, on the other hand, are *reactive*: a stream's value can be changed freely, and it automatically updates the values of other streams that depend on it.
|
||||
|
||||
Promises are required by spec to resolve asynchronously, even if the resolution value is known in advance (e.g. `Promise.resolve("hello")`). Mithril streams are guaranteed to update synchronously and atomically.
|
||||
|
||||
Mithril streams are also more oriented towards functional programming. In addition to being usable for composing higher order functions, the stream API comply with [Fantasy Land's Applicative specification](https://github.com/fantasyland/fantasy-land), which enables interoperability with functional libraries like Ramda and Sanctuary.
|
||||
|
||||
#### Interoperability with promises
|
||||
|
||||
An increasing number of third party APIs return promises, and it's often desirable to transfer their resolved values to Mithril streams. This can be accomplished by simply chaining the stream itself to the promise chain:
|
||||
|
||||
```javascript
|
||||
var promise = Promise.resolve(123)
|
||||
var stream = m.prop()
|
||||
|
||||
// set the stream to listen to the promise resolution event
|
||||
promise.then(stream)
|
||||
```
|
||||
|
||||
To track promise rejections as well as resolutions, pass the error stream as a rejection callback:
|
||||
|
||||
```javascript
|
||||
promise.then(stream, stream.error)
|
||||
```
|
||||
|
||||
To use a stream value to resolve a promise, simply pass the stream value to it:
|
||||
|
||||
```javascript
|
||||
var stream = m.prop("hello")
|
||||
var promise = Promise.resolve(stream())
|
||||
// promise resolves to "hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Chaining streams
|
||||
|
||||
Streams can be chained using the `map` method. A chained stream is also known as a *dependent stream*.
|
||||
|
||||
```javascript
|
||||
// parent stream
|
||||
var stream = m.prop(1)
|
||||
|
||||
// dependent stream
|
||||
var doubled = stream.map(function(value) {
|
||||
return value * 2
|
||||
})
|
||||
|
||||
console.log(doubled()) // logs 2
|
||||
```
|
||||
|
||||
Dependent streams are *reactive*: their values are updated any time the value of their parent stream is updated. This happens regardless of whether the dependent stream was created before or after the value of the parent stream was set.
|
||||
|
||||
---
|
||||
|
||||
### Combining streams
|
||||
|
||||
Streams can depend on more than one parent stream. These kinds of streams can be created via `m.prop.combine()`
|
||||
|
||||
```javascript
|
||||
var a = m.prop(5)
|
||||
var b = m.prop(7)
|
||||
|
||||
var added = m.prop.combine(function(a, b) {
|
||||
return a() + b()
|
||||
}, [a, b])
|
||||
|
||||
console.log(added()) // logs 12
|
||||
```
|
||||
|
||||
A stream can depend on any number of streams and it's guaranteed to update atomically. For example, if a stream A has two dependent streams B and C, and a fourth stream D is dependent on both B and C, the stream D will only update once if the value of A changes. This guarantees that the callback for stream D is never called with unstable values such as receiving the new value of C but the old value of D. This also bring performance benefits of not recomputing downstreams unnecessarily.
|
||||
|
||||
---
|
||||
|
||||
### Absorbing streams
|
||||
|
||||
It's not possible to set the value of a stream to another streams. Doing so will cause wrapper stream to *absorb* the inner stream and adopt its value and [state](#stream-states):
|
||||
|
||||
```javascript
|
||||
var stream = m.prop(m.prop(1))
|
||||
|
||||
console.log(stream()) // logs 1
|
||||
```
|
||||
|
||||
```javascript
|
||||
var pending = m.prop(m.prop())
|
||||
|
||||
console.log(stream()) // logs undefined because the stream is pending
|
||||
```
|
||||
|
||||
This behavior also applies when mapping and combining streams:
|
||||
|
||||
```javascript
|
||||
var mapped = m.prop(1).map(function(value) {
|
||||
return m.prop(value * 2)
|
||||
})
|
||||
|
||||
console.log(mapped()) // logs 2
|
||||
```
|
||||
|
||||
```javascript
|
||||
var stream = m.prop(1)
|
||||
var combined = m.prop.combine(function(stream) {
|
||||
return m.prop(stream() * 2)
|
||||
}, [stream])
|
||||
|
||||
console.log(combined()) // logs 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Stream states
|
||||
|
||||
At any given time, a stream can be in one of four states: *pending*, *active*, *errored* and *ended*.
|
||||
|
||||
#### Pending state
|
||||
|
||||
Pending streams can be created by calling `m.prop()` with no parameters.
|
||||
|
||||
```javascript
|
||||
var pending = m.prop()
|
||||
```
|
||||
|
||||
If a stream is dependent on more than one stream, any of its parent streams is in a pending state, the dependent streams is also in a pending state, and does not update its value.
|
||||
|
||||
```javascript
|
||||
var a = m.prop(5)
|
||||
var b = m.prop() // pending stream
|
||||
|
||||
var added = m.prop.combine(function(a, b) {
|
||||
return a() + b()
|
||||
}, [a, b])
|
||||
|
||||
console.log(added()) // logs undefined
|
||||
```
|
||||
|
||||
In the example above, `added` is a pending stream, because its parent `b` is also pending.
|
||||
|
||||
This also applies to dependent streams created via `stream.map`:
|
||||
|
||||
```javascript
|
||||
var stream = m.prop()
|
||||
var doubled = stream.map(function(value) {return value * 2})
|
||||
|
||||
console.log(doubled()) // logs undefined because `doubled` is pending
|
||||
```
|
||||
|
||||
#### Active state
|
||||
|
||||
When a stream receives a value, it becomes active (unless the stream is ended).
|
||||
|
||||
```javascript
|
||||
var stream1 = m.prop("hello") // stream1 is active
|
||||
|
||||
var stream2 = m.prop() // stream2 starts off pending
|
||||
stream2("world") // then becomes active
|
||||
```
|
||||
|
||||
A dependent stream with multiple parents becomes active if all of its parents are active.
|
||||
|
||||
In the example above, setting `b(7)` would cause `b` to become active, and therefore `added` would also become active, and be updated to have the value `12`
|
||||
|
||||
#### Errored state
|
||||
|
||||
Errored streams can be created by calling `m.prop.reject()`
|
||||
|
||||
```javascript
|
||||
var erroredStream = m.prop.reject(new Error("Server is offline"))
|
||||
```
|
||||
|
||||
A stream can also become errored if it's a dependent stream and its [`combiner`](#combiner) or [`map`](#stream-map) function throws an error
|
||||
|
||||
```javascript
|
||||
var errored1 = m.prop(1).map(function(value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Not a string")
|
||||
}
|
||||
return value
|
||||
})
|
||||
// errored1 is in an errored state
|
||||
```
|
||||
|
||||
```javascript
|
||||
var stream = m.prop(1)
|
||||
var errored2 = m.prop.combine(function(stream) {
|
||||
if (typeof stream() !== "string") {
|
||||
throw new Error("Not a string")
|
||||
}
|
||||
return value
|
||||
}, [stream])
|
||||
// errored2 is in an errored state
|
||||
```
|
||||
|
||||
When a stream is in a errored state, its value is set to `undefined` and its `error` method is set to the error value
|
||||
|
||||
```javascript
|
||||
var errored = m.prop.reject("Server is offline")
|
||||
|
||||
console.log(errored()) // logs undefined
|
||||
console.log(errored.error()) // logs "Server is offline"
|
||||
```
|
||||
|
||||
#### Ended state
|
||||
|
||||
A stream can stop affecting its dependent streams by calling `stream.end(true)`. This effectively removes the connection between a stream and its dependent streams.
|
||||
|
||||
```javascript
|
||||
var stream = m.prop()
|
||||
var doubled = stream.map(function(value) {return value * 2})
|
||||
|
||||
stream.end(true) // set to ended state
|
||||
|
||||
stream(5)
|
||||
|
||||
console.log(doubled())
|
||||
// logs undefined because `doubled` no longer depends on `stream`
|
||||
```
|
||||
|
||||
Ended streams still have state container semantics, i.e. you can still use them as getter-setters, even after they are ended.
|
||||
|
||||
```javascript
|
||||
var stream = m.prop(1)
|
||||
stream.end(true) // set to ended state
|
||||
|
||||
console.log(stream(1)) // logs 1
|
||||
|
||||
stream(2)
|
||||
console.log(stream()) // logs 2
|
||||
```
|
||||
|
||||
Ending a stream can be useful in cases where a stream has a limited lifetime (for example, reacting to `mousemove` events only while a DOM element is being dragged, but not after it's been dropped).
|
||||
|
||||
---
|
||||
|
||||
### Handling errors
|
||||
|
||||
When a stream is in a errored state, its value is set to `undefined`, and its `error` method returns the error value.
|
||||
|
||||
```javascript
|
||||
var erroredStream = m.prop.reject("Server is offline")
|
||||
|
||||
console.log(erroredStream()) // logs undefined
|
||||
console.log(erroredStream.error()) // logs "Server is offline"
|
||||
```
|
||||
|
||||
Errors can be set in various ways:
|
||||
|
||||
```javascript
|
||||
// via m.prop.reject
|
||||
var errored1 = m.prop.reject("Server is offline")
|
||||
console.log(errored1.error()) // logs "Server is offline"
|
||||
|
||||
// via `.error`
|
||||
var errored2 = m.prop("hello")
|
||||
errored.error("Server is offline")
|
||||
console.log(errored2.error()) // logs "Server is offline"
|
||||
|
||||
// by throwing an error in a chain
|
||||
var errored3 = m.prop("hello").map(function() {
|
||||
throw "Server is offline"
|
||||
})
|
||||
console.log(errored3.error()) // logs "Server is offline"
|
||||
|
||||
var errored4 = m.prop.combine(function() {
|
||||
throw "Server is offline"
|
||||
}, [m.prop("hello")])
|
||||
console.log(errored4.error()) // logs "Server is offline"
|
||||
|
||||
//by returning an errored stream in a chain
|
||||
var errored5 = m.prop("hello").map(function() {
|
||||
return m.prop.reject("Server is offline")
|
||||
})
|
||||
console.log(errored5.error()) // logs "Server is offline"
|
||||
|
||||
var errored6 = m.prop.combine(function() {
|
||||
return m.prop.reject("Server is offline")
|
||||
}, [m.prop("hello")])
|
||||
console.log(errored6.error()) // logs "Server is offline"
|
||||
```
|
||||
|
||||
|
||||
Errors in stream chains propagate: if a stream is in an errored state, all of its dependent streams will have the same errored state, unless the error is handled via a `catch` method.
|
||||
|
||||
```javascript
|
||||
var dependentStream = erroredStream.map(function(value) {return value})
|
||||
console.log(dependentStream()) // logs undefined
|
||||
console.log(dependentStream.error()) // logs "Server is offline"
|
||||
|
||||
var recoveredStream = dependentStream.catch(function() {return "hello"})
|
||||
console.log(recoveredStream()) // logs "hello"
|
||||
console.log(recoveredStream.error()) // logs undefined
|
||||
```
|
||||
|
||||
Like in ES6 promises, the `catch` callback is only called if there is an error. If there isn't an error, it adopts the same value as its parent stream:
|
||||
|
||||
```javascript
|
||||
erroredStream("hi")
|
||||
|
||||
console.log(dependentStream()) // logs "hi"
|
||||
console.log(dependentStream.error()) // logs undefined
|
||||
|
||||
console.log(recoveredStream()) // logs "hi"
|
||||
console.log(recoveredStream.error()) // logs undefined
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
# render(element, vnodes)
|
||||
|
||||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [API](#api)
|
||||
- [Standalone usage](#standalone-usage)
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
### API
|
||||
|
||||
`render(element, vnodes)`
|
||||
`m.render(element, vnodes)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
|
|
@ -22,9 +21,9 @@ Argument | Type | Required | Description
|
|||
|
||||
### How it works
|
||||
|
||||
The `m.render(element, vnodes)` method takes a virtual DOM tree (typically generated via the [`m()` hyperscript function](hyperscript.md), generates a DOM tree and appends it to `element`.
|
||||
The `m.render(element, vnodes)` method takes a virtual DOM tree (typically generated via the [`m()` hyperscript function](hyperscript.md), generates a DOM tree and mounts it on `element`. If `element` already has a DOM tree mounted via a previous `m.render()` call, `vnodes` is diffed against the previous `vnodes` tree and the existing DOM tree is modified where needed to reflect the changes.
|
||||
|
||||
This method is internally called by [`m.mount()`](mount.md) and [`m.route()`](route.md).
|
||||
This method is internally called by [`m.mount()`](mount.md), [`m.route()`](route.md) amd `[m.request()](request.md)`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,23 @@ Sometimes non-native types may appear to indicate that a specific object signatu
|
|||
|
||||
The **Required** column indicates whether an argument is required or optional. If an argument is optional, you may set it to `null` or `undefined`, or omit it altogether, such that the next argument appears in its place.
|
||||
|
||||
---
|
||||
|
||||
### Splats
|
||||
|
||||
A splat argument means that if the last argument is an array, you can omit the square brackets and have a variable number of arguments in the method instead.
|
||||
A splat argument means that if the argument is an array, you can omit the square brackets and have a variable number of arguments in the method instead.
|
||||
|
||||
In the example at the top, this means that `m("div", {id: "foo"}, ["a", "b", "c"])` can also be written as `m("div", {id: "foo"}, "a", "b", "c")`.
|
||||
|
||||
Splats are useful in some compile-to-js languages such as Coffeescript, and also allow helpful shorthands for some common use cases.
|
||||
Splats are useful in some compile-to-js languages such as Coffeescript, and also allow helpful shorthands for some common use cases.
|
||||
|
||||
---
|
||||
|
||||
### Function signatures
|
||||
|
||||
Functions are denoted with an arrow (`->`). The left side of the arrow indicates the types of the input arguments and the right side indicates the type for the return value.
|
||||
|
||||
For example, `parseFloat` has the signature `String -> Number`, i.e. it takes a string as input and returns a number as output.
|
||||
|
||||
Functions with multiple arguments are denoted with parenthesis: `String, Array -> Number`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# trust(html)
|
||||
|
||||
- [Signature](#signature)
|
||||
- [API](#api)
|
||||
- [How it works](#how-it-works)
|
||||
- [Security considerations](#security-considerations)
|
||||
- [Scripts that do not run](#scripts-that-do-not-run)
|
||||
|
|
@ -8,9 +8,11 @@
|
|||
|
||||
---
|
||||
|
||||
### Signature
|
||||
### API
|
||||
|
||||
`render(element, vnodes)`
|
||||
Generates a trusted HTML [vnode](vnodes.md)
|
||||
|
||||
`vnode = m.trust(html)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
|
|
@ -49,6 +51,8 @@ You **must sanitize the input** of `m.trust` to ensure there's no user-generated
|
|||
There are many ways in which an HTML string may contain executable code. The most common ways to inject security attacks are to add an `onload` or `onerror` attributes in `<img>` or `<iframe>` tags, and to use unbalanced quotes such as `" onerror="alert(1)` to inject executable contexts in unsanitized string interpolations.
|
||||
|
||||
```javascript
|
||||
var data = {}
|
||||
|
||||
// Sample vulnerable HTML string
|
||||
var description = "<img alt='" + data.title + "'> <span>" + data.description + "</span>"
|
||||
|
||||
|
|
@ -121,7 +125,7 @@ var FacebookLikeButton = {
|
|||
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1";
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}(document, 'script', 'facebook-jssdk'));
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return [
|
||||
m("#fb-root"),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ In `v0.2.x` mithril provided a single lifecycle method, `config`. `v1.x` provide
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", {
|
||||
config : function(element, isInitialized) {
|
||||
// runs on each redraw
|
||||
|
|
@ -31,20 +31,20 @@ m("div", {
|
|||
|
||||
More documentation on these new methods is available in [lifecycle-methods.md](lifecycle-methods.md).
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", {
|
||||
// Called before the DOM node is created
|
||||
oninit : function(vnode) { ... },
|
||||
oninit : function(vnode) { /*...*/ },
|
||||
// Called after the DOM node is created
|
||||
oncreate : function(vnode) { ... },
|
||||
oncreate : function(vnode) { /*...*/ },
|
||||
// Called before the node is updated, return false to cancel
|
||||
onbeforeupdate : function(vnode, old) { ... },
|
||||
onbeforeupdate : function(vnode, old) { /*...*/ },
|
||||
// Called after the node is updated
|
||||
onupdate : function(vnode) { ... },
|
||||
onupdate : function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, call done() when ready for the node to be removed from the DOM
|
||||
onbeforeremove : function(vnode, done) { ... },
|
||||
onbeforeremove : function(vnode, done) { /*...*/ },
|
||||
// Called before the node is removed, but after onbeforeremove calls done()
|
||||
onremove : function(vnode) { ... }
|
||||
onremove : function(vnode) { /*...*/ }
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ If available the DOM-Element of the vnode can be accessed at `vnode.dom`.
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", {
|
||||
onclick : function(e) {
|
||||
m.redraw.strategy("none");
|
||||
|
|
@ -66,7 +66,7 @@ m("div", {
|
|||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", {
|
||||
onclick : function(e) {
|
||||
e.redraw = false;
|
||||
|
|
@ -80,7 +80,7 @@ In `v1.x` there is no more `controller` property in components, use `oninit` ins
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
controller : function() {
|
||||
var ctrl = this;
|
||||
|
|
@ -96,7 +96,7 @@ m.mount(document.body, {
|
|||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
oninit : function(vnode) {
|
||||
vnode.state.fooga = 1;
|
||||
|
|
@ -130,7 +130,7 @@ Arguments to a component in `v1.x` must be an object, simple values like `String
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
var component = {
|
||||
controller : function(options) {
|
||||
// options.fooga === 1
|
||||
|
|
@ -146,7 +146,7 @@ m("div", m.component(component, { fooga : 1 }));
|
|||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
var component = {
|
||||
oninit : function(vnode) {
|
||||
// vnode.attrs.fooga === 1
|
||||
|
|
@ -166,14 +166,14 @@ In `v0.2.x` you could pass components as the second argument of `m()` w/o any wr
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
m("div", <component>);
|
||||
```javascript
|
||||
m("div", component);
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
m("div", m(<component>));
|
||||
```javascript
|
||||
m("div", m(component));
|
||||
```
|
||||
|
||||
## `m.route()` and anchor tags
|
||||
|
|
@ -182,7 +182,7 @@ Handling clicks on anchor tags via the mithril router is similar to `v0.2.x` but
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// When clicked this link will load the "/path" route instead of navigating
|
||||
m("a", {
|
||||
href : "/path",
|
||||
|
|
@ -192,7 +192,7 @@ m("a", {
|
|||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// When clicked this link will load the "/path" route instead of navigating
|
||||
m("a", {
|
||||
href : "/path",
|
||||
|
|
@ -206,7 +206,7 @@ In `v0.2.x` all interaction w/ the current route happened via `m.route()`. In `v
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// Getting the current route
|
||||
m.route()
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ m.route("/other/route");
|
|||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// Getting the current route
|
||||
m.route.getPath();
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ In `v0.2.x` reading route params was all handled through the `m.route.param()` m
|
|||
|
||||
### `v0.2.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr" : {
|
||||
view : function() {
|
||||
|
|
@ -242,7 +242,7 @@ m.route(document.body, "/booga", {
|
|||
|
||||
### `v1.x`
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr" : {
|
||||
oninit : function(vnode) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue