4

I have a table, and I've dynamically added / removed rows from it. The table contains fields that will be posted back as a collection to MVC. In this scenario updating the table with jquery means I up with collections that contain incomplete collections for example ...

function add() {
     $.ajax({
            url: "GetRow",
            type: 'post',
            dataType: 'html',
            timeout: 30000,
            data: {
                RowIndex: otherRows.length,
                "Item.Property1": $("option:selected", dropDown).text(),
                "Item.Property2": $("option:selected", dropDown).val()
            },
            success: function (result) {
                var newRow = $(result);
                tableBody.append(newRow);
            }
        });
}

This function makes an ajax call to go get an mvc action result that returns the result of a row at the given index and additionally sets some default values from a drop down on my page.

Now lets assume I have a similar function called "delete" in which using jquery i remove a row and at this point the table has 3 rows (ignoring the header row of course), and the row i'm removing is the middle row.

this means the fields in my rows end up looking something like this ...

//Row 1:
<input type="text" name="SomeCollection[0].Property1" /> 

//Row 2:
   Nothing, deleted of course

//Row 3: 
<input type="text" name="SomeCollection[2].Property1" /> 

So if i now do a postback, because of the inconsistent id range, the MVC model binder will only bind item 1 for me and item 2 (actually item 3 prior to the client side delete) is not mapped.

The idea is that i want my server logic to do a very simple "if the id for an item in my collection is not in the post data, delete it from the database", this way all the collection manipulation can be entirely client side in save on constant postbacks on this very heavy page.

So I started putting a function together in jquery to fix the problem ...

function updateNames(table) {
    var rows = $("tr", table);
    var index = 0;

    rows.each(function () {
        var inputFields = $("input");

        inputFields.each(function (){
          //replace name="bla[<any number>].Anything" with 
          //        name="bla[" + index + "].Anything"        
        });

        index++;
    });
}

So my question is ... How do i say to jquery "replace [] in the name attribute with [index]"?

I know this wont solve the problem of nested collections / other such complex scenarios but for that once i have this function solved I can always extend it later and my requirements are not that involved yet.

EDIT:

Some additional detail on my current thought pattern ... good or bad?

if i grab the name attribute value and "walk the chars" until i find the first "[" and the next "]" then replace everything in between, it should solve my problem, but this type of thing on IE probably slow as hell.

Anyone got a neat "just call this clever function" type answer? (if there is one).

EDIT 2:

Wow I really gotta look harder ... what a dumbass i feel like right now for 2 reasons (not looking and regex is an obvious solution here)

JQuery: how to replace all between certain characters?

if i can figure out the right regex i've got my solution (maybe that's the question I should have asked as i'm constantly annoyed by the crazyness of regex).

But the power of a regex cannot be under estimated :)

Community
  • 1
  • 1
War
  • 8,539
  • 4
  • 46
  • 98

2 Answers2

4

This should work for you:

function updateNames(table) {

    var rows = $("tr", table);
    var index = 0;

    rows.each(function () {
        var inputFields = $(this).find("input");
        inputFields.each(function (){
            var currentName = $(this).attr("name");
            $(this).attr("name", currentName.replace(/\[(.*?)\]/, '['+index+']')); 

        });
        index++;
     });
}

If there are multiple inputs inside each row and you just want to update one then consider using the 'starts with' jQuery selector: http://api.jquery.com/attribute-starts-with-selector/.

Joe
  • 15,205
  • 8
  • 49
  • 56
  • that's about where I got to, I can't seem to get it to work though for some reason. – War May 04 '13 at 14:20
  • What exactly is "not working" about it? This is the solution I came to, also. – Ant P May 04 '13 at 14:25
  • lol well the first mistake we both made ... was the second selector is looking document wide. to setup var inputFields – War May 04 '13 at 14:30
  • Just updated it to use the `.find()` selector on the current row. – Joe May 04 '13 at 14:31
  • ive also noticed that for some reason jquery seems to ignore tbody / thead elements – War May 04 '13 at 14:34
  • what i was hoping to do is select only rows in the tbody (since i know i format all my tables this way), as it seems to be pulling the header row too (odd) – War May 04 '13 at 14:35
  • Would you be able to post the basic html of your table? That way I'll be able to make sure you're selecting the right elements. – Joe May 04 '13 at 14:37
  • what was the difference in your regex expression ? – War May 04 '13 at 14:41
  • Yours: /\[(.*?)\]/ Mine: \[\d+\] – War May 04 '13 at 14:42
  • Yours is more concise and searches for numbers, mine just looks for any characters between `[` and `]`. – Joe May 04 '13 at 14:43
  • ah that makes sense ... since i'm looking for array indexes because i know that's what mvc will render I guess mine is more specific to the situation, still for others this might help to have both :) thanks Joe – War May 04 '13 at 14:45
4

Though you've already got a resolution, I thought a working demo of how one might implement this functionality from the ground up might be useful.

Supposing you have a button with a class of delete on each row, you can achieve this with the following:

$('.delete').click(function(e) {
    // Get table body and rows
    var body = $(this).closest('tbody');

    // Remove current row
    $(this).closest('tr').remove();

    // Get new set of rows from table body
    var rows = body.find('tr')

    // Update all indeces in rows
    var re = new RegExp(/\[[0-9]+\]/);
    var index = 0;
    rows.each(function () {
        $(this).find('input').each(function (e) {
            var input = $(this).attr('name');
            if (input) {
                $(this).attr('name', input.replace(re, '['+index+']'));
            }
        });
        index ++;
    });
});

This should delete the row and update all indeces for every input in the remaining rows so that they increment properly again (and thus will be bound properly by the model binder). Additionally, it should be restricted to the current table. Here it is in action.

Ant P
  • 24,820
  • 5
  • 68
  • 105