Using Bootstrap dual listbox with ASP.NET MVC - asp.net

I added the Bootstrap dual listbox plugin on my project and it's working fine, but how can I pass the contents of the selected listbox to my controller? It's already inside a form but if I try to get it through FormCollection it returns as null.
View:
<div class="form-group col-md-7" style="margin-left: -15px;">
<select style="display: none;" multiple="multiple" size="10" name="dualListbox" id="dualListbox" class="demo2">
#if (ViewData["Customers"] != null)
{
foreach (var item in ViewData["Customers"] as List<Testbox.Models.Customer>)
{
<option value="customer">#item.NAME #item.LName - #item.PHONE11</option>
}
}
</select>
</div>

Well you can do it as below:
I'll assume that your form id is demoform and below is how the post action method will look like:
[HttpPost]
public ActionResult GetForm()
{
string values = Request.Form["dualListbox"];
//Form[key] will be 'name' property of your select box
//You will get values as comma ',' separated values like option 1,
//option 2, option 4 etc., and I hope you know how you can get each
//options by splitting the comma separated values.
......
......
}

Here is an example to your question. Maybe this will help people in the future.
The trick is in the JS.
Let's say you want to use Bootstrap Dual Listbox in conjuction with ASP.NET MVC 4 and Kendo framework.
We will use the Razor syntax and C#.
First, we write in the view the placeholder for the code. We will be linking a kendo control and the Bootstrap Dual Listbox
<script>
var urlGetCascadeMultiSelectBrandTypeByBrand = "#(Url.Action("GetCascadeMultiSelectBrandTypeByBrand", "DropDownList"))";
</script>
<div class="col-md-12 col-sm-12 col-xs-12 padding-0 ">
<div class="col-md-6 col-sm-6 col-xs-12">
#Html.LabelFor(m => m.BrandId)<br />
#(Html.Kendo().DropDownListFor(m => m.BrandId)
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeDdlBrandBySegment", "DropDownList")
.Data("filterSegments");
})
.ServerFiltering(true);
})
.DataTextField("BrandName")
.DataValueField("BrandId")
.Filter(FilterType.Contains)
.CascadeFrom("SegmentId")
.OptionLabel("Select brand")
.Events(evt => evt.Change("onBrandIdDdlChange"))
.HtmlAttributes(new { #style = "width: 100%;" }))
#Html.ValidationMessageFor(m => m.BrandId)
</div>
<div class="col-md-6 col-sm-6 col-xs-12">
</div>
</div>
<div class="clear height10"></div>
<div class="col-md-12 col-sm-12 col-xs-12 padding-0 ">
<div class="col-md-12 col-sm-12 col-xs-12">
#Html.LabelFor(m => m.BrandTypeIdList)<br />
#if (Model.IsEdit)
{
#Html.ListBoxFor(m => m.BrandTypeIdList, Html.GetBrandTypeByBrandIdSelectListItemsList(Model.BrandId))
}
else
{
#Html.ListBoxFor(m => m.BrandTypeIdList, new List<SelectListItem>())
}
#Html.ValidationMessageFor(m => m.BrandTypeIdList)
</div>
</div>
Then, we create the C# helper code to go with it.
public static IEnumerable<SelectListItem> GetBrandTypeByBrandIdSelectListItemsList(this HtmlHelper htmlHelper, int brandId)
{
using (var dbContext = new Entities())
{
return dbContext.BrandType.Where(x => x.Active == true && x.BrandId == brandId).Select(BrandTypeToDdlSelector).ToList();
}
}
public static Func<BrandType, SelectListItem> BrandTypeToDdlSelector
{
get
{
return (x => new SelectListItem()
{
Value = x.BrandTypeId.ToString(),
Text = x.Name
});
}
}
public JsonResult GetCascadeMultiSelectBrandTypeByBrand(int? brandId)
{
var brandTypesList = DbContext.BrandType.Where(p => p.Active == true);
if (brandId != null)
{
brandTypesList = brandTypesList.Where(p => p.BrandId == brandId);
}
return Json(brandTypesList.Select(x => new { BrandTypeId = x.BrandTypeId, BrandTypeName = x.Name }), JsonRequestBehavior.AllowGet);
}
Then we create the JS code to manipulate the HTML at runtime and bind the selected values to the MVC model.
var brandTypeIdDualListbox = new Object();
$(document).ready(function ()
{
//we create the dual list control
brandTypeIdDualListbox = $('select[name="BrandTypeIdList"]').bootstrapDualListbox({
nonSelectedListLabel: 'Non-selected',
selectedListLabel: 'Selected',
preserveSelectionOnMove: 'moved',
moveOnSelect: false,
});
//we setup the change event for the control
$('select[name="BrandTypeIdList').on('change', function (args)
{
//we traverse every option
$("#BrandTypeIdList option").each(function (index,element)
{
//we check if the element has the `data-sortindex` attribute
if (!!$(element).attr('data-sortindex'))
$(element).attr('selected', 'selected');
else
$(element).removeAttr('selected');
});
})
});
function filterBrands()
{
var brandId = $("#BrandId").val();
if (brandId == "")
brandId = "-1";
return {
BrandId: brandId
};
}
function populateBrandTypeIdDualListbox()
{
$.getJSON(urlGetCascadeMultiSelectBrandTypeByBrand, filterBrands(), function (data)
{
var items;
$.each(data, function (i, item)
{
brandTypeIdDualListbox.append("<option value=" + item.BrandTypeId/*Value*/ + ">" + item.BrandTypeName/*Key or Text*/ + "</option>");
});
brandTypeIdDualListbox.trigger('bootstrapDualListbox.refresh', true); // we refresh the control
});
}
function onBrandIdDdlChange(evt)
{
var brandIdDropDownList = $("#BrandId").data("kendoDropDownList");
$('#BrandTypeIdList').empty();
brandTypeIdDualListbox.trigger('bootstrapDualListbox.refresh', true);
if ($("#BrandId").val() == "" || $("#BrandId").val() == "-1")
{
//if no value is selected we disable the control
$(".bootstrap-duallistbox-container").find("*").prop("disabled", true);
}
else
{
populateBrandTypeIdDualListbox();
$(".bootstrap-duallistbox-container").find("*").prop("disabled", false); // we enable the control
}
}

Related

Kendo grid edit template data bind - no data

I have a problem with edit template data bind for kendo grid.
I have binded my grid to model which is DataTable(it must be DataTable because all cloumns must be generated dynamic - I do not know the schema), I added export, sortable, pages etc. I also added edit button, and defined another cshtml file with edit template. It all works almost perfect. One problem is that I receive the empty model for edit template.
View:
#(Html.Kendo().Grid(Model.Data)
.Name("OpertionalViewGrid")
.DataSource(dataSource => dataSource
.Ajax()
.ServerOperation(false)
.Events(events => events.Error("error_handler"))
.Model(model =>
{
model.Id(p => p.Row[0]);
if (Model.Data != null)
{
foreach (System.Data.DataColumn column in Model.Data.Columns)
{
var field = model.Field(column.ColumnName, column.DataType);
}
}
})
.PageSize(20)
.Read(read =>
read.Action("Export_Read","OperationalView").Data("getRow"))
.Update(up => up.Action("UpdateRow", "OperationalView").Data("getRow"))
)
.Columns(c =>
{
if (Model.Data != null)
{
int index = 0;
foreach (System.Data.DataColumn column in Model.Data.Columns)
{
var col = c.Bound(column.ColumnName).Width("300px");
if (index < 2)
{
col.Locked(true).Lockable(false);
}
index++;
}
c.Command(command => { command.Edit();}).Width(100);
}
})
.Editable(editable => editable
.Mode(GridEditMode.PopUp)
.TemplateName("OperationalViewEditor")
)
.Scrollable()
.Reorderable(reorder => reorder.Columns(true))
.Resizable(r => r.Columns(true))
.Filterable(ftb => ftb.Mode(GridFilterMode.Row))
.HtmlAttributes(new { style = "height:100%" })
.Pageable()
.Navigatable()
.Sortable()
.Groupable(g => g.ShowFooter(true))
.ToolBar(tools => tools.Excel())
.Excel(excel => excel.AllPages(true))
.ToolBar(tools => tools.Pdf())
.Pdf(pdf => pdf
.AllPages()
.AvoidLinks()
.PaperSize("A4")
.Scale(0.8)
.Margin("2cm", "1cm", "1cm", "1cm")
.Landscape()
.RepeatHeaders()
.TemplateId("page-template")
.FileName("Export.pdf")
)
)
I have also my getRow function:
<scriprt>
function getRow()
{
var grid = $('#OpertionalViewGrid').data('kendoGrid');
var selectedItem = grid.dataItem(grid.select());
return selectedItem;
} </script>
In my controler I have:
public ActionResult Export_Read([DataSourceRequest]DataSourceRequest request)
{
var ovs = new OperationalViewService();
return Json(ovs.Read()/*.ToDataSourceResult(request)*/);
}
public ActionResult UpdateRow([DataSourceRequest] DataSourceRequest dsRequest, DataRowView row)
{
var t = 5;
return Json(row/*ModelState.ToDataSourceResult()*/);
}
And my edit template:
#model System.Data.DataRowView
<h3>Customized edit template</h3>
#if(Model == null)
{
<label> null</label>
}
else
{
<label> not null!!!!!!</label><br />
#Html.Label(Model.DataView.Table.Columns.Count.ToString())<br />
#Html.Label(Model.ToString())<br />
#Html.Label(Model.Row.Table.Columns.Count.ToString())<br />
}
#for (int i = 0; i < Model.DataView.Table.Columns.Count; i++)
{
#Html.LabelFor(model => model.DataView.Table.Columns[i].ColumnName.ToString())
#Html.LabelFor(model => model.Row[i].ToString())
<br />
}
So.. when I click Edit button, I get a window with proper header, with note "01234 not null!! 0 DataRowView 0 "
So I conclude that model is of proper type, one problem is to pass my selected row.
I am very new to web and Kendo/telerik so my question: how to send my selected grid row to edit template?
Another question is: why when I click "edit" i do not get into UpdateRow action in my controler?
I have ommited the problem by using edit mode InCell - one important note is to specify the column type, as shown here: http://www.telerik.com/forums/inline-batch-crud-fails-with-datatable-model

Kendo Editors not working in jQuery Steps?

I am using jQuery steps within a form.
I have some model properties which I want to edit using Kendo Editors.
I a using following (partial) code, the rest is markup for the jQuery wizard:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Start, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Start, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Start, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.End, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.End, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.End, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#(Html.Kendo().DropDownListFor(x => x.RecurrenceRule)
.Name("recurrenceRule")
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new List<SelectListItem>
{
new SelectListItem
{
Text = #"Nie",
Value = "Never"
},
new SelectListItem
{
Text = #"Täglich",
Value ="Daily"
},
new SelectListItem
{
Text = #"Monatlich",
Value ="monthly"
}
}))
</div>
</div>
}
For some reason, the editors for Date properties are not properly displayed while the #(Html.Kendo().DropDownListFor( is working just fine.
In another step (not documented here) #(Html.Kendo().MultiselectFor( does not work correctly either.
What could cause this issue?
Update:
Positioning this code outside the markup for jQuery steps works fine. Here is my code for the wizard:
#using (Ajax.BeginForm("Wizard_Submit", "Wizard", new
{
Area = ""
}, new AjaxOptions
{
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace
}, new
{
id = "wizardSumbit"
}))
{
////0
<div id="wizard">
<h3></h3>
<section>
<p></p>
#(Html.Kendo().DropDownListFor(x => x.SlRateBaseTypes)
.Name("slRateBaseTypes")
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new List<SelectListItem>
{
new SelectListItem
{
Text = #"abc",
Value = ((int) SlRateBaseTypes.Course).ToString()
},
new SelectListItem
{
Text = #"cde",
Value = ((int) SlRateBaseTypes.Leisure).ToString()
}
}))
</section>
#*//1*#
<h3>Vorlage</h3>
<section>
<p>Wähle eine Vorlage aus</p>
#(Html.Kendo().DropDownListFor(x => x.OfferingTemplateId)
.Name("offeringTemplate")
.DataTextField("Text")
.DataValueField("Value")
.Events(x => x.Change("onOfferingTemplateChange"))
.DataSource(
source => source
.Read(read => read.Url(Url.Action("Wizard_GetOfferingTemplates", "Wizard")).Data("getSlRateBaseTypesSelection").Type(HttpVerbs.Post))
.ServerFiltering(true)
)
.AutoBind(false))
<div id="offeringTemplateDescription"></div>
</section>
#*//2*#
<h3></h3>
<section>
<p></p>
<label for="optional"></label>
#(Html.Kendo().MultiSelectFor(x => x.SlRateBaseTypes)
.Name("selectOfferingRateTypes")
.Placeholder("")
.AutoClose(false)
.DataTextField("Text")
.DataValueField("Value")
.DataSource(
source => source
.Read(read => read.Url(Url.Action("Wizard_GetPossibleOfferingRates", "Wizard")).Data("getSlRateBaseTypesSelection").Type(HttpVerbs.Post))
.ServerFiltering(true)
)
)
..
}
Developer options reveal following :
"Cannot read property 'inspectKendoWidget' of undefined"
"TypeError: Cannot read property 'inspectKendoWidget' of undefined
at :1:15"
Update 2:
this is the html code for the documented datepicker:
<span class="k-widget k-datetimepicker k-header k-input" style="width: 100%;">
<span class="k-picker-wrap k-state-default"><input data-val="true" data-val-date="The field Start must be a date." data-val-required="The Start field is required." id="Start" name="Start" type="text" value="01.01.0001 00:00" data-role="datetimepicker" class="k-input" role="combobox" aria-expanded="false" aria-disabled="false" style="width: 100%;" vk_15f93="subscribed">
<span unselectable="on" class="k-select">
<span class="k-link k-link-date" aria-label="Open the date view">
<span unselectable="on" class="k-icon k-i-calendar" aria-controls="Start_dateview"><
/span>
</span>
<span class="k-link k-link-time" aria-label="Open the time view">
<span unselectable="on" class="k-icon k-i-clock" aria-controls="Start_timeview">
</span>
</span>
</span>
</span>
</span>
Just in case anybody cares:
you'll have to remove some css styles in the jQuery steps css file
-> change to:
.tabcontrol ul > li
{
display: block;
/*padding: 0;*/
}
Update:
Also important: Actually the basic problem was solved by modifiying the jquery.steps.js file according to another post I found on the internet:
--> comment this section
//function render(wizard, options, state)
//{
// // Create a content wrapper and copy HTML from the intial wizard structure
// var wrapperTemplate = "<{0} class=\"{1}\">{2}</{0}>",
// orientation = getValidEnumValue(stepsOrientation, options.stepsOrientation),
// verticalCssClass = (orientation === stepsOrientation.vertical) ? " vertical" : "",
// contentWrapper = $(wrapperTemplate.format(options.contentContainerTag, "content " + options.clearFixCssClass, wizard.html())),
// stepsWrapper = $(wrapperTemplate.format(options.stepsContainerTag, "steps " + options.clearFixCssClass, "<ul role=\"tablist\"></ul>")),
// stepTitles = contentWrapper.children(options.headerTag),
// stepContents = contentWrapper.children(options.bodyTag);
// // Transform the wizard wrapper and remove the inner HTML
// wizard.attr("role", "application").empty().append(stepsWrapper).append(contentWrapper)
// .addClass(options.cssClass + " " + options.clearFixCssClass + verticalCssClass);
// // Add WIA-ARIA support
// stepContents.each(function (index)
// {
// renderBody(wizard, state, $(this), index);
// });
// stepTitles.each(function (index)
// {
// renderTitle(wizard, options, state, $(this), index);
// });
// refreshStepNavigation(wizard, options, state);
// renderPagination(wizard, options, state);
// }
-->replace with this
function render(wizard, options, state) {
// Create a content wrapper and copy HTML from the intial wizard structure
var contentWrapperTemplate = "<{0} class=\"{1}\"></{0}>",
stepsWrapperTemplate = "<{0} class=\"{1}\">{2}</{0}>",
orientation = getValidEnumValue(stepsOrientation, options.stepsOrientation),
verticalCssClass = (orientation === stepsOrientation.vertical) ? " vertical" : "",
contentWrapper = $(contentWrapperTemplate.format(options.contentContainerTag, "content " + options.clearFixCssClass)),
stepsWrapper = $(stepsWrapperTemplate.format(options.stepsContainerTag, "steps " + options.clearFixCssClass, "<ul role=\"tablist\"></ul>"));
// Transform the wizard wrapper by wrapping the innerHTML in the content wrapper, then prepending the stepsWrapper
wizard.attr("role", "application").wrapInner(contentWrapper).prepend(stepsWrapper)
.addClass(options.cssClass + " " + options.clearFixCssClass + verticalCssClass);
//Now that wizard is tansformed, select the the title and contents elements
var populatedContent = wizard.find('.content'),
stepTitles = populatedContent.children(options.headerTag),
stepContents = populatedContent.children(options.bodyTag);

ModelStateErrors not showing up in ValidationSummary

I have two validations in my action like so:
if (viewModel.IssuePDFFile == null || viewModel.IssuePDFFile.ContentLength == 0)
{
ModelState.AddModelError("", "Please select a file to upload.");
if (selectedJournalId > 0)
{
return RedirectToAction("CreateNewIssue", new { journalId = selectedJournalId });
}
else
{
return RedirectToAction("CreateNewIssue",
new { journalId = selectedJournalId, journalName = viewModel.JournalName });
}
}
var fileInfo = new FileInfo(viewModel.IssuePDFFile.FileName);
if (!StaticData.AcceptedContentTypes.Contains(viewModel.IssuePDFFile.ContentType,
StringComparer.InvariantCultureIgnoreCase) ||
!fileInfo.Extension.Equals(".pdf", StringComparison.InvariantCultureIgnoreCase))
{
ModelState.AddModelError("IssuePDFFile", "You can only select a PDF file.");
if (selectedJournalId > 0)
{
return RedirectToAction("CreateNewIssue", new { journalId = selectedJournalId });
}
else
{
return RedirectToAction("CreateNewIssue", new { journalId = selectedJournalId,
journalName = viewModel.JournalName });
}
}
And in my view, I have this:
#using (Html.BeginForm(...)
{
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
#*File*#
<div class="form-group">
#Html.LabelFor(model => model.IssuePDFFile, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-12">
<input type="file" id="File" name="issuePDFFile" placeholder="Select a file to upload" />
#Html.ValidationMessageFor(model => model.IssuePDFFile, "", new { #class = "text-danger" })
</div>
</div>
...
}
Please note that the excludePropertyErrors argument in the call to the #Html.ValidationSummary extension is set to false in the hope that errors that I myself add to the model state errors collection using keys other than my property names will also show up in the summary.
However, the two error messages shown in the code snippet above do not show up anywhere, neither in the validation summary, nor in the ValidationFor place where I think at least one of them, the one I have added a key for, should show up?
It looks like you are performing a redirect after writing errors to your modelstate. Redirecting will loose the modelstate, as it is only valid for the lifetime of the request, which ends when you redirect.
The solution is to
return View(Model) //customise as necessary
rather than your current
return RedirectToAction()
Writing the response back to the client within the same reuqest will mean the Modelstate is available to use, and display the errors.

Dynamically add/remove table rows in a wizard

I'm currently using this Wizard in my application.
In one of my steps, I need to add and remove items from a table. Add functions works fine. But I can't make the Delete function work. I've searched SO and other resources online, can't find a solution.
This is what I've so far:
Wizard Step 2 Table:
<table id="LibList" class="table table-responsive table-striped">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Types)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Teams)
</th>
<th>
<label>Delete</label>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Step3.Liabilities) {
Html.RenderPartial("_LibListItem", item);
}
</tbody>
</table>
Partial View:
<tr>
<td>
#Html.DropDownListFor(model => model.Type, Model.Types, new { #class = "selectpicker form-control", id = string.Format("{0}_{1}", "TYPE", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Name, new { #class = "form-control", id = string.Format("{0}_{1}", "NAME", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Teams, new { #class = "form-control", #type = "currency", id = string.Format("{0}_{1}", "TERMS", Model.ID) })
</td>
<td>
#Ajax.ActionLink("Delete", "Delete", "QuestionWizard", new { id = Model.ID },
new AjaxOptions() {
HttpMethod = "Delete",
Confirm = "Are you sure you want to delete this record?",
OnComplete = "function() { $(this).parent().parent().remove() }"
},
new { #class = "btn btn-primary" })
</td>
Add and Delete Action:
public ActionResult AddRow() {
LiabilityModel lib = new LiabilityModel();
try {
if (LiabilitySessionState.Count == 0) {
lib.ID = 1;
} else {
lib.ID = LiabilitySessionState.LastOrDefault().ID + 1;
}
LiabilitySessionState.Add(lib);
return PartialView("_LibListItem", lib);
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
return RedirectToAction("Index", "Error");
}
}
public void Delete(int Id) {
try {
if (LiabilitySessionState.Count > 0) {
LiabilitySessionState.RemoveAll(item => item.ID == Id);
}
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
RedirectToAction("Index", "Error");
}
}
public List<LiabilityModel> LiabilitySessionState {
get {
if (Session["LiabilityModel"] == null) {
return LiabilitySessionState = new List<LiabilityModel>();
} else {
return Session["LiabilityModel"] as List<LiabilityModel>;
}
}
set {
Session["LiabilityModel"] = value;
}
}
Script:
<script type="text/javascript">
$(document).ready(function () {
$('.add-button').click(function () {
var action = "/QuestionWizard/AddRow";
$.ajax({
url: action,
cache: false,
success: function (result) {
$("#LibList tbody").append($(result));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
}
});
})
$(".remove-button").click(function () {
$(this).parents().parent().remove();
return false;
});
});
Right now, the Delete Action returns a blank page, because I'm not returning a view. But, I'm not sure what view to return.
Please help!
Thanks in advance.
Firstly your Delete() method is changing data so it needs to be a POST, not a GET (which #Ajax.ActionLink() is). Next, your 'delete link' does not have class="remove-button" so $(".remove-button").click(function () { wont do anything. Since you already using jquery ajax methods to add items (refer notes below), there seems no point using the Ajax helpers anyway (your just adding extra overhead by loading the jquery.unobtrusive-ajax.js file).
Change the last <td> element of the partial to
<td>
<button type="button" class="remove-button" data-id=#Model.ID>Delete</button>
</td>
Then in the main view, use the following script (note you need event delegation so you can remove dynamically added items)
var url = '#Url.Action("Delete", "YourControllerName")';
$('#LibList').on('click', .remove-button, function() {
var row = $(this).closest('tr');
var id = $(this).data('ID');
if (id) { // assumes property ID is nullable, otherwise may need to test if id != 0
$.post(url, {ID: id }, function(response) {
if(response) {
row.remove();
} else {
// Oops - display message?
}
});
} else {
// its a new row so it has not been saved yet - just delete the row
row.remove();
}
});
and the controller method should look like
[HttpPost]
public JsonResult Delete(int ID)
{
// delete the item
return Json(true); // or if an exception occurred, return(null) to indicate failure
}
Side notes:
Both your AddRow() and Delete() methods include a return
RedirectToAction() statement. You are making ajax calls which stay
on the same page so RedirectToAction() is pointless (it will never
be executed)
If you believe the "Add functions works fine", then you must have
some unnecessary hacks in order to make this work (your generating
duplicate name attributes for the controls (without indexers)
which will not bind to your collection of Liabilities). You need
to generate existing items in a for loop and include a hidden
input for an Index property, and use a client side template to
generate new items; or you need to use the BeginCollectionItem
helper if using a partial view. This
answer
shows how to use both techniques.
You don't appear (and you should not need) to access the id
attributes of the controls in your table. Instead of using id = string.Format("{0}_{1}", "NAME", Model.ID), just use id="" which
will render the element without and id attribute.

Partial View and Validation

I have tried the many solutions online for trying to get validation to work when loading a partial view, but none of them seemed to work.
I currently have some jQuery code which looks like:
$(".alert").click(function () {
$("#add-comics-container").load("/ManageComics/ComicEditor", function () {
$.validator.unobtrusive.parse("#add-comics-container");
});
$("#add-comics-container").fadeIn();
$(".blackout").css("display", "block");
return false;
});
My /ManageComics/ComicEditor looks like:
public ActionResult ComicEditor() {
return PartialView("_ComicEditorPartial");
}
My Partial more or less looks like:
#model Comics.Models.LocalComicModel
#Html.BeginForm("Index", "ManageComics", FormMethod.Post, new { enctype = "multipart/form-data", id = "addComicForm", value = "1"}){
<div class="add-comics-item">
<div class="add-comics-left">
<span class="add-comics-title bold_text">#Html.LabelFor(u => u.Title)</span>
<div class="add-comics-help">WebComic Title throughout website</div>
#Html.ValidationMessageFor(u => u.Title)
</div>
<div class="add-comics-right">
#Html.TextBoxFor(u => u.Title)
</div>
</div>
}

Resources