19

Couchdb only parse application/x-www-form-urlencoded. Is there a FormData() attribute that set the enctype?

xhr.open('put',document.myForm.action,false)
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
xhr.send(new FormData(document.myForm))
Gert Cuykens
  • 6,845
  • 13
  • 50
  • 84
  • 1
    Wouldn't you just set the `Content-Type` header in the AJAX request to `application/x-www-form-urlencoded` ? If you use jQuery, this is the default Content-Type header for $.ajax(). If you use the regular XMLHttpRequest, it's `xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')`. – ampersand Sep 25 '11 at 00:49
  • 2
    In chrome it still sends multipart/form-data anyway. – Gert Cuykens Sep 25 '11 at 01:32
  • 1
    What framework, if any, are you using for the AJAX request? – ampersand Sep 25 '11 at 01:35
  • none just doing xhr.open('put',document.user.action,false) xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded') xhr.send(new FormData(document.user)) – Gert Cuykens Sep 25 '11 at 01:38
  • I just tried this in Chrome 14 with a basic form, and it works. Post your form HTML for us to see; it's a far shot, but perhaps there is some issue with it. – ampersand Sep 25 '11 at 02:20
  • http://gert.iriscouch.com/www/user/user.htm if you check the xhr header in chrome inspector you wil see multipart/form-data – Gert Cuykens Sep 25 '11 at 03:07
  • I see application/x-www-form-urlencoded in the Content-Type request header. The response from the server is a 409 Conflict with the following body: `{"error":"conflict","reason":"Document update conflict."}`. I'm not sure what could be happening on your end, but it seems to be working as expected – ampersand Sep 25 '11 at 03:19
  • the data send should not be ------WebKitFormBoundaryZRNvtqo5x6N8YfOn but name1=value1&name2=value2 – Gert Cuykens Sep 25 '11 at 03:32
  • ah, that's an entirely different issue, and one that I cannot answer. Perhaps a new question is in order? In the meantime, maybe you should try using jQuery's $.ajax along with .serialize (http://api.jquery.com/serialize/) to send form data? Perhaps, Chrome's/Safari's FormData() is not ready for prime-time...i don't know. – ampersand Sep 25 '11 at 04:05
  • formURI=function(v){ var t=v.getElementsByTagName('input') var s='' for(i in t)if(t[i].type=='text')s+=encodeURIComponent(t[i].name)+'='+encodeURIComponent(t[i].value)+'&' return s.slice(0,-1) } i use this function for now. – Gert Cuykens Sep 25 '11 at 04:39
  • 1
    ok, I would also recommend that you use jquery.couch.js (found at http://gert.iriscouch.com/_utils/script/jquery.couch.js) to interact w/ your couchdb. The documentation is at http://daleharvey.github.com/jquery.couch.js-docs/symbols/%24.couch.html – ampersand Sep 25 '11 at 05:05

5 Answers5

30

FormData will always be sent as multipart/form-data.

If you just post normal form data without uploading files, multipart/form-data is better.

If you want to upload files, multipart/form-data is better.

If you have to upload files with x-www-form-urlencoded, you should convert the files data to string data via base64 etc, and write special server-end code. This is strongly not recommended.

If you want to send FormData as x-www-form-urlencoded, there are 2 ways at least.

1. Send URLSearchParams directly:

// use xhr API
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
xhr.send(new URLSearchParams(formDataObj));

// or use fetch API
fetch(url, {
    method: 'post',
    body: new URLSearchParams(formDataObj)
});

2. Encode the content items to querystring.

function urlencodeFormData(fd){
    var s = '';
    function encode(s){ return encodeURIComponent(s).replace(/%20/g,'+'); }
    for(var pair of fd.entries()){
        if(typeof pair[1]=='string'){
            s += (s?'&':'') + encode(pair[0])+'='+encode(pair[1]);
        }
    }
    return s;
}

var form = document.myForm;
xhr.open('POST', form.action, false);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
xhr.send(urlencodeFormData(new FormData(form)));

you can also use URLSearchParams like this:

function urlencodeFormData(fd){
    var params = new URLSearchParams(fd);
    return params.toString();
}

For old browsers which doesn't support URLSearchParams API, you can use one of polyfills:

cuixiping
  • 24,167
  • 8
  • 82
  • 93
  • 1
    MDN says it is not supported by IE and Safari. Also the support for this requires pretty high versions of the other browsers: Chrome 49, Opera 36 and Firefox 29 (for `entries()` Firefox 44 is required): https://developer.mozilla.org/en/docs/Web/API/URLSearchParams – StanE Aug 14 '16 at 08:28
  • There are polyfills for old browsers which doesn't support URLSearchParams API – cuixiping Aug 14 '16 at 11:54
  • I know. This was not a negative criticism from me. I just wanted to point out, that this technique is very handy, but also quite new. :-) – StanE Aug 14 '16 at 15:13
  • I'm glad you seem to have looked at my answer. ;-) – StanE Aug 14 '16 at 15:21
  • Yes I looked your answer. but your answer has some bugs. you cannot handle form elements so easily. there are some different cases (disabled, checked, selected). – cuixiping Aug 14 '16 at 18:21
  • urlencodeFormData fails to correctly format forms containing File uploads. – Steve Owens Dec 16 '17 at 21:27
  • 1
    @SteveOwens You must use multipart/form-data if you want upload file. – cuixiping Dec 18 '17 at 03:32
  • I spent an hour looking for why form data can't be sent as `application/x-www-form-urlencoded` and didn't know that it's always sent as `multipart/form-data`. Thanks a lot! – Anh Tran Jan 04 '21 at 08:50
24

Here's a simpler way to do it that doesn't rely on writing your own conversions:

 const form = document.getElementById('my_form')
 fetch(form.action,
       { body: new URLSearchParams(new FormData(form)) })

It uses the fact that the URLSearchParams constructor can accept a FormData object (anything that will iterate pairs of values, I think) and that fetch knows to convert URLSearchParams into a string and set the Content-Type header correctly.

cmc
  • 973
  • 8
  • 17
  • 1
    The browsers will automatically add the header, so you don't need to do it yourself (remove the `headers` field and see...the official spec is here, scroll down to item number 6 to see: https://fetch.spec.whatwg.org/#bodyinit-unions) – Eric Mutta Jun 29 '22 at 21:51
8

No, the XHR2 "send" method is specified to always send FormData objects as multipart/form-data.

As ampersand suggests, one option would be to use the jquery.couch.js plugin that's built into every CouchDB instance within Futon.

If you like a bit more generic HTTP interface, Fermata also has support for URL encoded requests:

fermata.json(document.myForm.action).put({'Content-Type':"application/x-www-form-urlencoded"}, {...form fields...});

Another option would be to send JSON to your update function (which I'm assuming is the 'action' URL of your form) instead.

Of course, the trick with any of these is that you'll have to extract the form fields yourself, since there's no easy DOM-level equivalent of new FormData(document.myForm) that returns an Object instead AFAIK.

natevw
  • 16,807
  • 8
  • 66
  • 90
0

Some time ago I wrote the following function. It collects form values and encodes them url encoded, so they can be sent with content type application/x-www-form-urlencoded:

function getURLencodedForm(form)
{
  var urlEncode = function(data, rfc3986)
  {
    if (typeof rfc3986 === 'undefined') {
      rfc3986 = true;
    }

    // Encode value
    data = encodeURIComponent(data);
    data = data.replace(/%20/g, '+');

    // RFC 3986 compatibility
    if (rfc3986)
    {
      data = data.replace(/[!'()*]/g, function(c) {
        return '%' + c.charCodeAt(0).toString(16);
      });
    }

    return data;
  };

  if (typeof form === 'string') {
    form = document.getElementById(form);
  }

  var url = [];
  for (var i=0; i < form.elements.length; ++i)
  {
    if (form.elements[i].name != '')
    {
      url.push(urlEncode(form.elements[i].name) + '=' + urlEncode(form.elements[i].value));
    }
  }

  return url.join('&');
}

// Example (you can test & execute this here on this page on stackoverflow)
var url = getURLencodedForm('post-form');
alert(url);
StanE
  • 2,704
  • 29
  • 38
0

Further building upon the response by @cmc and combining snippets from FormData API and Codemag's Fetch API tutorial, I use the following for a POST request that requires the Content-Type to be application/x-www-form-urlencoded:

const form = document.forms.namedItem("my_form");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  fetch(form.action, {
    method: "POST",
    body: new URLSearchParams(new FormData(form)),
  })
    .then((response) => {
      if (response.ok) {
        return response.json();
      } else {
        return response.text();
      }
    })
    .then((data) => displayMessage(JSON.stringify(data)))
    .catch((error) => {
      console.error("Error: ", error);
    });
});
ar13pit
  • 46
  • 4