Search code examples
javascriptarraysobjectreduceform-control

How does one create, collect and programmatically (one entry per form-element) aggregate objects from related form-elements?


I want to group by data objects from the children of a parent <div>

This is my code:

<form id="myForm">
  <div id="parent-div">
    <div>
      <input type="text" style="margin: 0;" name="t-name-1" value="value1" />
      <span>
        <input type="text" name="t-quantity-1" value="1" />
        <input type="text" name="t-frequency-1" value="6" />
      </span>
    </div>
    <div>
      <input type="text" style="margin: 0;" name="t-name-2" value="value2" />
      <span>
        <input type="text" name="t-quantity-2" value="2" />
        <input type="text" name="t-frequency-2" value="8" />
      </span>
    </div>
  </div>
  <button type="submit">
    Submit
  </button>
</form>
var group = [];

$('#myForm').on('submit', function(e){
e.preventDefault();
    var elements = document.getElementById("myForm").elements;
  
  for(var i = 0 ; i < elements.length - 1 ; i++){
        var item = elements.item(i);
        //console.log(i, item.name, '-', item.value);
        
        if(item.name.includes(item.name.split("-")[2])){
            var obj = {};
          var name = item.name.split("-")[1];
          obj[ name ] = item.value
                    group.push(obj);
        }
    }
    
    console.log(group);
});

The output I'm getting is:

[{
  name: "value1"
}, {
  quantity: "1"
}, {
  frequency: "6"
}, {
  name: "value2"
}, {
  quantity: "2"
}, {
  frequency: "8"
}]

The desired output:

var group = [
  {
    "name": "value1",
    "quantity": "1",
    "frequency": "6"
  },
  {
    "name": "value2",
    "quantity": "2",
    "frequency": "8"
  }
]

This is a working jsFiddle: https://jsfiddle.net/7yxzpsd5/45/


Solution

  • The most flexible approach does not make any assumptions about a form's inner DOM hierarchy. It instead would focus on the naming schema which all form-element's name attributes have in common, and which is ... "t-<key>-<id>".

    Thus an entirely DOM-structure agnostic but partially re-usable approach would be bipartite / two-folded.

    • The first part does introduce the regex upon which any form-element will be identified. This regex is as follows ... /^t-(?<key>\w+)-(?<id>\d+)$/. It features named groups for capturing a name value's key and id part. The actual task ...

      • transforms the form elements collection into an array

      • filters all valid (according to the regex) form elements.

      • reduces the filtered array into a list of dataItems

      • The reducer function gets provided the regex as part of a config object which is passed as initial value to reduce as well.

    • The second task is implemented as generic and re-usable function which creates and stores dataItem objects upon a form element's extracted id as well as this element's extracted key and its value.

    function collectDataItemsByElementKeyAndId(collector, elmNode) {
      const { regX, list, map } = collector;
      const { key, id } = regX.exec(elmNode.name)?.groups ?? {};
    
      let dataItem = map[id];
      if (!dataItem) {
    
        dataItem = map[id] = {};
        list.push(dataItem);
      }
      Object.assign(dataItem, { [key]: elmNode.value });
      
      return collector;
    }
    
    function handleSubmit(evt) {
      evt.preventDefault();
    
      const regXKeyAndId = /^t-(?<key>\w+)-(?<id>\d+)$/;
    
      const dataItemList = Array
        .from(this.elements) // `this` refers to the form element.
    
        .filter(elm => regXKeyAndId.test(elm.name))
        .reduce(collectDataItemsByElementKeyAndId, {
    
          regX: regXKeyAndId,
          map: {},
          list: [],
    
        }).list;
    
      console.log({ dataItemList });
    }
    
    $('#myForm').on('submit', handleSubmit);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <form id="myForm">
      <div id="parent-div">
        <div>
          <input type="text" style="margin: 0;" name="t-name-1" value="value1" />
          <span>
            <input type="text" name="t-quantity-1" value="1" />
            <input type="text" name="t-frequency-1" value="6" />
          </span>
        </div>
        <div>
          <input type="text" style="margin: 0;" name="t-name-2" value="value2" />
          <span>
            <input type="text" name="t-quantity-2" value="2" />
            <input type="text" name="t-frequency-2" value="8" />
          </span>
        </div>
      </div>
      <button type="submit">
        Submit
      </button>
    </form>