Too Complex for a Form

Too Complex for a Form
Photo by Ernest Ojeh / Unsplash

I often use - and, sometime, abuse - dynamic tables as a design pattern: an HTML table with a button to add new rows, each row contains multiple inputs to define details of an entity, the whole set is submitted once on the server for persistency. I adopt this so often that I've implemented it on my own jQuery utilities library, and I include this kind of element in almost all applications I implement.

It is easy as long as each input includes a single value, and their name attribute can be wrote in array-like syntax to enforce submit of all the values to be then iterated server-side: all inputs at index 0 refers to the first row, all inputs at index 1 refers to the second row, and so on. The difficult part is when multiple values are involved in a single input, such as in checkboxes or tags, as you loose the same-index map or related values and you are no longer able to rebuild the correct structure.

So, I've been a bit radical on my latest implementation. On form's submit, using dynamicFunctions feature from the same jBob library above mentioned, I just serialize the table in a JSON array which is then serialized and appended to the form. In this way, the hierarchy can be of any complexity: each row in the table is converted in a self-contained JSON object, isolated from the others, which can be easily handled on the server.

In JS:

var j = new jBob();
j.init({
  dynamicFunctions: {
    jsonableTable: (form) => {
      let complete = [];

      form.find('tbody').find('tr').each((index, node) => {
        let row = $(node).find('input, select, textarea').serializeArray();
        if (row.length == 0) {
          return;
        }

        let obj = {};

        row.forEach((r) => {
          let name = r.name.replace(/\[.*\]/g, '');

          if (obj.hasOwnProperty(name)) {
            if (Array.isArray(obj[name])) {
              obj[name].push(r.value);
            }
            else {
              let arr = [];
              arr.push(obj[name]);
              arr.push(r.value);
              obj[name] = arr;
            }
          }
          else {
            obj[name] = r.value;
          }
        });

        complete.push(obj);
      });

      let json = JSON.stringify(complete);

      let appended = form.find('input[name=jsonable]');
      if (appended.length == 0) {
        appended = $('<input type="hidden" name="jsonable">');
        form.append(appended);
      }

      appended.val(json);
    },
  }
});

In HTML:

<form class="dynamic-form">
  <input type="hidden" name="jb-pre-saved-function" value="jsonableTable">
  
  <table class="dynamic-table">
    <tbody>
      <tr>
        <td><!-- Your inputs with multiple values --></td>
      </tr>
    </tbody>
    <tfoot hidden>
      <tr>
        <td><!-- --></td>
      </tr>
    </tfoot>
  </table>
</form>

Naive, but practical.