9

We can use .formData() of Body mixin to return a FormData representation of data at Chromium (Chrome) 60+ and Firefox 39+

Relevant specifications:

Errata

Related

How to manually create multipart/form-data using JavaScript at client and at server to serve the multipart/form-data as a response?

guest271314
  • 1
  • 15
  • 104
  • 177
  • The `Body.formData()` method is meant to be used by ServiceWorkers which would intercept user's request before it's been sent to the server. To create a FormData manually, you can use the FormData Constructor. – Kaiido Nov 08 '17 at 05:26
  • @Kaiido How did you draw the conclusion that `Body.formData()` is meant to be used for a specific purpose only? Am trying to build the `multipart/form-data` string by hand, from scratch, without using `FormData()`. – guest271314 Nov 08 '17 at 06:21
  • https://developer.mozilla.org/en-US/docs/Web/API/Body/formData – Kaiido Nov 08 '17 at 06:30
  • @Kaiido Your source is the MDN document only? – guest271314 Nov 08 '17 at 06:31
  • And the reading of what this method does. Servers usually don't send FormData requests to browsers, but maybe you've got a case? – Kaiido Nov 08 '17 at 06:32
  • @Kaiido MDN can be edited by users of MDN. That is not a binding document as to the intended usage of `Body.formData()` https://fetch.spec.whatwg.org/#dom-body-formdata – guest271314 Nov 08 '17 at 06:34
  • @Kaiido We can get the raw `multipart/form-data` https://stackoverflow.com/questions/40111982/get-http-body-of-form-in-javascript/, though how to create the data by hand? – guest271314 Nov 08 '17 at 06:47
  • Regarding you previous comment, I know MDN is driven by users, but this note was added by an MDN's official and don't bind anything, it just points out what motivated the creation of this method, it doesn't say you can **only** use it from SeviceWorkers. If you really want to create such data yourself, https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm and https://tools.ietf.org/html/rfc7578 But I don't see why send such data from a server since it is meant to return *a set of values as the result of a user filling out a form*. – Kaiido Nov 08 '17 at 06:55
  • @Kaiido The specification does not state that is the reason for `.formData()` usage. A server is not involved in the procedure – guest271314 Nov 08 '17 at 07:11
  • @Kaiido MDN is a valuable resource, though an occasional error or omission could occur https://stackoverflow.com/questions/36072936/is-it-possible-to-display-an-html-document-or-html-fragment-at-css-content, https://stackoverflow.com/questions/46718355/sending-a-sub-segment-of-an-arraybuffer-over-a-websocket-without-copying – guest271314 Nov 08 '17 at 07:21
  • 2
    Yes "I know MDN is driven by users" and do contains errors => https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter$history https://developer.mozilla.org/en-US/docs/Web/API/Request/mode$history – Kaiido Nov 08 '17 at 07:29

2 Answers2

16

You can create multipart/form-data manually with XMLHttpRequest like this example.

function multiPost(method, url, formHash){
    var boundary = "nVenJ7H4puv"
    var body = ""
    for(var key in formHash){
        body += "--" + boundary
             + "\r\nContent-Disposition: form-data; name=" + formHash[key].name
             + "\r\nContent-type: " + formHash[key].type
             + "\r\n\r\n" + formHash[key].value + "\r\n"
    }
    body += "--" + boundary + "--\r\n"

    var xml = new XMLHttpRequest();
    xml.open(method, url)
    xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary)
    xml.setRequestHeader("Content-Length", body.length)
    xml.send(body)
}
sapics
  • 1,104
  • 8
  • 22
  • Yes it would be better, if it is file type. – sapics Nov 09 '17 at 14:32
  • 2
    The code at Answer does not handle `` ``, correct? – guest271314 Nov 09 '17 at 14:47
  • You can handle file similar way. If your server can accept the base64, you can send file in base64 format with adding body `Content-Transfer-Encoding: base64`. It would help you https://stackoverflow.com/questions/7529159/javascript-isnt-uploading-binary-data – sapics Nov 09 '17 at 15:17
  • 2
    The code at your Answer does not handle `` element – guest271314 Nov 09 '17 at 15:23
  • 2
    We need a working example with an input of type file. Also, what is "formHash" in your code ? – trogne Aug 02 '18 at 22:40
  • @trogne im ahving same problem as you did you solve it? – Kay Dec 11 '19 at 13:06
  • I struggled with this! I was sending a FormData object to server in the body of a post. I found that the problem was with my Content-type header for that post! I just removed the Content-type header, then the browser created the correct multipart form data header for me, and voila! I got the idea from this question: https://stackoverflow.com/questions/39280438/fetch-missing-boundary-in-multipart-form-data-post – Erex Jun 12 '20 at 13:36
