Chace Fields
Chace Fields

Reputation: 907

How to dynamically add items to my collection from a view when single partial requires multiple objects?

I have a list of items displayed in my view. I need to be able to manually add items to this list from the UI with an add button. I know I need to load a partial view with javascript to get the new item to be displayed.

@{var count = 0;}
@foreach (var dx in Model.Patient.DxList)
{    
    <div class="form-field">
        <label>@(++count)</label>
        @Html.HiddenFor(d => dx.DxIndex)
        @Html.TextBoxFor(d => dx.Dx, new { @class = "small" })
        <span class="description">@((dx.DxRef != null) ? dx.DxRef.Title : "")</span>
        @Html.DropDownListFor(d=> dx.Indicator, new SelectList(Model.Codes, "Value", "Description", dx.Indicator), "Please select", new { @class = "" })
    </div>
}

However, because my item contains both a TextBox and a DropDownList, which is populated with Codes, does my partial view need a ViewModel too? To contain not only the Dx, but the list of appropriate Codes to create the list?

public class DxSingleViewModel
{
    public Dx Dx { get; set; }
    public List<Code> Codes { get; set; }
}

No idea if this is the general approach, or if I'm off base.

Upvotes: 5

Views: 1801

Answers (2)

Jan
Jan

Reputation: 16042

You should separate the rendering of one item to a separate partial view / editor template and call it inside your foreach loop, so you have jsut one place where you define the markup for one item:

@foreach (var dx in Model.Patient.DxList)
{
    @Html.EditorFor(m => dx)
}

Then your AddNewItem action can return a partial view which just calls @Html.EditorForModel()

When adding and removing rows dynamically, you should embed your own index (for example a guid string) to the items to avoid collisions. Take a look at this great article which explains how to implement this with a custom html helper: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Your EditorTemplate will look like this:

@using (Html.BeginCollectionItem("DxList"))
{
    @Html.TextBoxFor(d => dx.Dx, new { @class = "small" })
    ....
}

Regarding your question about whether the viewmodel should contain the list for your dropdown or not: The viewmodel should contain every data you need to render the view. So i would say yes, its a good place to pass the list to the view.

Of cause you could pass it in the viewbag too, but i'm a friend of strong typed data bags, so i would prefer the viewmodel.

Upvotes: 2

mipe34
mipe34

Reputation: 5666

You can try this approach. It has one downside that you have to remember name (path) of the property to render the right input name for automatic binding (so you cannot easily change it with refactoring tool).

View

@foreach (int i=0;i < Model.Patient.DxList.Count;i++)
{
    @{Html.RenderPartial("PartialDx", Model.Patient.DxList[i] ,new ViewDataDictionary() { {"count",i}});}
}

PartialView - strongly typed partial view with type of DxSingleViewModel

@{string collectionName = string.Format("Patient.DxList[{0}].", ViewBag.count);}

<div class="form-field">
   <label>@{ViewBag.count + 1}</label>
   @Html.Hidden(collectionName+"Dx.DxIndex", Model.Dx.DxIndex)

</div>

Upvotes: 0

Related Questions