57

I’m sending a POST request via XMLHttpRequest with data entered into an HTML form. The form without interference of JavaScript would submit its data encoded as application/x-www-form-urlencoded.

With the XMLHttpRequest, I wanted to send the data with via the FormData API which does not work since it treats the data as if it were encoded as multipart/form-data. Therefor I need to write the data as a query string, properly escaped, into the send method of the XMLHttpRequest.

addEntryForm.addEventListener('submit', function(event) {
    // Gather form data
    var formData = new FormData(this);
    // Array to store the stringified and encoded key-value-pairs.
    var parameters = []
    for (var pair of formData.entries()) {
        parameters.push(
            encodeURIComponent(pair[0]) + '=' +
            encodeURIComponent(pair[1])
        );
    }

    var httpRequest = new XMLHttpRequest();
    httpRequest.open(form.method, form.action);

    httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

    httpRequest.onreadystatechange = function() {
        if (httpRequest.readyState === XMLHttpRequest.DONE) {
            if (httpRequest.status === 200) {
                console.log('Successfully submitted the request');
            } else {
                console.log('Error while submitting the request');
            }
        }
    };

    httpRequest.send(parameters.join('&'));

    // Prevent submitting the form via regular request
    event.preventDefault();
});

Now this whole thing with the for ... of loop, etc. seems a bit convoluted. Is there a simpler way to transform FormData into a query string? Or can I somehow send FormData with a different encoding?

kleinfreund
  • 6,546
  • 4
  • 30
  • 60

3 Answers3

115

You could use URLSearchParams

const queryString = new URLSearchParams(new FormData(myForm)).toString()
Björn
  • 12,587
  • 12
  • 51
  • 70
  • 2
    Great idea, except for current browser support. https://caniuse.com/#feat=urlsearchparams – Nathan Strutz Feb 28 '18 at 20:54
  • Why do you need `.toString()`? – Ollie Williams Mar 18 '18 at 20:44
  • 3
    @o-t-w because `new URLSeachParams()` returns an object so you need to convert it to a string in order to use it ;) – Adrien G Apr 19 '18 at 14:26
  • 1
    @o-t-w, if you don't want to use `.toString()`, try `const queryString = \`new URLSearchParams(new FormData(myForm))\`;` – ParaBolt May 03 '19 at 13:15
  • In case for someone like me found error in Edge, use Lodash and update the code to this can help. `new URLSearchParams(_.toArray(new FormData(myForm))).toString()`. Poor MS Edge! – vee Nov 14 '19 at 11:41
  • 1
    Issue when checkboxs with same name – ahdung Apr 15 '22 at 04:57
  • is it possible to get the url path for which you will submit to from FormData? – mike01010 Sep 13 '22 at 22:44
  • @mike01010 the URL isn't usually contained inside the FormData. If your app does, you simply query the value by it's key: `myFormData.get(myKey)` – Design by Adrian Feb 20 '23 at 09:13
  • @NathanStrutz sorry to follow up on such an old comment - but caniuse says that every browser had support for it in 2018. why did you say that? because edge only got it in a later release? – KTibow Jul 22 '23 at 22:20
  • @KTibow Internet Explorer, for one (which I was forced to support until like 2021). Looks like it wasn't supported by Edge either until later in 2018. Honestly hard to say really since that was so long ago. All good now, probably a good solution. – Nathan Strutz Jul 24 '23 at 00:09
10

You've asked for a simpler solution...
A for loop is the simplest way to traverse over a collection - imho.

But there is a shorter version if you use the spread operator/syntax (...)

The spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.

Your for...of loop can then be replaced with:

let parameters = [...formData.entries()] // expand the elements from the .entries() iterator into an actual array
                     .map(e => encodeURIComponent(e[0]) + "=" + encodeURIComponent(e[1]))  // transform the elements into encoded key-value-pairs
Andreas
  • 21,535
  • 7
  • 47
  • 56
  • Though I accepted this answer, I do think it's better to write a loop like I did in my question, because it's easier to read. – kleinfreund Apr 03 '17 at 13:53
5

If you could use JQuery, you'll simply call .serialize() function on your form object, like follow:

function getQueryString() {
  var str = $( "form" ).serialize();
  console.log(str);
}

$( "#cmdTest" ).on( "click", getQueryString );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<form>
  <select name="list1">
    <option>opt1</option>
    <option>opt2</option>
  </select>
 
  <br>
  <input type="text" name="txt1" value="valWith%Special&Char$">
  <br>
  <input type="checkbox" name="check" value="check1" id="ch1">
  <label for="ch1">check1</label>
  <input type="checkbox" name="check" value="check2" checked="checked" id="ch2">
  <label for="ch2">check2</label>
 
  <br>
  <input type="radio" name="radio" value="radio1" checked="checked" id="r1">
  <label for="r1">radio1</label>
  <input type="radio" name="radio" value="radio2" id="r2">
  <label for="r2">radio2</label>
</form>
  
<button id="cmdTest">Get queryString</button>

Note: It also encode data for use it in url request

I hope it helps you, bye.

Alessandro
  • 4,382
  • 8
  • 36
  • 70
  • 1
    JQuery eventually does something similar. I’m after the algorithmic implementation, not a way to hide the complexity. – kleinfreund Mar 24 '17 at 11:50