8
votes

I have a model that contains a collection, such as this:

class MyModel
{
    public List<MySubModel> SubModels { get; set; }
}

In the view, I want to dynamically add/remove from this list using Javascript before submitting. Right now I have this:

$("#new-submodel").click(function () {
    var i = $("#submodels").children().size();
    var html = '<div>\
                    <label for="SubModels[' + i + '].SomeProperty">SomeProperty</label>\
                    <input name="SubModels[' + i + '].SomeProperty" type="textbox" />\
                </div>'
    $("#submodels").append(html);
});

This works, but it's ugly. And, if I want to show those labels/textboxes for the existing items, there's no clean way to do that either (without duplicating).

I feel like I should be able to use Razor helpers or something to do this. Any ideas? Help me stay DRY.

2

2 Answers

7
votes

You approach may lead to unexpected errors if you when you are removing or adding the divs. For example you have 4 items, you remove the first item, then $('#submodels').children().size() will return 3, but your last inserted div has the name attribute value set SubModels[3].SomeProperty which results in a conflict. And if your posted values contain SubModels[1] but not SubModels[0] the default model binder will fail to bind the list (it will bind it as null). I had to learn this the hard way...

To eliminate the aforementioned problem (and your's) I suggest you do something like this:

$("#addBtn").click(function() {
  var html = '<div class="submodel">\
                <label>SomeProperty</label>\
                <input type="textbox" />\
              </div>'; // you can convert this to a html helper!
  $("#submodels").append(html);
  refreshNames(); // trigger after html is inserted
});

$(refreshNames); // trigger on document ready, so the submodels generated by the server get inserted!

function refreshNames() {
  $("#submodels").find(".submodel").each(function(i) {
     $(this).find("label").attr('for', 'SubModels[' + i + '].SomeProperty');
     $(this).find("label").attr('input', 'SubModels[' + i + '].SomeProperty');
  });
}

Then your view (or even better an EditorTemplate for the SubModel type) can also generate code like:

 <div class="submodel">
    @Html.LabelFor(x => x.SomeProperty);
    @Html.EditorFor(x => x.SomeProperty);
 </div>

It would also be possible to convert the code generation to a html helper class, and use it in the EditorTemplate and in the JavaScript code

4
votes

I would recommend you going through the following blog post.