Search code examples
javascripthtmlform-data

Form to Array of Object using FormData


I am working on a form that the user can add or remove friends' information.
My current code is something like below:

(() => {
  let currentCount = 0;
  const theForm = document.querySelector("#theForm");

  document
    .querySelector("#addMoreFields")
    .addEventListener("click", (event) => {
      event.preventDefault();

      // find and clone the template content
      const template = theForm.getElementsByTagName("template")[0];
      const clone = template.content.cloneNode(true);
      const submitButton = document.querySelector("#submitForm");

      // if the submitButton is hidden, show it
      if (submitButton.offsetParent === null) {
        submitButton.removeAttribute("style");
      }

      // add class to set and name on input fields
      clone.querySelector(".set").classList.add(`set-${currentCount}`);
      clone
        .querySelector(".first_name")
        .setAttribute("name", `firstName[${currentCount}]`);
      clone
        .querySelector(".middle_name")
        .setAttribute("name", `middleName[${currentCount}]`);
      clone
        .querySelector(".last_name")
        .setAttribute("name", `lastName[${currentCount}]`);
      clone.querySelectorAll(".checkbox").forEach(function (item) {
        item.setAttribute("name", `checkbox[${currentCount}]`);
      });
      clone.querySelectorAll(".radio").forEach(function (item) {
        item.setAttribute("name", `radio[${currentCount}]`);
      });

      // remove button
      clone
        .querySelector(".removeSet")
        .setAttribute("id", `remove-${currentCount}`);
      clone
        .querySelector(".removeSet")
        .setAttribute("data-number", currentCount);

      theForm.append(clone);

      // add event listener to removeSet button
      document
        .querySelector(`#remove-${currentCount}`)
        .addEventListener("click", (event) => {
          event.preventDefault();
          const setName = `.set-${event.target.getAttribute("data-number")}`;
          document.querySelector(setName).remove();
        });

      currentCount++;
    });

  theForm.addEventListener("submit", (event) => {
    event.preventDefault();

    const theFormData = new FormData(theForm);
    /*
      we need to format the form as:
      ```
      [
        {
          "first_name": "x",
          "middle_name": "x",
          "last_name": "x",
          "checkbox": ['checkbox a', 'checkbox c'],
          "radio": "radio b",
          "textarea": "some text"
        },
        {
          "first_name": "y",
          "middle_name": "y",
          "last_name": "y",
          "checkbox": ['checkbox a', 'checkbox c'],
          "radio": "radio b",
          "textarea": "some text"
        },
        ...
      ]
      ```
    */
  });
})();
.set {
  margin: 5px;
  padding: 5px;
  outline: 1px solid #ccc;
}
.removeSet {
  float: right;
}
<form id="theForm">
  <template>
    <div class="set">
      <button class="removeSet">remove</button>
      <input class="first_name" placeholder="first name" /><br>
      <input class="middle_name" placeholder="middle name" /><br>
      <input class="last_name" placeholder="last name" /><br>
      <!-- checkbox -->
      <label><input type="checkbox" class="checkbox" value="checkbox a" />checkbox a</label>
      <label><input type="checkbox" class="checkbox" value="checkbox b" />checkbox b</label>
      <label><input type="checkbox" class="checkbox" value="checkbox c" />checkbox c</label><br>
      <!-- radio -->
      <label><input type="radio" class="radio" value="radio a" />radio a</label>
      <label><input type="radio" class="radio" value="radio b" />radio b</label>
      <label><input type="radio" class="radio" value="radio c" />radio c</label><br>
      <!-- textarea -->
      <textarea class="textarea" rows="4" cols="50">additional notes</textarea>
    </div>
  </template>
  <button id="addMoreFields">add</button>
  <button id="submitForm" style="display: none;">submit</button>
</form>

My issue is, how can I create an array of objects to be sent to the back-end with the format as follow:

[
  {
    "first_name": "x",
    "middle_name": "x",
    "last_name": "x",
    "checkbox": ['checkbox a', 'checkbox c'],
    "radio": "radio b",
    "textarea": "some text"
  },
  {
    "first_name": "x",
    "middle_name": "x",
    "last_name": "x",
    "checkbox": ['checkbox a', 'checkbox c'],
    "radio": "radio b",
    "textarea": "some text"
  },
  ...
]

