Reputation: 454
I have an ASP.NET MVC page that uses a lot of AJAX. At the most basic level, it contains a list of questions, with each question having a list of answers inside it. My view models (vastly simplified for the sake of this question):
View Models:
public class QuestionViewModel
{
....
public string QuestionText { get; set; }
public List<AnswerViewModel> AnswerList { get; set; }
public bool EditMode { get; set; }
....
}
public class AnswerViewModel
{
....
public string Answer { get; set; }
....
}
_View.cshtml:
@model QuestionViewModel
@if(Model.EditMode)
{
@Html.EditorFor(x => x)
}
else
{
...
}
QuestionViewModel.cshtml (Editor Template):
@model Project.Models.QuestionViewModel
....
@Html.EditorFor(x => x.AnswerList)
....
AnswerViewModel.cshtml (Editor Template):
@model KSUAdvising.Models.AnswerViewModel
....
@Html.TextBoxFor(x => x.Answer)
....
^^^This EditorFor call renders my answer list fine (along with the appropriate indices so the model binder will bind back to the list appropriately. i.e.:
<input id="AnswerList_0__Answer" name="AnswerList[0].Answer" type="text" value="Yes">
<input id="AnswerList_1__Answer" name="AnswerList[1].Answer" type="text" value="No">
However, on the client side I need to add items to this list when the user clicks a button. This needs to happen client side so no changes are made to the database until the user saves the question.
I see a few direction to go to solve this problem:
This seems like it would be a common scenario, but I'm having trouble coming up with a good solution to this. What is the recommended approach for this case?
Upvotes: 1
Views: 2247
Reputation: 1861
I vote for option 2.
Render a hidden HTML Item Template. Then on a client button click, clone the template and modify the index.
In the cshtml page, after the foreach, add a new hidden div for the template. Give it an index place holder (I use _ x0x_). Create a new empty item and then render it the same way as you did in the foreach. (You could also have the item render as a partial view and then call it inside the foreach and outside.)
Here is sample cshtml page:
@foreach (var role in roles)
{
int roleIndex = roles.IndexOf(role);
string rolePrefix = "CasePartyRoles[" + roleIndex + "].";
<div id="CasePartyRoleIndex_@roleIndex" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
@Html.Hidden(rolePrefix + "SequenceNo", role.SequenceNo)
@Html.Hidden(rolePrefix + "RowVersion", role.RowVersion)
@Html.DropDownListFormGroupFor(modelItem => role.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, role.PartyRoleCode, rolePrefix + "PartyRoleCode")
@Html.DropDownListFormGroupFor(modelItem => role.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, role.PartyStatusCode, rolePrefix + "PartyStatusCode")
@Html.EditorFormGroupFor(modelItem => role.SubFileNo, "col-md-3", "col-md-9", null, null, rolePrefix + "SubFileNo")
@Html.EditorFormGroupFor(modelItem => role.PartyRank, "col-md-3", "col-md-9", null, null, rolePrefix + "PartyRank")
</div>
</div>
}
<div id="CasePartyRoleTemplate" class="hidden">
@*Template for new Role*@
@{
var newRole = new CasePartyRole();
string newRolePrefix = "CasePartyRoles[_x0x_].";
}
<div id="CasePartyRoleIndex__x0x_" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
@Html.Hidden(newRolePrefix + "SequenceNo", newRole.SequenceNo)
@Html.Hidden(newRolePrefix + "RowVersion", newRole.RowVersion)
@Html.DropDownListFormGroupFor(modelItem => newRole.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyRoleCode, newRolePrefix + "PartyRoleCode")
@Html.DropDownListFormGroupFor(modelItem => newRole.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyStatusCode, newRolePrefix + "PartyStatusCode")
@Html.EditorFormGroupFor(modelItem => newRole.SubFileNo, "col-md-3", "col-md-9", null, null, newRolePrefix + "SubFileNo")
@Html.EditorFormGroupFor(modelItem => newRole.PartyRank, "col-md-3", "col-md-9", null, null, newRolePrefix + "PartyRank")
</div>
</div>
</div>
Here is a little jQuery function to add the item based on the template:
function createItemFromTemplate(templateId, indexNo, insertBeforeId) {
// Copy the template Element, replaces all the _x0x_ with the index no and add the new element
$(insertBeforeId).before($.parseHTML($(templateId).clone().prop('outerHTML').replace(/_x0x_/g, indexNo)));
}
Upvotes: 1
Reputation: 2689
It depends... For the sake of performance I wouldn't go with the first option. This slight delay to load up a new item via Ajax could be annoying.
Personally most times I would just build an HTML element completely on client side. I know it complicates maintanance as you have to remember to change your client side logic any time you make changes to your model collection but it's fast and straightforward.
Also have an idea how to improve your second option. Basicly you don't need to have a real object to build it.
@Html.TextBoxFor(model => model.Items[int.MaxValue], new { style= "display:none" })
the input rendered
<input name="Items[2147483647]" id="Items_2147483647_" style="display: none;" type="text" value="">
Please note the Items property is null in my case but it doesn't matter if you just want to build a template as xxxFor html helpers just use expressions to build html. Also it's very easy then to replace int.MaxValue while cloning the template with a real index. It's safe to use int.MaxValue as a placeholder, it's very unlickely you will have that much items on your page.
Hope it helps!
Upvotes: 0