71

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?

1
  • @Andreas This is one way to make the code shorter, so feel free to add it as an answer. Commented Mar 24, 2017 at 11:48

5 Answers 5

135

You could use URLSearchParams

const queryString = new URLSearchParams(new FormData(myForm)).toString()
Sign up to request clarification or add additional context in comments.

12 Comments

Great idea, except for current browser support. caniuse.com/#feat=urlsearchparams
@o-t-w because new URLSeachParams() returns an object so you need to convert it to a string in order to use it ;)
@o-t-w, if you don't want to use .toString(), try const queryString = `new URLSearchParams(new FormData(myForm))`;
Issue when checkboxs with same name
This gives error in Typescript: Argument of type 'FormData' is not assignable to parameter of type 'string | Record<string, string> | URLSearchParams | string[][] | undefined'.
As a workaround for TS: formData.entries().toArray().map(([k, v]) => [k, v.toString()])).toString();
|
11

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

1 Comment

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.
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.

1 Comment

JQuery eventually does something similar. I’m after the algorithmic implementation, not a way to hide the complexity.
2

Typescript workaround:

const formData = new FormData();
const searchParams = new URLSearchParams(
  formData as unknown as Record<string, string>,
).toString();

Comments

2

FormData can have File as a value, in which case the form serializes the file as its filename. That's why you can't pass FormData as URLSearchParams in TypeScript.

Therefore, the exact code to convert FormData into URLSearchParams is as follows:

const searchParams = new URLSearchParams(
  Array.from(formData, ([key, value]): [string, string] => {
    if (typeof value === 'string') {
      return [key, value];
    } else {
      return [key, value.name];
    }
  }),
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.