A little trick for grouping fields in an HTML form

Posted on December 15, 2016 | By Matt Stauffer

Imagine this scenario: You have a group of fields that represent a person. Maybe the form you're building allows you to create a company, but you also want to be able to add employees of the company.

You don't know how many employees your users will add, so you add a little JavaScript button that makes it possible to add more.

How do you name your fields?

How we used to do it

You probably know this is the wrong way:

Person 1:
<label>First name</label>
<input name="first_name1">

<label>Last name</label>
<input name="last_name1">

<label>Email</label>
<input name="email1">

To pull those out in the backend will require string manipulation—and imagine parsing out the number from that string field name when some numbers have one digit, but then all of a sudden you have 10 employees and now some of the fields have two digits at the end instead. Fail. Don't do it.

Here's the more common suggestion: use the field name array syntax:

Person 1:
<label>First name</label>
<input name="first_name[]">

<label>Last name</label>
<input name="last_name[]">

<label>Email</label>
<input name="email[]">

Person 2:
<label>First name</label>
<input name="first_name[]">

<label>Last name</label>
<input name="last_name[]">

<label>Email</label>
<input name="email[]">

This seems like a great idea, and it is—but when you parse the input on the other end, you're probably expecting something like this:

person1 = ['Jim', 'Barber', 'jim@barber.com'];
person2 = ['Amira', 'Sayegh', 'amira@sayegh.com'];

Sadly, that's not what you get. Instead, you get this:

first_name = ['Jim', 'Amira'];
last_name = ['Barber', 'Sayegh'];
email_name = ['jim@barber.com', 'amira@sayegh.com'];

Parsing that together is not awful, but it can get really clumsy—especially as you add more fields.

What's this all about then

Fear not! There is a better solution!

If you set your fields to be grouped by "children" of a parent field, and give each "child" a numeric index, they'll all returned grouped, and then they'll return the way you're expecting. So people is our "parent field", people[1] is our first "child", and people[1][first_name] is that child's first property.

Person 1:
<label>First name</label>
<input name="people[1][first_name]">

<label>Last name</label>
<input name="people[1][last_name]">

<label>Email</label>
<input name="people[1][email]">

Person 2:
<label>First name</label>
<input name="people[2][first_name]">

<label>Last name</label>
<input name="people[2][last_name]">

<label>Email</label>
<input name="people[2][email]">

And take a look at what we get now:

people = [
    [
        'first_name' => 'Jim',
        'last_name' => 'Barber',
        'email' => 'jim@barber.com
    ],
    [
        'first_name' => 'Amira',
        'last_name' => 'Sayegh',
        'email' => 'amira@sayegh.com
    ]
]

Boom, baby.

Bonus

Here's a quick bit of ES6 JavaScript to show one way you might want to do this:

<form method="post">
    <div id="people-container">
        <h3>Person 1:</h3>
        <p>
            <label>First name</label><br>
            <input name="people[1][first_name]">
        </p>

        <p>
            <label>Last name</label><br>
            <input name="people[1][last_name]">
        </p>

        <p>
            <label>Email</label><br>
            <input name="people[1][email]">
        </p>

        <h3>Person 2:</h3>
        <p>
            <label>First name</label><br>
            <input name="people[2][first_name]">
        </p>

        <p>
            <label>Last name</label><br>
            <input name="people[2][last_name]">
        </p>

        <p>
            <label>Email</label><br>
            <input name="people[2][email]">
        </p>
    </div>

    <a href="javascript:;" id="add-new-person">Add new person</a>

    <p>
        <input type="submit">
    </p>
</form>

<script>
let i = 3;
document.getElementById('add-new-person').onclick = function () {
    let template = `
        <h3>Person ${i}:</h3>
        <p>
            <label>First name</label><br>
            <input name="people[${i}][first_name]">
        </p>

        <p>
            <label>Last name</label><br>
            <input name="people[${i}][last_name]">
        </p>

        <p>
            <label>Email</label><br>
            <input name="people[${i}][email]">
        </p>`;

    let container = document.getElementById('people-container');
    let div = document.createElement('div');
    div.innerHTML = template;
    container.appendChild(div);

    i++;
}
</script>

On CodePen:

See the Pen HTML form submission with multiple sub items by Matt Stauffer (@mattstauffer) on CodePen.

Bonus

I remembered that I wanted to write this article as I was listening to a great Full-Stack Radio episode with Jonathan Reinink where they talk about forms for an hour. It's good stuff. Take a listen.

Also, Adam has written a little about this same problem on his blog—but he chose to solve it on the server side instead. Take a look: Cleaning up form input with transpose


Comments? I'm @stauffermatt on Twitter


Tags: javascript  •  html  •  forms