3

You can write you own FormData polyfill, or just google it "FormData polyfill"))) And also you can use normal FormData at browsers Chrome, FireFox, Opera, Safari, IE(10+), Edge. FormData polyfill is only useful for old IE, and for workers, but for workers you better should use this - https://gist.github.com/Rob--W/8b5adedd84c0d36aba64

wikipedia

standart formdata not of body

What you need to do? You want send formdata or recieve it at js?

You can try to use my polyfill, but I have not tested it.

sample:

var data = new RawFormData();
data.append("key","value")
data.append("key", new Blob("test"), "my file.txt");
data.getOutputDeferred().then(function(formData){
    var xml = new XMLHttpRequest();
    xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + data.getBoundry());
    xml.setRequestHeader("Content-Length", formData.length);
    xml.open(method, url);
    xml.send(formData);
});

code:

/**
* @constructor
*/
RawFormData = function () {
    this._promises = [];
    this._boundry = this.makeBoundary();
};

/**
* @return {string}
*/
RawFormData.prototype.getBoundary = function () {
    return this._boundry;
}

/**
* @return {string}
*/
RawFormData.prototype.makeBoundary = function () {
    return 'MyBoundary' + window.btoa(Math.random().toString()).substr(0, 12);
};

/**
* @param {string} name
* @param {string|number|File|Blob|boolean|null|undefined} val
* @param {string=} filename
*/
RawFormData.prototype.append = function (name, val, filename) {
    var prom = null;

    if(val instanceof File || val instanceof Blob){
        prom = this.readAsBinaryString(val).then(function(base64){
            var contentType = val.type || 'application/octet-stream';
            var result = '--' + this._boundry + '\r\n' +
                'Content-Disposition: form-data; ' +
                'name="' + name + '"; filename="' + this.encode_utf8(filename || "blob") + '"\r\n' +
                'Content-Type: ' + contentType + '\r\n\r\n' +
                base64 + '\r\n';
            return result;
        }.bind(this))
    }else{
        prom = new Promise(function(resolve){
            return '--' + this._boundry + '\r\n' +
                'Content-Disposition: form-data; ' +
                'name="' + this.encode_utf8(name) + '"\r\n\r\n' +
                this.encode_utf8(val) + '\r\n'
        }.bind(this));
    }

    this._promises.push(prom);

    return prom;
};
/**
* @return {File|Blob} blob
* @return {Promise<string>}
*/
RawFormData.prototype.readAsBinaryString = function (blob) {
        var reader = new FileReader();
        return new Promise(function(resolve,reject){
            var binStringCallback = function (e) {
                resolve(e.target.result);
            };

            var arrBufferCallback = function (e) {
                var binary = "";
                var bytes = new Uint8Array(e.target.result);
                var length = bytes.byteLength;
                for (var i = 0; i < length; i++) {
                    binary += String.fromCharCode(bytes[i]);
                }
                resolve(binary);
            };

            reader.onerror = reader.onabort = function () {
                resolve(null);
            };

            if (typeof reader.readAsBinaryString != "undefined") {
                reader.onload = binStringCallback;
                reader.readAsBinaryString(blob);
            } else {
                reader.onload = arrBufferCallback;
                reader.readAsArrayBuffer(blob);
            }
        });
};

RawFormData.prototype.encode_utf8 = function( s ){
   return unescape( encodeURIComponent( s ) );
}

RawFormData.prototype.getOutputDeferred = function () {
    return Promise.all(this._promises).then(function (rows) {
        var output = '--' + this._boundry + '\r\n';
        rows.forEach(function(row) {
            output += row;
        });
        output += '--' + this._boundry + '\r\n';
        return output;
    }.bind(this));
};
Alex Nikulin
  • 8,194
  • 4
  • 35
  • 37
  • We are looking for a simple and clear answer on how to send a file without using the FormData api. That is, mimic the FormData api work. That is, creating the object that FormData api creates. – trogne Aug 03 '18 at 03:58
  • @trogne you can send a file as base64 and attach it to request like a string. In modern browsers, you can use WebSocket and fetch API – Alex Nikulin Aug 03 '18 at 05:35
  • I know I can send as base64, but that's not what I want. I want to mimic FormData and not changing the server side code. – trogne Aug 03 '18 at 11:13