Wednesday, June 3, 2015

Dynamically adding Elements to forms and then submitting

Recently I decided to write a web application using PHP, for a change, rather than Perl, and as part of my application wanted to dynamically add some fields to a form, by allowing the user to decide how many fields they wanted to add.

The fields needed to be added to a table layout which would include the detail for the user in one column and the input fields in the right, example code;
<script>
function addExtraBoxes(numBoxes) {

        var container = document.getElementById('extragrid');
        // Clear container
        while ( container.hasChildNodes() ) {
                container.removeChild(container.lastChild);
        }
        
        // Add the question boxes
        for ( x = 1; x <= numBoxes; x++ ) {
                var newdiv = document.createElement('tr');
                newdiv.innerHTML = '<td>Extra Box ' + x + ': </td><td> <input type="text" name="extra' + x + '" size="60"></td></tr>'
                container.appendChild(newdiv);
        }
}
</script>

<table>
<form name='dynamic' id='dynamic' action='somepage.php' method='post'>
<tr><td>Number extra fields:</td><td><select name='extras' onChange='addExtraBoxes(this.options[this.selectedIndex].value)'>
<option value='Choose' selected>---Choose one---</option>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
<option value='4'>4</option>
<option value='5'>5</option>
</select></td></tr>
<tr><td colspan='2'>
<table id='extragrid'></table>
</td></tr>
</form>
</table>

The above code will conveniently add the new input text boxes and field info to the table based on the users selection from the select box.

A lot of web sites will show you only this piece of code, and then proceed to tell you that if the form elements are showing within the <form> elements in your developer view (e.g. firebug or Chrome's developer tools) that the fields should be submitted with the rest of your form.  This sadly is not true, since what they are doing is adding the new form elements to either a DIV, or in my case above a table.  The table or DIV itself is not associated with the form (you have to know about the DOM to understand this).

So, let me explain:

I have a <form> called dynamic.  That form at the time of the web page rendering only has a <select> element and nothing else.
If we then select one of the numbers from the select element it fires the javascript function addExtraBoxes() which will add the chosen number of text field elements to the extragrid table.  The tables are not in the same location as the form in the DOM, so we get the following;

document.dynamic.extras     This is the form and the select box

document.extragrid               This is the table element
document.extragrid.extra1
document.extragrid.extra2    These extran elements are not associated with the form, but with the table since that is how javascript added them.

So you can see that the DOM does not associate the new form elements to the form, so although you will see them showing nicely in the developer tools, when you come to submit the form the elements are not submitted and are missing from the form data.

Therefore those people saying that if they are showing up in the developer tools inside the <form> and </form> elements when viewing the source, who say that the fields should be contained in the form data, couldn't be more wrong and don't understand the JavaScript DOM.

How do you over come this, I hear you ask.  Well if you have an understanding of the engineering of things then from above you'll realise that you'll need some more Javascript that will add these disjoint elements to the form before submitting it.  Below is the piece of code that will do that;

function addExtras() {
    var str="";
    try {
        var elems = document.getElementsByTagName('input');
        for ( i = 0; i < elems.length; i++ ) {
            if ( elems.item(i).type == 'text' ) {
                document.getElementById('dynamic').appendChild(elems.item(i));
            }
        }
    } catch(e) {
        alert(e.message);
    }
    document.getElementById('dynamic').submit();
    return false
}

Our <form> element needs to have the onSubmit event added to call the function as follows;
<form name='dynamic' id='dynamic' action='somepage.php' method='post' onSubmit='return addExtras()'>

By using the onSubmit event we are able to use the JavaScript to add the form elements to the correct location in the DOM, and therefore when the submit occurs the data is correctly posted with the rest of the form.

Here is the final code in the correct order;
<script>
function addExtraBoxes(numBoxes) {

        var container = document.getElementById('extragrid');
        // Clear container
        while ( container.hasChildNodes() ) {
                container.removeChild(container.lastChild);
        }
        
        // Add the question boxes
        for ( x = 1; x <= numBoxes; x++ ) {
                var newdiv = document.createElement('tr');
                newdiv.innerHTML = '<td>Extra Box ' + x + ': </td><td> <input type="text" name="extra' + x + '" size="60"></td></tr>'
                container.appendChild(newdiv);
        }
}

function addExtras() {
    var str="";
    try {
        var elems = document.getElementsByTagName('input');
        for ( i = 0; i < elems.length; i++ ) {
            if ( elems.item(i).type == 'text' ) {
                document.getElementById('dynamic').appendChild(elems.item(i));
            }
        }
    } catch(e) {
        alert(e.message);
    }
    document.getElementById('dynamic').submit();
    return false
}
</script>

<table>
<form name='dynamic' id='dynamic' action='somepage.php' method='post' onSubmit='return addExtras()'>
<tr><td>Number extra fields:</td><td><select name='extras' onChange='addExtraBoxes(this.options[this.selectedIndex].value)'>
<option value='Choose' selected>---Choose one---</option>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
<option value='4'>4</option>
<option value='5'>5</option>
</select></td></tr>
<tr><td colspan='2'>
<table id='extragrid'></table>
</td></tr>
</form>
</table>