1

Hopefully my last question for a little while, although I can't be sure...

I'm trying to produce a form where once data is input it creates a new area for inputting data. This seems like a very trivial problem, and I can think of several possible uses for such a form (inputting job history on an application, inputting a list of phone numbers where you can be reached, etc.)

It's easy enough to create a new row once data is entered, however I can't find a way to tell this new row that when data is entered into it, it should create yet another row.

For simplicity, and to help get the appearance I wanted, I was using a table for my form and each time anything was typed into the last row it would create a new row. Here's the HTML that I'm using:

<table>

    <tr>
        <th>Field 1</th>
        <th>Field 2</th>
        <th>Field 3</th>
    </tr>

    <tr>
        <input type="hidden" name="id-row-0" value="0" />
        <td><input type="text" name="field1-row-0" value="" /></td>
        <td><input type="text" name="field2-row-0" value="" class="date-field" /></td>
        <td><input type="text" name="field3-row-0" value="" /></td>
    </tr>

</table>

Then I have the following javascript:

$.spreadsheet = {
    fields  : $('#myForm').find('input,textarea,select'), // list of input HTML elements
    rows    : $('#myForm').find('tr').length - 1, // number of rows currently. Subtract 1 for table headers
    columns : $('#myForm').find('tr')[0].cells.length // number of fields pulled from the database
}

$($.spreadsheet.fields).find('input,textarea,select').change( function() {

    var index = $.spreadsheet.fields.index( $( this ) );
    var row = Math.floor(index / $.spreadsheet.columns);

    if( row == $.spreadsheet.rows - 1 ) {
        // Last row was changed. Do we add a new row?
        if( $(this).val() != '' ) {

            // A value was added. We ned a new row
            new_row = $("input[name=id-row-"+row+"]").parent().clone().appendTo("#myForm table");

            // Now we need to change the name of every element in this row to reflect the new row number
            new_row.find('input,textarea,select').each( function() {
                partial_name = this.name.substring(0, this.name.indexOf('-row-'+row));
                this.name = partial_name + '-row-' + (row+1);

                // If it was a datepicker before then the id will cause all sorts of issues
                this.id = '';
                $(this).removeClass('hasDatepicker');
            });

            // Create date pickers
            new_row.find('.date-field').datepicker({ dateFormat: "yy-mm-dd", changeMonth: true, changeYear: true });

            // Recursively set this function for change()?
            // Help, guys =(

            // Update the total number of rows
            $.spreadsheet.rows += 1;
        }
    }

});

// Create date pickers

$( ".date-field" ).datepicker({ dateFormat: "yy-mm-dd", changeMonth: true, changeYear: true });

As you can see, I'm not sure how to recursively set this function as the callback for .change(). I tried doing something like the following:

function add_new_row = function() {
    ...
    // Recursively set this function for change()?
    new_row.find('input,textarea,select').change( add_new_row );
    ...
}

$($.spreadsheet.fields).find('input,textarea,select').change( add_new_row );

But it didn't work. Does javascript not support any form of recursion? And if not, how does one solve this dilemma?

After using .delegate() at the suggestion of @J0HN, here is my current javascript file in its entirety:

$(document).ready( function() {

$.spreadsheet = {
    fields  : $('#myForm').find('input,textarea,select'),   // list of input HTML elements
    rows    : $('#myForm').find('tr').length - 1,           // number of rows currently. Subtract 1 for table headers
    columns : $('#myForm').find('tr')[0].cells.length,      // number of fields pulled from the database
}

// Add new rows

$("#myForm").delegate("input, textarea, select", "change", function() {

    var index = $.spreadsheet.fields.index( $( this ) );
    var row = Math.floor(index / $.spreadsheet.columns);
    var col = index % $.spreadsheet.columns;

    if( row == $.spreadsheet.rows - 1 ) {

        // Last row was changed. Do we add a new row?

        if( $(this).val() != '' ) {

            // A value was added. We ned a new row
            // This row should look just like the previous... A CLONE, one might say?

            new_row = $("input[name=id-row-"+row+"]").parent().clone().appendTo("#myForm table");

            // Now we need to change the name of every element in this row to reflect the new row number

            new_row.find('input, textarea, select').each( function() {

                partial_name = this.name.substring(0, this.name.indexOf('-row-'+row));
                this.name = partial_name + '-row-' + (row+1);

                // We can't initialize a datepicker if it thinks one is already initialized

                $(this).removeClass('hasDatepicker');
                this.id = '';

            });

            // Create date pickers

            new_row.find('.date-field').datepicker({ dateFormat: "yy-mm-dd", changeMonth: true, changeYear: true });

            // Update the total number of rows

            $.spreadsheet.rows += 1;

        }

    }

});

// Create date pickers

$( ".date-field" ).datepicker({ dateFormat: "yy-mm-dd", changeMonth: true, changeYear: true });

});

When I type some data into the first row it successfully adds a new row, as expected. Using Chrome's "Inspect Element" feature I can check the row numbers on these new elements and they all seem correct, and the date pickers even work properly.

However when I type data into this new row nothing happens. No rows are added, there are no errors, javascript doesn't crash (datepickers continue to work)... It's as if the "change" method is never called.

Ideas?

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
stevendesu
  • 15,753
  • 22
  • 105
  • 182
  • Please write down this function 'add_new_row' – cvsguimaraes Aug 31 '11 at 11:47
  • Without going through the bunch of code, if I understood correctly, a possible solution would be to use event delegation. Bind an event handler to an ancestor and listen to change events of the input fields. If the field that triggered the change is the last one (or is contained in the last row), append a new field (row). – Felix Kling Aug 31 '11 at 11:51
  • @cvsguimaraes, `add_new_row` is an exact copy of the function that was previously passed to `$($.spreadsheet.fields).find('input,textarea,select').change()`. I though this much was obvious from context, as well as the re-use of the comment above. – stevendesu Aug 31 '11 at 14:26

1 Answers1

4

I think jQuery.delegate should do the trick. It "binds" the event handler to any element that suites the selector, now or in the future.

$("#myform").delegate("input, textarea, select", "change", function(){
     //place row adding logic here
});
J0HN
  • 26,063
  • 5
  • 54
  • 85
  • Also look at the live() function which does the same. – Ali Aug 31 '11 at 11:59
  • As I understand, `live` has worser performanse, do not allow to stop propagation of the events and have some other drabacks. So, use `delegate` instead. – J0HN Aug 31 '11 at 12:03
  • The solution sounds perfect, however I just tried it out and it didn't work for me. Perhaps there's something wrong with my syntax? I have to run to class now, but I'll update my question later today with the current jQuery and see if anyone can figure out why it's not working. – stevendesu Aug 31 '11 at 14:29
  • Don't forget to explain what does `didn't work` mean. Errors, malfunction, etc.? – J0HN Aug 31 '11 at 14:38
  • @J0HN: I have resolved the issue. `$("#myForm").delegate()` was exactly what I needed, however I also needed to recalculate the value of `$.spreadhseet.fields` after adding a new row. I just added `$.spreadsheet.fields = $('#myForm').find('input,textarea,select')` after adding the row. – stevendesu Sep 02 '11 at 16:39