javascript – What's the cleanest way to implement a dynamic HTML form?-ThrowExceptions

Exception or error:

I want to make a HTML form where the user can edit a set of elements at once. The user has to be able to remove an element, edit element and add new elements. I’m using PHP (Laravel) as a backend and jQuery for the dynamic form.

My initial idea was do basically this:

<form id="bars">
    @foreach($foo as $bar)
        <input type="text" name="name[]" value="{{$bar->name}}" required>
        <input type="color" name="color[]" value="{{$bar->color}}" required>
        <input type="checkbox" name="completed[]"{{$bar->completed ? ' checked' : ''}}>
        <span class="deleteRow"></span>
    @endforeach
</form>

<div id="template" style="display:none;">
        <input type="text" name="name[]" required>
        <input type="color" name="color[]" required>
        <input type="checkbox" name="completed[]"{{$bar->completed ? ' checked' : ''}}>
        <span class="deleteRow"></span>
</div>

<script>
    $(document).ready(function() {
    // Remove rows
        $('form').on('click', '.deleteRow', function() {
            $(this).closest('tr').remove();
        });

        // Clone new rows from template
        $('#addRow').click(function() {
            $('#template tr')
                .clone()
                .appendTo('#bars');
        });
    });
</script>

This is the Blade template from which I’ve removed all irrelevant code and styling. This approach uses the following concepts:

  • [] in the input names so that the POSTed data will be placed into an array. In processing I end up with three arrays name, color, and completed which will contain the POSTed data.
  • Existing elements are rendered server-side in the form by the Blade template
  • New rows are cloned from a ‘form template’ and added to the form when the user clicks the “new row” button.

This is quite simple, and works in most cases (I’ve used this before), but it does not work in this case because of a small thing: the checkboxes. Unchecked checkboxes will not be POSTed which means that in the completed[] array will be smaller in size than the other arrays and I have no way of checking which elements have the checkbox checked.

Now I could modify my JS in such a way that it keeps track of the indexes and explicitly inserts the index in every input name (so name[0], name[1], etc) but that approach is complicated by the fact that the form must be pre-filled with data and does not start out empty.

I can, instead of filling the data through the Blade template, let the JS handle that too (through a JSON API) but that also gets complicated fast because the JS now has to ‘parse’ the form template and fill in all the values.

What’s the best practise to accomplish this in a clean way?

How to solve:

What I ended up with is the following:

<form id="bars">
    @foreach($foo as $i => $bar)
        <input type="hidden" name="id[{{$i}}]" value="{{$status->id}}">
        <input type="text" class="form-control" name="name[{{$i}}]" value="{{$status->name}}" required>
        <input type="color" name="color[{{$i}}]" value="{{$bar->color}}" required>
        <input type="checkbox" name="completed[{{$i}}]"{{$bar->completed ? ' checked' : ''}}>
        <span class="deleteRow"></span>
    @endforeach
</form>

<div id="template" style="display:none;">
        <input type="text" name="name[$i]" required>
        <input type="color" name="color[$i]" required>
        <input type="checkbox" name="completed[$i]"{{$bar->completed ? ' checked' : ''}}>
        <span class="deleteRow"></span>
</div>

<script>
    $(document).ready(function() {
    // Remove rows
        $('form').on('click', '.deleteRow', function() {
            $(this).closest('tr').remove();
        });

        // Clone new rows from template
        $('#addRow').click(function() {
            $('#template tr')
                .clone()
                .html(function(i, oldHTML) {
                    return oldHTML.replace(/\$i/g, $('#bars tr').length);
                })
                .appendTo('#bars');
        });
    });
</script>

This is mostly what @MagnusEriksson advised me to do (so thanks!) but solved in a slightly cleaner way by not having to keep a counter but simply using the number of rows inside the form as the index for the cloned rows.

Leave a Reply

Your email address will not be published. Required fields are marked *