docs: merge docs from next to master

This commit is contained in:
Pat Cavit 2017-03-24 15:36:53 -07:00
parent 2a61e17332
commit 33aa1fa735
19 changed files with 285 additions and 72 deletions

1
docs/CNAME Normal file
View file

@ -0,0 +1 @@
mithril.js.org

View file

@ -1,7 +1,38 @@
# Change log # Change log
- [v1.0.2](#v101)
- [v1.0.1](#v101)
- [Migrating from v0.2.x](#migrating-from-v02x) - [Migrating from v0.2.x](#migrating-from-v02x)
- [Older docs](http://mithril.js.org/archive/v0.2.5/change-log.html) - [Older docs](http://mithril.js.org/archive/v0.2.5/index.html)
---
### v1.0.2
#### News
- support for ES6 class components
- updated typescript definitions
#### Bug fixes
- fix IE11 input[type] error - [#1610](https://github.com/lhorie/mithril.js/issues/1610)
- apply [#1609](https://github.com/lhorie/mithril.js/issues/1609) to unkeyed children case
- fix abort detection [#1612](https://github.com/lhorie/mithril.js/issues/1612)
- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/lhorie/mithril.js/issues/1593)
---
### v1.0.1
#### News
- performance improvements in IE [#1598](https://github.com/lhorie/mithril.js/pull/1598)
#### Bug fixes
- prevent infinite loop in non-existent default route - [#1579](https://github.com/lhorie/mithril.js/issues/1579)
- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/lhorie/mithril.js/issues/1609)
--- ---
@ -28,6 +59,7 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
- [`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)
- [Building/Parsing query strings](#buildingparsing-query-strings)
- [Preventing unmounting](#preventing-unmounting) - [Preventing unmounting](#preventing-unmounting)
- [Run code on component removal](#run-code-on-component-removal) - [Run code on component removal](#run-code-on-component-removal)
- [`m.request`](#mrequest) - [`m.request`](#mrequest)
@ -459,6 +491,28 @@ m.route(document.body, "/booga", {
--- ---
## Building/Parsing query strings
`v0.2.x` used methods hanging off of `m.route`, `m.route.buildQueryString()` and `m.route.parseQueryString()`. In `v1.x` these have been broken out and attached to the root `m`.
### `v0.2.x`
```javascript
var qs = m.route.buildQueryString({ a : 1 });
var obj = m.route.parseQueryString("a=1");
```
### `v1.x`
```javascript
var qs = m.buildQueryString({ a : 1 });
var obj = m.parseQueryString("a=1");
```
---
## Preventing unmounting ## 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. 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.
@ -588,11 +642,13 @@ greetAsync()
### `v1.x` ### `v1.x`
```javascript ```javascript
var greetAsync = new Promise(function(resolve){ var greetAsync = function() {
setTimeout(function() { return new Promise(function(resolve){
resolve("hello") setTimeout(function() {
}, 1000) resolve("hello")
}) }, 1000)
})
}
greetAsync() greetAsync()
.then(function(value) {return value + " world"}) .then(function(value) {return value + " world"})

View file

@ -3,6 +3,7 @@
- [Structure](#structure) - [Structure](#structure)
- [Lifecycle methods](#lifecycle-methods) - [Lifecycle methods](#lifecycle-methods)
- [State](#state) - [State](#state)
- [ES6 classes](#es6-classes)
- [Avoid-anti-patterns](#avoid-anti-patterns) - [Avoid-anti-patterns](#avoid-anti-patterns)
### Structure ### Structure
@ -108,7 +109,7 @@ The state of a component can be accessed three ways: as a blueprint at initializ
#### At initialization #### At initialization
Any property attached to the component object is copied for every instance of the component. This allows simple state initialization. The component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of `vnode.state`. This allows simple state initialization.
In the example below, `data` is a property of the `ComponentWithInitialState` component's state object. In the example below, `data` is a property of the `ComponentWithInitialState` component's state object.
@ -170,6 +171,44 @@ Be aware that when using ES5 functions, the value of `this` in nested anonymous
--- ---
### ES6 classes
Components can also be written using ES6 class syntax:
```javascript
class ES6ClassComponent {
view() {
return m("div", "Hello from an ES6 class")
}
}
```
They can be consumed in the same way regular components can.
```javascript
// EXAMPLE: via m.render
m.render(document.body, m(ES6ClassComponent))
// EXAMPLE: via m.mount
m.mount(document.body, ES6ClassComponent)
// EXAMPLE: via m.route
m.route(document.body, "/", {
"/": ES6ClassComponent
})
// EXAMPLE: component composition
class AnotherES6ClassComponent {
view() {
return m("main", [
m(ES6ClassComponent)
])
}
}
```
---
### Avoid anti-patterns ### Avoid anti-patterns
Although Mithril is flexible, some code patterns are discouraged: Although Mithril is flexible, some code patterns are discouraged:

View file

@ -17,10 +17,10 @@ Special thanks to:
- Leon Sorokin, for writing a DOM instrumentation tool that helped improve performance in Mithril 1.0 - Leon Sorokin, for writing a DOM instrumentation tool that helped improve performance in Mithril 1.0
- Jordan Walke, whose work on React was prior art to the implementation of keys in Mithril - Jordan Walke, whose work on React was prior art to the implementation of keys in Mithril
- Pierre-Yves Gérardy, who consistently makes high quality contributions - Pierre-Yves Gérardy, who consistently makes high quality contributions
- Gyandeep Singh, who contributed significant IE performance improvements
Other people who also deserve recognition: Other people who also deserve recognition:
- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](http://arthurclemens.github.io/mithril-template-converter/index.html) - Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](http://arthurclemens.github.io/mithril-template-converter/index.html)
- Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint) - Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint)
- the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril - the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril

View file

@ -35,11 +35,11 @@ Generates a fragment [vnode](vnodes.md)
`vnode = m.fragment(attrs, children)` `vnode = m.fragment(attrs, children)`
Argument | Type | Required | Description Argument | Type | Required | Description
----------- | ------------------------------------ | -------- | --- ----------- | --------------------------------------------------- | -------- | ---
`attrs` | `Object` | Yes | A map of attributes `attrs` | `Object` | Yes | A map of attributes
`children` | `Array<Vnode>|String|Number|Boolean` | Yes | A list of vnodes `children` | `Array<Vnode|String|Number|Boolean|null|undefined>` | Yes | A list of vnodes
**returns** | `Vnode` | | A fragment [vnode](vnodes.md) **returns** | `Vnode` | | A fragment [vnode](vnodes.md)
[How to read signatures](signatures.md) [How to read signatures](signatures.md)

View file

@ -22,7 +22,7 @@ However, if you're starting something new, do consider giving Mithril a try, if
## Why use Mithril? ## Why use Mithril?
In one sentence: because **Mithril is pragmatic**. The 10 minutes [guide](index.md) is a good example: that's how long it takes to learn components, XHR and routing - and that's just about the right amount of knowledge needed to build useful applications. In one sentence: because **Mithril is pragmatic**. This [10 minute guide](index.md) is a good example: that's how long it takes to learn components, XHR and routing - and that's just about the right amount of knowledge needed to build useful applications.
Mithril is all about getting meaningful work done efficiently. Doing file uploads? [The docs show you how](request.md#file-uploads). Authentication? [Documented too](route.md#authentication). Exit animations? [You got it](animation.md). No extra libraries, no magic. Mithril is all about getting meaningful work done efficiently. Doing file uploads? [The docs show you how](request.md#file-uploads). Authentication? [Documented too](route.md#authentication). Exit animations? [You got it](animation.md). No extra libraries, no magic.

View file

@ -1,15 +1,17 @@
"use strict"
var fs = require("fs") var fs = require("fs")
var path = require("path") var path = require("path")
var marked = require("marked") var marked = require("marked")
var layout = fs.readFileSync("./docs/layout.html", "utf-8") var layout = fs.readFileSync("./docs/layout.html", "utf-8")
var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version
try {fs.mkdirSync("../mithril")} catch (e) {} try {fs.mkdirSync("./dist")} catch (e) {/* ignore */}
try {fs.mkdirSync("../mithril/archive")} catch (e) {} try {fs.mkdirSync("./dist/archive")} catch (e) {/* ignore */}
try {fs.mkdirSync("../mithril/archive/v" + version)} catch (e) {} try {fs.mkdirSync("./dist/archive/v" + version)} catch (e) {/* ignore */}
try {fs.mkdirSync("../mithril/archive/v" + version + "/lib")} catch (e) {} try {fs.mkdirSync("./dist/archive/v" + version + "/lib")} catch (e) {/* ignore */}
try {fs.mkdirSync("../mithril/archive/v" + version + "/lib/prism")} catch (e) {} try {fs.mkdirSync("./dist/archive/v" + version + "/lib/prism")} catch (e) {/* ignore */}
try {fs.mkdirSync("../mithril/lib")} catch (e) {} try {fs.mkdirSync("./dist/lib")} catch (e) {/* ignore */}
try {fs.mkdirSync("../mithril/lib/prism")} catch (e) {} try {fs.mkdirSync("./dist/lib/prism")} catch (e) {/* ignore */}
var guides = fs.readFileSync("docs/guides.md", "utf-8") var guides = fs.readFileSync("docs/guides.md", "utf-8")
var methods = fs.readFileSync("docs/methods.md", "utf-8") var methods = fs.readFileSync("docs/methods.md", "utf-8")
@ -25,7 +27,7 @@ function generate(pathname) {
generate(pathname + "/" + filename) generate(pathname + "/" + filename)
}) })
} }
else if (!pathname.match(/tutorials|archive/)) { else if (!pathname.match(/tutorials|archive|guides|methods/)) {
if (pathname.match(/\.md$/)) { if (pathname.match(/\.md$/)) {
var outputFilename = pathname.replace(/\.md$/, ".html") var outputFilename = pathname.replace(/\.md$/, ".html")
var markdown = fs.readFileSync(pathname, "utf-8") var markdown = fs.readFileSync(pathname, "utf-8")
@ -33,7 +35,7 @@ function generate(pathname) {
.replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags .replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags
return "<code>" + (a + b + c).replace(/\|/g, "&#124;") + "</code>" return "<code>" + (a + b + c).replace(/\|/g, "&#124;") + "</code>"
}) })
.replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav, space) { // inject menu .replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav) { // inject menu
var file = path.basename(pathname) var file = path.basename(pathname)
var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))") var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))")
var replace = function(match, space, li, link) { var replace = function(match, space, li, link) {
@ -53,14 +55,14 @@ function generate(pathname) {
.replace(/\[version\]/, version) // update version .replace(/\[version\]/, version) // update version
.replace(/\[body\]/, markedHtml) .replace(/\[body\]/, markedHtml)
.replace(/<h(.) id="([^"]+?)">(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors .replace(/<h(.) id="([^"]+?)">(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors
return "<h" + n + " id=\"" + text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/<a.*?>.+?<\/a>/g, "").replace(/\.|\[|\]|&quot;|\/|\(|\)/g, "").replace(/\s/g, "-") + "\">" + text + "</h" + n + ">" return "<h" + n + ' id="' + text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/<a.*?>.+?<\/a>/g, "").replace(/\.|\[|\]|&quot;|\/|\(|\)/g, "").replace(/\s/g, "-") + '">' + text + "</h" + n + ">"
}) })
fs.writeFileSync("../mithril/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8") fs.writeFileSync("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
fs.writeFileSync("../mithril/" + outputFilename.replace(/^docs\//, ""), html, "utf-8") fs.writeFileSync("./dist/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
} }
else if (!pathname.match(/lint|generate/)) { else if (!pathname.match(/lint|generate/)) {
fs.writeFileSync("../mithril/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8") fs.writeFileSync("./dist/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
fs.writeFileSync("../mithril/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8") fs.writeFileSync("./dist/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
} }
} }
} }

View file

@ -44,6 +44,8 @@ Mithril is used by companies like Vimeo and Nike, and open source platforms like
If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](framework-comparison.md) page. If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](framework-comparison.md) page.
Mithril supports browsers all the way back to IE9, no polyfills required.
--- ---
### Getting started ### Getting started
@ -54,7 +56,7 @@ Let's create an HTML file to follow along:
```markup ```markup
<body> <body>
<script src="http://unpkg.com/mithril/mithril.js"></script> <script src="//unpkg.com/mithril/mithril.js"></script>
<script> <script>
var root = document.body var root = document.body
@ -93,7 +95,7 @@ Let's wrap our text in an `<h1>` tag.
m.render(root, m("h1", "My first app")) 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>`: The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `<h1>`:
```javascript ```javascript
m("h1", {class: "title"}, "My first app") m("h1", {class: "title"}, "My first app")
@ -231,7 +233,7 @@ var count = 0
var increment = function() { var increment = function() {
m.request({ m.request({
method: "PUT", method: "PUT",
url: "http://rem-rest-api.herokuapp.com/api/tutorial/1", url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
data: {count: count + 1}, data: {count: count + 1},
withCredentials: true, withCredentials: true,
}) })

View file

@ -36,6 +36,7 @@ Argument | Type | Required | Descript
`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`)
`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 `type` method **returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through `type` method
[How to read signatures](signatures.md) [How to read signatures](signatures.md)

View file

@ -45,8 +45,8 @@ var link = <a href={url}>{greeting + "!"}</a>
Components can be used by using a convention of uppercasing the first letter of the component name: Components can be used by using a convention of uppercasing the first letter of the component name:
```jsx ```jsx
m.mount(document.body, <MyComponent />) m.render(document.body, <MyComponent />)
// equivalent to m.mount(document.body, m(MyComponent)) // equivalent to m.render(document.body, m(MyComponent))
``` ```
--- ---

View file

@ -3,7 +3,6 @@
- [What are keys](#what-are-keys) - [What are keys](#what-are-keys)
- [How to use](#how-to-use) - [How to use](#how-to-use)
- [Debugging key related issues](#debugging-key-related-issues) - [Debugging key related issues](#debugging-key-related-issues)
- [Avoid anti-patterns](#avoid-anti-patterns)
--- ---
@ -152,3 +151,56 @@ var things = [
] ]
``` ```
#### Avoid mixing keyed and non-keyed vnodes in the same array
An array of vnodes must have only keyed vnodes or non-keyed vnodes, but not both. If you need to mix them, create a nested array.
```javascript
// AVOID
m("div", [
m("div", "a"),
m("div", {key: 1}, "b"),
])
// PREFER
m("div", [
m("div", {key: 0}, "a"),
m("div", {key: 1}, "b"),
])
// PREFER
m("div", [
m("div", "a"),
[
m("div", {key: 1}, "b"),
]
])
```
#### Avoid passing model data directly to components if the model uses `key` as a data property
The `key` property may appear in your data model in a way that conflicts with Mithril's key logic. For example, a component may represent an entity whose `key` property is expected to change over time. This can lead to components receiving the wrong data, re-initialise, or change positions unexpectedly. If your data model uses the `key` property, make sure to wrap the data such that Mithril doesn't misinterpret it as a rendering instruction:
```javascript
// Data model
var users = [
{id: 1, name: "John", key: 'a'},
{id: 2, name: "Mary", key: 'b'},
]
// Later on...
users[0].key = 'c'
// AVOID
users.map(function(user){
// The component for John will be destroyed and recreated
return m(UserComponent, user)
})
// PREFER
users.map(function(user){
// Key is specifically extracted: data model is given its own property
return m(UserComponent, {key: user.id, model: user})
})
```

View file

@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
"use strict"
var fs = require("fs") var fs = require("fs")
var path = require("path") var path = require("path")
@ -100,11 +101,11 @@ function ensureLinkIsValid(file, data) {
} }
function initMocks() { function initMocks() {
global.window = require("../test-utils/browserMock")() global.window = require("../test-utils/browserMock")() // eslint-disable-line global-require
global.document = window.document global.document = window.document
global.m = require("../index") global.m = require("../index") // eslint-disable-line global-require
global.o = require("../ospec/ospec") global.o = require("../ospec/ospec") // eslint-disable-line global-require
global.stream = require("../stream") global.stream = require("../stream") // eslint-disable-line global-require
global.alert = function() {} global.alert = function() {}
//routes consumed by request.md //routes consumed by request.md
@ -121,7 +122,7 @@ function initMocks() {
"GET /api/v1/todos": function() { "GET /api/v1/todos": function() {
return {status: 200, responseText: JSON.stringify([])} return {status: 200, responseText: JSON.stringify([])}
}, },
"PUT /api/v1/users/1": function() { "PUT /api/v1/users/1": function(request) {
return {status: 200, responseText: request.query.callback ? request.query.callback + "([])" : "[]"} return {status: 200, responseText: request.query.callback ? request.query.callback + "([])" : "[]"}
}, },
"POST /api/v1/upload": function() { "POST /api/v1/upload": function() {

View file

@ -69,7 +69,7 @@ In contrast, traversing a javascript data structure has a much more predictable
### Differences from m.render ### Differences from m.render
A component rendered via `m.mount` automatically auto-redraws in response to view events, `m.redraw()` calls or `m.request()` calls. Vnodes rendered via `m.render()` do not. Note that calls to `m.prop()` do not trigger auto-redraws. A component rendered via `m.mount` automatically auto-redraws in response to view events, `m.redraw()` calls or `m.request()` calls. Vnodes rendered via `m.render()` do not.
`m.mount()` is suitable for application developers integrating Mithril widgets into existing codebases where routing is handled by another library or framework, while still enjoying Mithril's auto-redrawing facilities. `m.mount()` is suitable for application developers integrating Mithril widgets into existing codebases where routing is handled by another library or framework, while still enjoying Mithril's auto-redrawing facilities.

View file

@ -53,8 +53,8 @@ Argument | Type | Required | Descr
`options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`). `options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`).
`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.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`). `options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`).
`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 `xhr.responseText`. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. If `extract` is defined, `deserialize` will be skipped.
`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 automatically be parsed as JSON. `options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `xhr.responseText`, which is in turn passed to `deserialize`. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will not automatically 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`. `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

View file

@ -7,6 +7,7 @@
- [m.route.get](#mrouteget) - [m.route.get](#mrouteget)
- [m.route.prefix](#mrouteprefix) - [m.route.prefix](#mrouteprefix)
- [m.route.link](#mroutelink) - [m.route.link](#mroutelink)
- [m.route.param](#mrouteparam)
- [RouteResolver](#routeresolver) - [RouteResolver](#routeresolver)
- [routeResolver.onmatch](#routeresolveronmatch) - [routeResolver.onmatch](#routeresolveronmatch)
- [routeResolver.render](#routeresolverrender) - [routeResolver.render](#routeresolverrender)
@ -100,12 +101,41 @@ Argument | Type | Required | Description
##### m.route.link ##### m.route.link
`eventHandler = m.route.link(vnode)` This function can be used as the `oncreate` (and `onupdate`) hook in a `m("a")` vnode:
```JS
m("a[href=/]", {oncreate: m.route.link})`.
```
Using `m.route.link` as a `oncreate` hook causes the link to behave as a router link (i.e. it navigates to the route specified in `href`, instead of nagivating away from the current page to the URL specified in `href`.
If the `href` attribute is not static, the `onupdate` hook must also be set:
```JS
m("a", {href: someVariable, oncreate: m.route.link, onupdate: m.route.link})`
```
`m.route.link(vnode)`
Argument | Type | Required | Description Argument | Type | Required | Description
----------------- | ----------- | -------- | --- ----------------- | ----------- | -------- | ---
`vnode` | `Vnode` | Yes | This method is meant to be used in conjunction with an `<a>` [vnode](vnodes.md)'s [`oncreate` hook](lifecycle-methods.md) `vnode` | `Vnode` | Yes | This method is meant to be used as or in conjunction with an `<a>` [vnode](vnodes.md)'s [`oncreate` and `onupdate` hooks](lifecycle-methods.md)
**returns** | Function(e) | | Returns an event handler that calls `m.route.set` with the link's `href` as the `path` **returns** | | | Returns `undefined`
##### m.route.param
Retrieves a route parameter. A route parameter is a key-value pair. Route parameters may come from a few different places:
- route interpolations (e.g. if a route is `/users/:id`, and it resolves to `/users/1`, the route parameter has a key `id` and value `"1"`)
- router querystrings (e.g. if the path is `/users?page=1`, the route parameter has a key `page` and value `"1"`)
- `history.state` (e.g. if history.state is `{foo: "bar"}`, the route parameter has key `foo` and value `"bar"`)
`value = m.route.param(key)`
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** | `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
#### RouteResolver #### RouteResolver
@ -123,11 +153,11 @@ For more information on `onmatch`, see the [advanced component resolution](#adva
`routeResolver.onmatch(args, requestedPath)` `routeResolver.onmatch(args, requestedPath)`
Argument | Type | Description Argument | Type | Description
--------------- | ------------------------------ | --- --------------- | ---------------------------------------- | ---
`args` | `Object` | The [routing parameters](#routing-parameters) `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. `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.
**returns** | `Component|Promise<Component>` | 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"`. 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"`.
@ -139,11 +169,11 @@ The `render` method is called on every redraw for a matching route. It is simila
`vnode = routeResolve.render(vnode)` `vnode = routeResolve.render(vnode)`
Argument | Type | Description 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` | `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 `vnode.attrs` | `Object` | A map of URL parameter values
**returns** | `Vnode` | Returns a vnode **returns** | `Array<Vnode>|Vnode` | The [vnodes](vnodes.md) to be rendered
--- ---
@ -257,8 +287,6 @@ In the example above, we defined a route `/edit/:id`. This creates a dynamic rou
It's possible to have multiple arguments in a route, for example `/edit/:projectID/:userID` would yield the properties `projectID` and `userID` on the component's vnode attributes object. It's possible to have multiple arguments in a route, for example `/edit/:projectID/:userID` would yield the properties `projectID` and `userID` on the component's vnode attributes object.
In addition to routing parameters, the `attrs` object also includes a `path` property that contains the current route path, and a `route` property that contains the matched routed.
#### Key parameter #### Key parameter
When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from `/page/1` to `/page/2` given a route `/page/:id`, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the `onupdate` hook, rather than `oninit`/`oncreate`. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event. When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from `/page/1` to `/page/2` given a route `/page/:id`, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the `onupdate` hook, rather than `oninit`/`oncreate`. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event.
@ -385,7 +413,7 @@ var Layout = {
} }
``` ```
In the example above, the layout merely consists of a `<div class="layout">` that contains the children passed to the component, but in a real life scenario it could be as complex as neeeded. In the example above, the layout merely consists of a `<div class="layout">` that contains the children passed to the component, but in a real life scenario it could be as complex as needed.
One way to wrap the layout is to define an anonymous component in the routes map: One way to wrap the layout is to define an anonymous component in the routes map:
@ -501,7 +529,7 @@ For the sake of simplicity, in the example above, the user's logged in status is
var Auth = { var Auth = {
username: "", username: "",
password: "", password: "",
setUsername: function(value) { setUsername: function(value) {
Auth.username = value Auth.username = value
}, },

View file

@ -74,7 +74,7 @@ var User = {
module.exports = User module.exports = User
``` ```
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET http://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint. Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint.
```javascript ```javascript
// src/models/User.js // src/models/User.js
@ -85,7 +85,7 @@ var User = {
loadList: function() { loadList: function() {
return m.request({ return m.request({
method: "GET", method: "GET",
url: "http://rem-rest-api.herokuapp.com/api/users", url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true, withCredentials: true,
}) })
.then(function(result) { .then(function(result) {
@ -324,7 +324,7 @@ module.exports = {
m("input.input[type=text][placeholder=First name]"), m("input.input[type=text][placeholder=First name]"),
m("label.label", "Last name"), m("label.label", "Last name"),
m("input.input[placeholder=Last name]"), m("input.input[placeholder=Last name]"),
m("button.button[type=submit]", "Save"), m("button.button[type=button]", "Save"),
]) ])
} }
} }
@ -357,7 +357,7 @@ var User = {
loadList: function() { loadList: function() {
return m.request({ return m.request({
method: "GET", method: "GET",
url: "http://rem-rest-api.herokuapp.com/api/users", url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true, withCredentials: true,
}) })
.then(function(result) { .then(function(result) {
@ -380,7 +380,7 @@ var User = {
loadList: function() { loadList: function() {
return m.request({ return m.request({
method: "GET", method: "GET",
url: "http://rem-rest-api.herokuapp.com/api/users", url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true, withCredentials: true,
}) })
.then(function(result) { .then(function(result) {
@ -392,7 +392,7 @@ var User = {
load: function(id) { load: function(id) {
return m.request({ return m.request({
method: "GET", method: "GET",
url: "http://rem-rest-api.herokuapp.com/api/users/:id", url: "https://rem-rest-api.herokuapp.com/api/users/:id",
data: {id: id}, data: {id: id},
withCredentials: true, withCredentials: true,
}) })
@ -420,7 +420,7 @@ module.exports = {
m("input.input[type=text][placeholder=First name]", {value: User.current.firstName}), m("input.input[type=text][placeholder=First name]", {value: User.current.firstName}),
m("label.label", "Last name"), m("label.label", "Last name"),
m("input.input[placeholder=Last name]", {value: User.current.lastName}), m("input.input[placeholder=Last name]", {value: User.current.lastName}),
m("button.button[type=submit]", "Save"), m("button.button[type=button]", "Save"),
]) ])
} }
} }
@ -461,7 +461,12 @@ var User = require("../models/User")
module.exports = { module.exports = {
oninit: function(vnode) {User.load(vnode.attrs.id)}, oninit: function(vnode) {User.load(vnode.attrs.id)},
view: function() { view: function() {
return m("form", [ return m("form", {
onsubmit: function(e) {
e.preventDefault()
User.save()
}
}, [
m("label.label", "First name"), m("label.label", "First name"),
m("input.input[type=text][placeholder=First name]", { m("input.input[type=text][placeholder=First name]", {
oninput: m.withAttr("value", function(value) {User.current.firstName = value}), oninput: m.withAttr("value", function(value) {User.current.firstName = value}),
@ -472,7 +477,7 @@ module.exports = {
oninput: m.withAttr("value", function(value) {User.current.lastName = value}), oninput: m.withAttr("value", function(value) {User.current.lastName = value}),
value: User.current.lastName value: User.current.lastName
}), }),
m("button.button[type=submit]", {onclick: User.save}, "Save"), m("button.button[type=button]", "Save"),
]) ])
} }
} }
@ -491,7 +496,7 @@ var User = {
loadList: function() { loadList: function() {
return m.request({ return m.request({
method: "GET", method: "GET",
url: "http://rem-rest-api.herokuapp.com/api/users", url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true, withCredentials: true,
}) })
.then(function(result) { .then(function(result) {
@ -503,7 +508,7 @@ var User = {
load: function(id) { load: function(id) {
return m.request({ return m.request({
method: "GET", method: "GET",
url: "http://rem-rest-api.herokuapp.com/api/users/:id", url: "https://rem-rest-api.herokuapp.com/api/users/:id",
data: {id: id}, data: {id: id},
withCredentials: true, withCredentials: true,
}) })
@ -515,7 +520,7 @@ var User = {
save: function() { save: function() {
return m.request({ return m.request({
method: "PUT", method: "PUT",
url: "http://rem-rest-api.herokuapp.com/api/users/:id", url: "https://rem-rest-api.herokuapp.com/api/users/:id",
data: User.current, data: User.current,
withCredentials: true, withCredentials: true,
}) })

View file

@ -40,6 +40,14 @@ Streams are NOT bundled with Mithril's core distribution. To include the Streams
var Stream = require("mithril/stream") var Stream = require("mithril/stream")
``` ```
You can also download the module directly if your environment does not support a bundling toolchain:
```markup
<script src="https://unpkg.com/mithril-stream"></script>
```
When loaded directly with a `<script>` tag (rather than required), the stream library will be exposed as `window.m.stream`. If `window.m` is already defined (e.g. because you also use the main Mithril script), it will attach itself to the existing object. Otherwise it creates a new `window.m`. If you want to use streams in conjunction with Mithril as raw script tags, you should include Mithril in your page before `mithril-stream`, because `mithril` will otherwise overwrite the `window.m` object defined by `mithril-stream`. This is not a concern when the libraries are consumed as CommonJS modules (using `require(...)`).
--- ---
### Signature ### Signature

View file

@ -36,6 +36,22 @@ npm test
--- ---
### Running mithril in a non-browser environment
Mithril has a few dependencies on globals that exist in all its supported browser environments but are missing in all non-browser environments. To work around this you can use the browser mocks that ship with the mithril npm package.
The simplest way to do this is ensure the following snippet of code runs **before** you include mithril itself in your project.
```js
// Polyfill DOM env for mithril
global.window = require("mithril/test-utils/browserMock.js")();
global.document = window.document;
```
Once that snippet has been run you can `require("mithril")` and it should be quite happy.
---
### 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.

View file

@ -73,9 +73,11 @@ Property | Type | Description
`text` | `(String|Number|Boolean)?` | This is used instead of `children` if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the `text` property even if they have a text node as their only child. `text` | `(String|Number|Boolean)?` | This is used instead of `children` if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the `text` property even if they have a text node as their only child.
`dom` | `Element?` | Points to the element that corresponds to the vnode. This property is `undefined` in the `oninit` lifecycle method. In fragments and trusted HTML vnodes, `dom` points to the first element in the range. `dom` | `Element?` | Points to the element that corresponds to the vnode. This property is `undefined` in the `oninit` lifecycle method. In fragments and trusted HTML vnodes, `dom` points to the first element in the range.
`domSize` | `Number?` | This is only set in fragment and trusted HTML vnodes, and it's `undefined` in all other vnode types. It defines the number of DOM elements that the vnode represents (starting from the element referenced by the `dom` property). `domSize` | `Number?` | This is only set in fragment and trusted HTML vnodes, and it's `undefined` in all other vnode types. It defines the number of DOM elements that the vnode represents (starting from the element referenced by the `dom` property).
`state` | `Object` | An object that is persisted between redraws. In component vnodes, `state` is a shallow clone of the component object. `state` | `Object`? | An object that is persisted between redraws. It is provided by the core engine when needed. In component vnodes, the `state` inherits prototypically from the component object/class.
`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 it. `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 it.
--- ---
### Vnode types ### Vnode types