1

I have the following ViewModel which I fill with some predefined data. One of that predefined data is a List of ApplicationParameter ViewModels which is set to initially display n number of ApplicationParameters.

What I wanted to achieve is to allow user to add additional number of ApplicationParameters ViewModels on a button click which I achieved by calling PartialView with the ApplicationParameter code. As expected controls do add automatically but they aren't recognized in the ViewModel as they don't have the correct naming (as they are being nested).

What to do to make dynamically added controls visible to the ViewModel returned on the POST.

ViewModels

public class ApplicationVM
{

    public int idApplication { get; set; }
    public int idCompany { get; set; }
    public string Company { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Link { get; set; }
    public string Photo { get; set; }

    public List<ApplicationParameterVM> ApplicationParameters { get; set; }
}

public class ApplicationParameterVM
{
    public string Name { get; set; }
    public bool Keep { get; set; }
}

Action view (code snippet)

 <tbody id="editorRows">
     @for (int i = 0; i < Model.ApplicationParameters.Count(); i++)
         {
            <tr>
                <td>
                    @Html.TextBoxFor(m => Model.ApplicationParameters[i].Name, new { @class = "form-control" })
                     @Html.CheckBoxFor(m => Model.ApplicationParameters[i].Keep)
                  </td>
              </tr>
           }
   </tbody>

Action view for adding ApplicationParameterVM

   @model AccessMarketmind.Areas.Administration.ViewModels.ApplicationParameterVM
   @{
        Layout = null;
    }

<tr class="application-parameters">
     <td>
         @Html.TextBoxFor(m => Model.Name, new { @class = "form-control" })
         @Html.CheckBoxFor(m => Model.Keep)
     </td>
</tr>

I know this one looks trivial and could be easily done using Javascript, but the thing is that I have more complicated ViewModels (read: heavily nested) in which Javascript isn't an option.

wegelagerer
  • 3,600
  • 11
  • 40
  • 60
  • I am not getting why you are still need "Action view for adding ApplicationParameterVM". – Sravan Jul 01 '15 at 19:16
  • @Sravan Maybe I wasn't clear enough, action is used for creating the ApplicationVM. As ApplicationParameters are part of the ViewModel I need them in my action. In short, I ommited unnecessary code. – wegelagerer Jul 01 '15 at 19:19
  • 1
    You need to use the [BeginCollectionItem](https://github.com/danludwig/BeginCollectionItem) helper, or for a pure client side approac, [refer this answer](http://stackoverflow.com/questions/29837547/set-class-validation-for-dynamic-textbox-in-a-table/29838689#29838689) –  Jul 02 '15 at 00:23
  • @StephenMuecke Thanks for the answer, I'll go with the client side approach as at the moment it is faster way to achieve the wanted result. – wegelagerer Jul 02 '15 at 08:43
  • It will certainly give better performance (no need to use ajax and call the server to return a partial view), but just keep in mind that its a bit harder to maintain - if you change or add a property or even change or add a validation attribute on a property, you will need to update the template as well :) –  Jul 02 '15 at 08:46
  • @StephenMuecke Sure, will keep that in mind - thank you for the info, I'm on the right track now. – wegelagerer Jul 02 '15 at 08:52

2 Answers2

1

The basic hack in these kind of dynamic forms is that the form data which is getting posted should match with the property names.

Ex: the final property names of your dynamic control array should be. ApplicationParameters[0].Name and ApplicationParameters[0].Keep and
ApplicationParameters[1].Name and ApplicationParameters[1].Keep .....

To achieve that please use the loosely coupled helper Html.TextBox() only.

Sravan
  • 1,095
  • 4
  • 16
  • 27
  • I don' think I'll be able to get the data on the post with your code as ApplicationParameters are part of Application view model. Also, a subquestion, what if we have a multilevel nesting - in this case it only goes one level but what if we had Application.ApplicationParameter[0].ApplicationParameterValue.Foo – wegelagerer Jul 01 '15 at 19:32
  • It will work for all levels as such if you have proper property names in post. Try posting data and analyze the form data. – Sravan Jul 01 '15 at 19:35
  • I have created the sample handy. – Sravan Jul 01 '15 at 19:36
  • Thank you, I'll give it a go! – wegelagerer Jul 01 '15 at 19:42
0

After useful answers I was able to get the thing working exactly the way I wanted to. What I did is a combination of server and client side code and the idea is to basically add an additional property into the ViewModel which I named ControlName and is of type string. What is great about this that if your ViewModel changes you can easily adjust the code to the current needs wihout too much hassle, not to mention that you can easily go to multiple levels of depth just by adding ControlName property to the each ViewModel sublevel.

NOTE: The following example isn't the one mentioned in the OP

First you'll need some kind of wrapper (in my case a table) and a link which will allow you to create new rows of controls:

        @Html.ActionLink("Add attribute", "AddProductCategoryAttribute", null, new { id = "addProductCategoryAttribute" })
        <table class="table table-condensed table-striped">
            <thead>
                <tr>
                    <th>

                    </th>
                    <th>
                        Attribute
                    </th>
                    <th>
                        Measure
                    </th>
                </tr>
            </thead>
            <tbody id="productCategoryAttributes"></tbody>
        </table>

On the client side you'll need something like this

    <script type="text/javascript">
    $("#addProductCategoryAttribute").click(function () {
        elementCount = $(".productCategoryAttribute").length;

        $.ajax({
            url: this.href + "?elementCount=" + elementCount,
            cache: false,
            success: function (html) {
                $("#productCategoryAttributes").append(html);
            }
        });
        return false;
    })
</script>

Note the appended elementCount to the link - this querystring is used to let the Action method know how many of elements already exist. When talking about Action method, this is how it looks now.

    public ActionResult AddProductCategoryAttribute(int elementCount)
    {
        ProductCategoryOverview.ProductCategoryAttributeVM productCategoryAttributeVM = new ProductCategoryOverview.ProductCategoryAttributeVM();
        productCategoryAttributeVM.ControlName = "ChangeThisNameToWhateverIsNeeded[" + elementCount.ToString() + "].";
        productCategoryAttributeVM.Keep = true;

        return View(productCategoryAttributeVM);
    }
wegelagerer
  • 3,600
  • 11
  • 40
  • 60