I am using new FormData but I am open to other alternatives.
We are only using vanilla javascript so jQuery is not an option 😞


Solution

  • This is how I solve my issue. 😊
    I added comments to make it understandable. ✌

    (() => {
      let currentCount = 0;
      const theForm = document.querySelector("#theForm");
    
      document
        .querySelector("#addMoreFields")
        .addEventListener("click", (event) => {
          event.preventDefault();
    
          // find and clone the template content
          const template = theForm.getElementsByTagName("template")[0];
          const clone = template.content.cloneNode(true);
          const submitButton = document.querySelector("#submitForm");
    
          // if the submitButton is hidden, show it
          if (submitButton.offsetParent === null) {
            submitButton.removeAttribute("style");
          }
    
          // add class to set and name on input fields
          clone.querySelector(".set").classList.add(`set-${currentCount}`);
          clone
            .querySelector(".first_name")
            .setAttribute("name", `firstName[${currentCount}]`);
          clone
            .querySelector(".middle_name")
            .setAttribute("name", `middleName[${currentCount}]`);
          clone
            .querySelector(".last_name")
            .setAttribute("name", `lastName[${currentCount}]`);
          clone.querySelectorAll(".checkbox").forEach(function (item) {
            item.setAttribute("name", `checkbox[${currentCount}]`);
          });
          clone.querySelectorAll(".radio").forEach(function (item) {
            item.setAttribute("name", `radio[${currentCount}]`);
          });
          clone
            .querySelector(".textarea")
            .setAttribute("name", `textarea[${currentCount}]`);
    
          // remove button
          clone
            .querySelector(".removeSet")
            .setAttribute("id", `remove-${currentCount}`);
          clone
            .querySelector(".removeSet")
            .setAttribute("data-number", currentCount);
    
          theForm.append(clone);
    
          // add event listener to removeSet button
          document
            .querySelector(`#remove-${currentCount}`)
            .addEventListener("click", (event) => {
              event.preventDefault();
              const setName = `.set-${event.target.getAttribute("data-number")}`;
              document.querySelector(setName).remove();
            });
    
          currentCount++;
        });
    
      theForm.addEventListener("submit", (event) => {
        event.preventDefault();
    
        const theArray = [];
        const theFormData = new FormData(theForm);
    
        // cycle through FormData keys
        for(const [key, value] of theFormData.entries()) {
        
            if (!value) {
              // do validation here
              return;
            }
    
          // get the key number
          const keyNumber = key.match(/\d/g).join('');
          // assign the object to theArray variable
          theArray[keyNumber] = {
            'first_name': theFormData.get(`firstName[${keyNumber}]`),
            'middle_name': theFormData.get(`middleName[${keyNumber}]`),
            'last_name': theFormData.get(`lastName[${keyNumber}]`),
            'checkbox': theFormData.getAll(`checkbox[${keyNumber}]`),
            'radio': theFormData.get(`radio[${keyNumber}]`),
            'textarea': theFormData.get(`textarea[${keyNumber}]`)
          }      
    
        }
        
        // clean theArray before sending to the back-end
        const filteredArray = theArray.filter(Boolean);
        console.log(filteredArray);
    
      });
    })();
    .set {
      margin: 5px;
      padding: 5px;
      outline: 1px solid #ccc;
    }
    .removeSet {
      float: right;
    }
    <form id="theForm">
      <template>
        <div class="set">
          <button class="removeSet">remove</button>
          <input class="first_name" placeholder="first name" /><br>
          <input class="middle_name" placeholder="middle name" /><br>
          <input class="last_name" placeholder="last name" /><br>
          <!-- checkbox -->
          <label><input type="checkbox" class="checkbox" value="checkbox a" />checkbox a</label>
          <label><input type="checkbox" class="checkbox" value="checkbox b" />checkbox b</label>
          <label><input type="checkbox" class="checkbox" value="checkbox c" />checkbox c</label><br>
          <!-- radio -->
          <label><input type="radio" class="radio" value="radio a" />radio a</label>
          <label><input type="radio" class="radio" value="radio b" />radio b</label>
          <label><input type="radio" class="radio" value="radio c" />radio c</label><br>
          <!-- textarea -->
          <textarea class="textarea" rows="4" cols="50">additional notes</textarea>
        </div>
      </template>
      <button id="addMoreFields">add</button>
      <button id="submitForm" style="display: none;">submit</button>
    </form>