I\'m using Steve Sanderson\'s BeginCollectionItem approach to add dynamic content. Everything works fine when I\'m doing it on the first level. However, when try to implemen
I couldn't properly adapt Job Stevens' method with MVC 5. I just use Job Stevens' below extension class with name BeginCollectionItem2
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem2(this HtmlHelper html, string collectionName)
{
if (html.ViewData["ContainerPrefix"] != null)
{
collectionName = string.Concat(html.ViewData["ContainerPrefix"], ".", collectionName);
}
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
var htmlFieldPrefix = string.Format("{0}[{1}]", collectionName, itemIndex);
html.ViewData["ContainerPrefix"] = htmlFieldPrefix;
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, htmlFieldPrefix);
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
scripts like below:
function addRow() {
$.ajax({
type: "POST",
data: {processTypeId:@Model.Id},
url: '@Url.Action("GetFlowItemRow", "Flow")',
success: function (partialView) {
$('#divItemList').append(partialView);
}
});
}
function addParameterRow(rw, prx) {
$.ajax({
type: "POST",
url: '@Url.Action("GetFlowItemParameterRow", "Flow")' + '?pId=' + '@Model.Id' + '&prefix=' + prx ,
success: function (partialView) {
rw.closest('table').find("tbody").append(partialView);
}
});
}
html buttons to add partial views like :
partial view methods on controller:
public PartialViewResult GetFlowItemRow(int? processTypeId)
{
FlowItemModel _item = new FlowItemModel() { ProcessTypeId = processTypeId ?? 0 };
return PartialView("~/Views/Flow/Partial/_FlowItem.cshtml", _item);
}
public PartialViewResult GetFlowItemParameterRow(int? pId, string prefix)
{
ViewData["ContainerPrefix"] = prefix;
FlowItemParameterModel _item = new FlowItemParameterModel() { };
return PartialView("~/Views/Flow/Partial/_FlowItemParameter.cshtml", _item);
}
Flow Item partial :
@using (Html.BeginCollectionItem2("OperationList"))
{
@Html.HiddenFor(model => model.ItemId)
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
Name
Unit
Add
}
Item Parameter Partial
@using (Html.BeginCollectionItem2("ParameterList"))
{
@Html.TextBoxFor(m => m.ParameterName, new { @class = "form-control" })
@Html.TextBoxFor(m => m.Unit, new { @class = "form-control" })
}