updates
This commit is contained in:
parent
94c83e9e50
commit
cc5213b09e
79 changed files with 1341 additions and 357 deletions
183
5-network/02-formdata/article.md
Normal file
183
5-network/02-formdata/article.md
Normal file
|
@ -0,0 +1,183 @@
|
|||
|
||||
# FormData
|
||||
|
||||
This chapter is about sending HTML forms: with or without files, with additional fields and so on. [FormData](https://xhr.spec.whatwg.org/#interface-formdata) is the object to handle that.
|
||||
|
||||
The constructor is:
|
||||
```js
|
||||
let formData = new FormData([form]);
|
||||
```
|
||||
|
||||
If HTML `form` element is provided, it automatically captures its fields.
|
||||
|
||||
Network methods, such as `fetch` can use `FormData` objects as a body, they are automatically encoded and sent with `Content-Type: form/multipart`.
|
||||
|
||||
So from the server point of view, that's a usual form submission.
|
||||
|
||||
## Sending a simple form
|
||||
|
||||
Let's send a simple form first.
|
||||
|
||||
As you can see, that's almost one-liner:
|
||||
|
||||
```html run autorun
|
||||
<form id="formElem">
|
||||
<input type="text" name="name" value="John">
|
||||
<input type="text" name="surname" value="Smith">
|
||||
<input type="submit">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
formElem.onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let response = await fetch('/article/formdata/post/user', {
|
||||
method: 'POST',
|
||||
*!*
|
||||
body: new FormData(formElem)
|
||||
*/!*
|
||||
});
|
||||
|
||||
let result = await response.json();
|
||||
|
||||
alert(result.message);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## FormData Methods
|
||||
|
||||
We can modify fields in `FormData` with methods:
|
||||
|
||||
- `formData.append(name, value)` - add a form field with the given `name` and `value`,
|
||||
- `formData.append(name, blob, fileName)` - add a field as if it were `<input type="file">`, the third argument `fileName` sets file name (not form field name), as it it were a name of the file in user's filesystem,
|
||||
- `formData.delete(name)` - remove the field with the given `name`,
|
||||
- `formData.get(name)` - get the value of the field with the given `name`,
|
||||
- `formData.has(name)` - if there exists a field with the given `name`, returns `true`, otherwise `false`
|
||||
|
||||
A form is technically allowed to have many fields with the same `name`, so multiple calls to `append` add more same-named fields.
|
||||
|
||||
There's also method `set`, with the same syntax as `append`. The difference is that `.set` removes all fields with the given `name`, and then appends a new field. So it makes sure there's only field with such `name`:
|
||||
|
||||
- `formData.set(name, value)`,
|
||||
- `formData.set(name, blob, fileName)`.
|
||||
|
||||
|
||||
Also we can iterate over formData fields using `for..of` loop:
|
||||
|
||||
```js run
|
||||
let formData = new FormData();
|
||||
formData.append('key1', 'value1');
|
||||
formData.append('key2', 'value2');
|
||||
|
||||
// List key/value pairs
|
||||
for(let [name, value] of formData) {
|
||||
alert(`${name} = ${value}`); // key1=value1, then key2=value2
|
||||
}
|
||||
```
|
||||
|
||||
## Sending a form with a file
|
||||
|
||||
The form is always sent as `Content-Type: form/multipart`. So, `<input type="file">` fields are sent also, similar to a usual form submission.
|
||||
|
||||
Here's an example with such form:
|
||||
|
||||
```html run autorun
|
||||
<form id="formElem">
|
||||
<input type="text" name="firstName" value="John">
|
||||
Picture: <input type="file" name="picture" accept="image/*">
|
||||
<input type="submit">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
formElem.onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let response = await fetch('/article/formdata/post/user-avatar', {
|
||||
method: 'POST',
|
||||
*!*
|
||||
body: new FormData(formElem)
|
||||
*/!*
|
||||
});
|
||||
|
||||
let result = await response.json();
|
||||
|
||||
alert(result.message);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Sending a form with blob
|
||||
|
||||
As we've seen in the chapter <info:fetch>, sending a dynamically generated `Blob`, e.g. an image, is easy. We can supply it directly as `fetch` body.
|
||||
|
||||
In practice though, it's often more convenient to send an image as a part of the form, with additional fields, such as "name" and other metadata.
|
||||
|
||||
Also, servers are usually more suited to accept multipart-encoded forms, rather than raw binary data.
|
||||
|
||||
This example submits an image from `<canvas>`, along with some other fields, using `FormData`:
|
||||
|
||||
```html run autorun height="90"
|
||||
<body style="margin:0">
|
||||
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
|
||||
|
||||
<input type="button" value="Submit" onclick="submit()">
|
||||
|
||||
<script>
|
||||
canvasElem.onmousemove = function(e) {
|
||||
let ctx = canvasElem.getContext('2d');
|
||||
ctx.lineTo(e.clientX, e.clientY);
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
async function submit() {
|
||||
let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
|
||||
|
||||
*!*
|
||||
let formData = new FormData();
|
||||
formData.append("firstName", "John");
|
||||
formData.append("image", imageBlob, "image.png");
|
||||
*/!*
|
||||
|
||||
let response = await fetch('/article/formdata/post/image-form', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
let result = await response.json();
|
||||
alert(result.message);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
Please note how the image `Blob` is added:
|
||||
|
||||
```js
|
||||
formData.append("image", imageBlob, "image.png");
|
||||
```
|
||||
|
||||
That's same as if there were `<input type="file" name="image">` in the form, and the visitor submitted a file `image.png` from their filesystem.
|
||||
|
||||
## Summary
|
||||
|
||||
[FormData](https://xhr.spec.whatwg.org/#interface-formdata) objects are used to read HTML form and submit it using `fetch` or another network method.
|
||||
|
||||
We can either create `new FormData(form)` from an HTML form, or create an empty object, and then append fields with methods:
|
||||
|
||||
- `formData.append(name, value)`
|
||||
- `formData.append(name, blob, fileName)`
|
||||
- `formData.set(name, value)`
|
||||
- `formData.set(name, blob, fileName)`
|
||||
|
||||
Two peculiarities here:
|
||||
1. The `set` method removes fields with the same name, `append` doesn't.
|
||||
2. To send a file, 3-argument syntax is needed, the last argument is a file name, that normally is taken from user filesystem for `<input type="file">`.
|
||||
|
||||
Other methods are:
|
||||
|
||||
- `formData.delete(name)`
|
||||
- `formData.get(name)`
|
||||
- `formData.has(name)`
|
||||
|
||||
That's it!
|
78
5-network/02-formdata/post.view/server.js
Normal file
78
5-network/02-formdata/post.view/server.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const Koa = require('koa');
|
||||
const app = new Koa();
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const getRawBody = require('raw-body')
|
||||
const busboy = require('async-busboy');
|
||||
const Router = require('koa-router');
|
||||
|
||||
let router = new Router();
|
||||
|
||||
router.post('/user', async (ctx) => {
|
||||
ctx.body = {
|
||||
message: "User saved."
|
||||
};
|
||||
});
|
||||
|
||||
router.post('/image-form', async (ctx) => {
|
||||
|
||||
let files = [];
|
||||
const { fields } = await busboy(ctx.req, {
|
||||
onFile(fieldname, file, filename, encoding, mimetype) {
|
||||
// read all file stream to continue
|
||||
let length = 0;
|
||||
file.on('data', function(data) {
|
||||
length += data.length;
|
||||
});
|
||||
file.on('end', () => {
|
||||
files.push({
|
||||
fieldname,
|
||||
filename,
|
||||
length
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
message: `Image saved, firstName: ${fields.firstName}, size:${files[0].length}, fileName: ${files[0].filename}.`
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
router.post('/user-avatar', async (ctx) => {
|
||||
|
||||
let files = [];
|
||||
const { fields } = await busboy(ctx.req, {
|
||||
onFile(fieldname, file, filename, encoding, mimetype) {
|
||||
// read all file stream to continue
|
||||
let length = 0;
|
||||
file.on('data', function(data) {
|
||||
length += data.length;
|
||||
});
|
||||
file.on('end', () => {
|
||||
files.push({
|
||||
fieldname,
|
||||
filename,
|
||||
length
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
message: `User with picture, firstName: ${fields.firstName}, picture size:${files[0].length}.`
|
||||
};
|
||||
});
|
||||
|
||||
app
|
||||
.use(bodyParser())
|
||||
.use(router.routes())
|
||||
.use(router.allowedMethods());
|
||||
|
||||
|
||||
if (!module.parent) {
|
||||
http.createServer(app.callback()).listen(8080);
|
||||
} else {
|
||||
exports.accept = app.callback();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue