Fix for #2423. request.data => body, params is used for querystrings (#2425)

* Fix for #2423. request.data is replaced by body, params is used for querystring interpolation.

* Updated documentation after code review by shadowhand and isiahmeadows

* Convert indentation to tabs

* Replacing m.request.data and m.jsonp.data with params or body.

* Update request.md


Co-authored-by: Isiah Meadows <contact@isiahmeadows.com>
This commit is contained in:
Erik Vullings 2019-06-11 01:36:33 +02:00 committed by Isiah Meadows
parent e82f6d00c2
commit 8a7eae00ed
7 changed files with 40 additions and 40 deletions

View file

@ -79,7 +79,7 @@ m("a[href='/Home']", {oncreate: m.route.link}, "Go to home page")
m.request({
method: "PUT",
url: "/api/v1/users/:id",
data: {id: 1, name: "test"}
params: {id: 1, name: "test"}
})
.then(function(result) {
console.log(result)
@ -93,7 +93,7 @@ m.request({
```javascript
m.jsonp({
url: "/api/v1/users/:id",
data: {id: 1},
params: {id: 1},
callbackKey: "callback",
})
.then(function(result) {

View file

@ -255,7 +255,7 @@ Basically, XHR is just a way to talk to a server.
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial.
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `body` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
```javascript
var count = 0
@ -263,7 +263,7 @@ var increment = function() {
m.request({
method: "PUT",
url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
data: {count: count + 1},
body: {count: count + 1},
withCredentials: true,
})
.then(function(data) {

View file

@ -14,7 +14,7 @@ Makes JSON-P requests. Typically, it's useful to interact with servers that allo
```javascript
m.jsonp({
url: "/api/v1/users/:id",
data: {id: 1},
params: {id: 1},
callbackKey: "callback",
})
.then(function(result) {
@ -31,8 +31,8 @@ m.jsonp({
Argument | Type | Required | Description
---------------------- | --------------------------------- | -------- | ---
`options` | `Object` | Yes | The request options to pass.
`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.data`.
`options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring.
`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.params`.
`options.params` | `Object` | No | The data to be interpolated into the URL and serialized into the querystring.
`options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
`options.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`)

View file

@ -204,7 +204,7 @@ In the refactored version, it's trivial to test whether `getFirstTen` has any of
Promises absorb other promises. Basically, this means you can never receive a Promise as an argument to `onFulfilled` or `onRejected` callbacks for `then` and `catch` methods. This feature allows us to flatten nested promises to make code more manageable.
```javascript
function searchUsers(q) {return m.request("/api/v1/users/search", {data: {q: q}})}
function searchUsers(q) {return m.request("/api/v1/users/search", {params: {q: q}})}
function getUserProjects(id) {return m.request("/api/v1/users/" + id + "/projects")}
// AVOID: pyramid of doom

View file

@ -27,7 +27,7 @@ Makes XHR (aka AJAX) requests, and returns a [promise](promise.md)
m.request({
method: "PUT",
url: "/api/v1/users/:id",
data: {id: 1, name: "test"}
params: {id: 1, name: "test"}
})
.then(function(result) {
console.log(result)
@ -44,8 +44,9 @@ Argument | Type | Required | Descr
------------------------- | --------------------------------- | -------- | ---
`options` | `Object` | Yes | The request options to pass.
`options.method` | `String` | No | The HTTP method to use. This value should be one of the following: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` or `OPTIONS`. Defaults to `GET`.
`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.data`.
`options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring (for GET requests) or body (for other types of requests).
`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.params`.
`options.params` | `Object` | No | The data to be interpolated into the URL and/or serialized into the query string.
`options.body` | `Object` | No | The data to be serialized into the body (for other types of requests).
`options.async` | `Boolean` | No | Whether the request should be asynchronous. Defaults to `true`.
`options.user` | `String` | No | A username for HTTP authorization. Defaults to `undefined`.
`options.password` | `String` | No | A password for HTTP authorization. Defaults to `undefined`. This option is provided for `XMLHttpRequest` compatibility, but you should avoid using it because it sends the password in plain text over the network.
@ -55,10 +56,9 @@ Argument | Type | Required | Descr
`options.config` | `xhr = Function(xhr)` | No | Exposes the underlying XMLHttpRequest object for low-level configuration. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
`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.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 `body`. Defaults to `JSON.stringify`, or if `options.body` 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(any)` | No | A deserialization method to be applied to the `xhr.response` or normalized `xhr.responseText`. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). If `extract` is defined, `deserialize` will be skipped.
`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 `options.deserialize(parsedResponse)`, throwing an exception when the server response status code indicates an error or when the response is syntactically invalid. 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 be left as-is when the promise resolves.
`options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods.
`options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`.
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods
@ -134,7 +134,7 @@ m.route(document.body, "/", {
Let's assume making a request to the server URL `/api/items` returns an array of objects in JSON format.
When `m.route` is called at the bottom, the `Todos` component is initialized. `oninit` is called, which calls `m.request`. This retrieves an array of objects from the server asynchronously. "Asynchronously" means that JavaScript continues running other code while it waits for the response from server. In this case, it means `fetch` returns, and the component is rendered using the original empty array as `Data.todos.list`. Once the request to the server completes, the array of objects `items` is assigned to `Data.todos.list` and the component is rendered again, yielding a list of `<div>`s containing the titles of each todo.
When `m.route` is called at the bottom, the `Todos` component is initialized. `oninit` is called, which calls `m.request`. This retrieves an array of objects from the server asynchronously. "Asynchronously" means that JavaScript continues running other code while it waits for the response from server. In this case, it means `fetch` returns, and the component is rendered using the original empty array as `Data.todos.list`. Once the request to the server completes, the array of objects `items` is assigned to `Data.todos.list` and the component is rendered again, yielding a list of `<div>`s containing the titles of each `todo`.
---
@ -204,31 +204,31 @@ Request URLs may contain interpolations:
m.request({
method: "GET",
url: "/api/v1/users/:id",
data: {id: 123},
params: {id: 123},
}).then(function(user) {
console.log(user.id) // logs 123
})
```
In the code above, `:id` is populated with the data from the `{id: 123}` object, and the request becomes `GET /api/v1/users/123`.
In the code above, `:id` is populated with the data from the `params` object, and the request becomes `GET /api/v1/users/123`.
Interpolations are ignored if no matching data exists in the `data` property.
Interpolations are ignored if no matching data exists in the `params` property.
```javascript
m.request({
method: "GET",
url: "/api/v1/users/foo:bar",
data: {id: 123},
params: {id: 123},
})
```
In the code above, the request becomes `GET /api/v1/users/foo:bar`
In the code above, the request becomes `GET /api/v1/users/foo:bar?id=123`
---
### Aborting requests
Sometimes, it is desirable to abort a request. For example, in an autocompleter/typeahead widget, you want to ensure that only the last request completes, because typically autocompleters fire several requests as the user types and HTTP requests may complete out of order due to the unpredictable nature of networks. If another request finishes after the last fired request, the widget would display less relevant (or potentially wrong) data than if the last fired request finished last.
Sometimes, it is desirable to abort a request. For example, in an autocompleter/typeahead widget, you want to ensure that only the last request completes, because typically autocompleters fire several requests as the user types and HTTP requests may complete out of order due to the unpredictable nature of networks. If another request finishes after the last fired request, the widget would display less relevant (or potentially wrong) data than if the last fired request finished last.
`m.request()` exposes its underlying `XMLHttpRequest` object via the `options.config` parameter, which allows you to save a reference to that object and call its `abort` method when required:
@ -240,7 +240,7 @@ function search() {
m.request({
method: "GET",
url: "/api/v1/users",
data: {search: query},
params: {search: query},
config: function(xhr) {searchXHR = xhr}
})
}
@ -274,24 +274,24 @@ Next, you need to create a [`FormData`](https://developer.mozilla.org/en/docs/We
function upload(e) {
var file = e.target.files[0]
var data = new FormData()
data.append("myfile", file)
var body = new FormData()
body.append("myfile", file)
}
```
Next, you need to call `m.request` and set `options.method` to an HTTP method that uses body (e.g. `POST`, `PUT`, `PATCH`) and use the `FormData` object as `options.data`.
Next, you need to call `m.request` and set `options.method` to an HTTP method that uses body (e.g. `POST`, `PUT`, `PATCH`) and use the `FormData` object as `options.body`.
```javascript
function upload(e) {
var file = e.target.files[0]
var data = new FormData()
data.append("myfile", file)
var body = new FormData()
body.append("myfile", file)
m.request({
method: "POST",
url: "/api/v1/upload",
data: data,
body: body,
})
}
```
@ -312,15 +312,15 @@ m.render(document.body, [
function upload(e) {
var files = e.target.files
var data = new FormData()
var body = new FormData()
for (var i = 0; i < files.length; i++) {
data.append("file" + i, files[i])
body.append("file" + i, files[i])
}
m.request({
method: "POST",
url: "/api/v1/upload",
data: data,
body: body,
})
}
```
@ -348,13 +348,13 @@ m.mount(document.body, {
function upload(e) {
var file = e.target.files[0]
var data = new FormData()
data.append("myfile", file)
var body = new FormData()
body.append("myfile", file)
m.request({
method: "POST",
url: "/api/v1/upload",
data: data,
body: body,
config: function(xhr) {
xhr.upload.addEventListener("progress", function(e) {
progress = e.loaded / e.total
@ -391,7 +391,7 @@ m.request({
})
```
In the example above, assuming `/api/v1/users` returns an array of objects, the `User` constructor will be instantiated (i.e. called as `new User(data)`) for each object in the array. If the response returned a single object, that object would be used as the `data` argument.
In the example above, assuming `/api/v1/users` returns an array of objects, the `User` constructor will be instantiated (i.e. called as `new User(data)`) for each object in the array. If the response returned a single object, that object would be used as the `body` argument.
---

View file

@ -67,18 +67,18 @@ Argument | Type | Required | D
Redirects to a matching route, or to the default route if no matching routes can be found. Triggers an asynchronous redraw off all mount points.
`m.route.set(path, data, options)`
`m.route.set(path, params, options)`
Argument | Type | Required | Description
----------------- | --------- | -------- | ---
`path` | `String` | Yes | The [path name](paths.md) to route to, without a prefix. The path may include parameters, interpolated with values from `data`.
`data` | `Object` | No | Routing parameters. If `path` has routing parameter slots, the properties of this object are interpolated into the path string
`path` | `String` | Yes | The [path name](paths.md) to route to, without a prefix. The path may include parameters, interpolated with values from `params`.
`params` | `Object` | No | Routing parameters. If `path` has routing parameter slots, the properties of this object are interpolated into the path string
`options.replace` | `Boolean` | No | Whether to create a new history entry or to replace the current one. Defaults to false
`options.state` | `Object` | No | The `state` object to pass to the underlying `history.pushState` / `history.replaceState` call. This state object becomes available in the `history.state` property, and is merged into the [routing parameters](#routing-parameters) object. Note that this option only works when using the pushState API, but is ignored if the router falls back to hashchange mode (i.e. if the pushState API is not available)
`options.title` | `String` | No | The `title` string to pass to the underlying `history.pushState` / `history.replaceState` call.
**returns** | | | Returns `undefined`
Remember that when using `.set` with params you also need to define the route:
Remember that when using `.set` with `params` you also need to define the route:
```javascript
var Article = {
view: function(vnode) {
@ -582,7 +582,7 @@ var Auth = {
login: function() {
m.request({
url: "/api/v1/auth",
data: {username: Auth.username, password: Auth.password}
params: {username: Auth.username, password: Auth.password}
}).then(function(data) {
localStorage.setItem("auth-token", data.token)
m.route.set("/secret")

View file

@ -522,7 +522,7 @@ var User = {
return m.request({
method: "PUT",
url: "https://rem-rest-api.herokuapp.com/api/users/" + User.current.id,
data: User.current,
body: User.current,
withCredentials: true,
})
}