0

I've got an HTML form that consists of a series of units like this:

<input name="categoryColor[]" />
<input name="categoryName[]" />

Using this jQuery code, I can capture this data and return it in an object like this:

{categoryColor: [array of values],
categoryName: [array of values]}

Here's an example of the code in action:

const getFormDataFromElem = function($elem, options) {
  options = options || {};
  const vis = options.onlyVisible ? ":visible" : "";
  const formInputs = $elem.find(`:input${vis}, [contenteditable=true]${vis}`);
  const data = {};
  formInputs.each(function() {
    const $this = $(this)
    const type = $this.attr('type');
    const val = type === "checkbox" ? (this.checked ? "1" : "0") :
      ($this.is('[contenteditable=true]') ? $this.text() : this.value);
    const name0 = $this.attr('name');
    const doArray = name0 && name0.slice(-2) === "[]";
    const name = doArray ? name0.slice(0, -2) : name0;
    if (!name || (!options.saveEmpty && !doArray && val === "")) {
      return;
    }
    if (doArray) {
      if (data.hasOwnProperty(name)) {
        data[name].push(val);
        return
      }
      data[name] = [val];
      return;
    }
    data[name] = val;
  });

  return data;
};

const data = getFormDataFromElem($('.input'));
$('.output').text(JSON.stringify(data, null, 2));
.output {
    font-family: monospace;
    white-space: pre;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h2>Input</h2>
<div class="input">
<input name="categoryName[]" value="phase1"/>
<input name="categoryColor[]" value="red"/>
<input name="categoryName[]" value="phase2"/>
<input name="categoryColor[]" value="green"/>
<input name="categoryName[]" value="phase3"/>
<input name="categoryColor[]" value="blue"/>
</div>

<h2>Output</h2>
<div class="output"></div>

BUT I'd like to be able to write the HTML form units like this

<input name="categories[].color" />
<input name="categories[].name" />

since I really need this data in this form:

{categories: [array of objects],
}

where the objects have the form {name: '<name of category>', color: '<color string>'}.

How would I rewrite my general-purpose form-capturing routine to produce values that are arbitrary arrays and objects?

JohnK
  • 6,865
  • 8
  • 49
  • 75
  • `name="categories[_COUNTER_][color]"` – GrumpyCrouton Sep 18 '20 at 18:10
  • @GrumpyCrouton, Thanks! Is `_COUNTER_` a literal or does it stand for an ordinal I have to put in by hand? And I take it `color` is a literal? – JohnK Sep 18 '20 at 18:24
  • `_COUNTER_` needs to be replace with a counter, maybe a `for` loop starting at `0` to however many inputs there are. `color` is a literal string. – GrumpyCrouton Sep 18 '20 at 18:27
  • @GrumpyCrouton, thanks for clarifying. And you're saying the JS code I have here will automatically make the object as I described, or is there some built-in jQuery method I'm supposed to use? I'm having trouble seeing how that's possible with the code I have. – JohnK Sep 18 '20 at 18:43
  • See this solution for form to object using multiple `[][]` in names https://stackoverflow.com/a/39248551/1175966 – charlietfl Sep 18 '20 at 19:10
  • @charlietfl, thanks for your reply. That's an interesting approach! Unfortunately I really need the arrays to be handled like PHP and not require an index to be specified (i.e., I need `name[]` for all entries, not `name[1]`, `name[2]`, etc.). Otherwise, things become complicated given the existing software this codebase is using. – JohnK Oct 01 '20 at 20:28
  • Are you sending this to php, or using the arrays local? Will they be grouped in any way ( like rows for example)? If grouped the indexing of containers will be helpful perhaps – charlietfl Oct 01 '20 at 20:46
  • @charlietfl, PHP is not involved, just using a similar format. The results are being sent to a server API though. I'm not sure what you mean by grouping. I just want out an array of objects (the objects have named fields), as described in the post. – JohnK Oct 02 '20 at 15:36

1 Answers1

0

Following assumes you are able to group each set of inputs that make up one object. Then rather than having to parse names use data attributes on the group container for the main object property name.

Still a bit unclear if this is what you are after but can also modify to suit more specific needs. I realize the names are not unique and not sure if that is an issue or not

const data = {};

$('.input').each(function(i){
   const $cont = $(this), 
          {struct, prop} = $cont.data(),          
          inputs = $cont.find('input').toArray();
         
     if(struct === 'obj'){
      data[prop] = data[prop] || [];     
      const obj = inputs.reduce((a,c)=>({...a, [c.name]:c.value}),{})
      data[prop].push(obj);      
   }
})

console.log(data)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="input" data-struct="obj" data-prop="otherCat">
  <input name="name" value="phase1" />
  <input name="color" value="red" />
</div>

<div class="input" data-struct="obj" data-prop="categories">
  <input name="name" value="phase2" />
  <input name="color" value="green" />
</div>

<div class="input" data-struct="obj" data-prop="categories">
  <input name="name" value="phase3" />
  <input name="color" value="blue" />
</div>
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Thanks for writing this. I think you're onto something. But the data attributes are rather awkward. I was really hoping to use the `name` attribute exclusively. – JohnK Oct 02 '20 at 15:49
  • Ok... just need to write a similar parser for names then – charlietfl Oct 02 '20 at 16:34