0

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

Neo Genesis
  • 922
  • 11
  • 26
  • Have a look at this answer https://stackoverflow.com/a/61167168/5417823 – mylee Nov 10 '20 at 02:53
  • Does this answer your question? [Pure JavaScript Send POST Data Without a Form](https://stackoverflow.com/questions/6396101/pure-javascript-send-post-data-without-a-form) – mylee Nov 10 '20 at 02:54
  • As long as you don't need to support old IE you can use [FormData.prototype.entries](https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries). – Jared Smith Nov 10 '20 at 03:01
  • Thanks for the comment guys. I know how to send data to the BE. My issue is to format the form the way BE requires it Currently. I can get the following `first_name, middle_name, last_name, radio, and textarea` on "FormData", but to format the checkbox to array from itis my last issue – Neo Genesis Nov 10 '20 at 05:18

1 Answers1

0

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>
Neo Genesis
  • 922
  • 11
  • 26