I'm creating a View that allows the user to add and remove objects in list in a particular way that the client wants. The user has to be able to add or remove multiple forms on the page before submitting. When the page posts it needs to display what's already in the list, with the option to remove each item, plus allow the user to add/remove more objects before submitting again.
I'm able to use JQuery and a Partial View to let the user add or remove new forms before submitting. What I can't figure out is how to provide a button to remove objects that are already in the list from a previous submit.
Can anyone help me out with this? It would be greatly appreciated.
Here's what I've figured out so far. In my View:
@using (Html.BeginForm())
{
<div id="recipients">
@foreach (var p in Model)
{
@Html.Partial("_Recipient", p)
}
<div id="recipients0"></div>
</div>
<button id="add" type="button">Add</button>
<button id="remove" type="button">Remove</button>
<input type="submit" value="Save" />
}
<script type="text/javascript">
$('#add').click(function () {
var url = '@Url.Action("Recipient")';
var div = $('#recipients');
var divlast = $('div[id^="recipients"]:last');
var num = parseInt(divlast.prop("id").match(/\d+/g), 10) + 1;
var newdiv = $(document.createElement('div')).attr('id', 'recipients' + num)//rest of code
$.get(url, function (response) {
div.append(newdiv);
newdiv.append(response);
});
})
$('#remove').click(function () {
var div = $('#recipients');
var divlast = $('div[id^="recipients"]:last');
var num = parseInt(divlast.prop("id").match(/\d+/g), 10);
alert("div[id='recipients" + num + "']");
$("div[id='recipients" + num + "']").remove();
//div.remove('div[id^="recipients' + num + '"]');
})
The Partial View with the form to add data to a new object:
@using (Html.BeginCollectionItem("recipients"))
{
@Html.HiddenFor(m => m.ID)
@Html.LabelFor(m => m.Recipient)
@Html.TextBoxFor(m => m.Recipient)
@Html.ValidationMessageFor(m => m.Recipient)
<br />
@Html.LabelFor(m => m.Amount)
@Html.TextBoxFor(m => m.Amount)
@Html.ValidationMessageFor(m => m.Amount)
}
<td>
@Ajax.ActionLink(
"Remove",
"Remove",
"CashRecipients",
new AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "onDeleteSuccess"
}
)
</td>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script>
var onDeleteSuccess = function (result) {
alert('Howdy');
};
</script>
My controller:
public PartialViewResult Recipient()
{
return PartialView("_Recipient", new CashRecipientViewModel());
}
// GET:
public ActionResult Create()
{
List<CashRecipientViewModel> model = new List<CashRecipientViewModel>();
return View(model);
}
// POST: CashRecipients/Create
[HttpPost]
public ActionResult Create([Bind(Include = "ID,Amount,Recipient")] IEnumerable<CashRecipientViewModel> recipients)
{
return View(recipients);
}
My View Model:
public class CashRecipientViewModel
{
public int? ID { get; set; }
public decimal Amount { get; set; }
[Required(ErrorMessage = "Please enter the name of the recipient")]
public string Recipient { get; set; }
}
Having a single 'remove' button does not make sense and you need a 'remove' associated with each item in the collection. In addition, remove the id
attributes from your <div>
elements and use relative selectors.
Change the partial to
@using (Html.BeginCollectionItem("recipients"))
{
<div class="item" data-id="@Model.ID"> // add an enclosing element
@Html.HiddenFor(m => m.ID)
@Html.LabelFor(m => m.Recipient)
@Html.TextBoxFor(m => m.Recipient)
@Html.ValidationMessageFor(m => m.Recipient)
@Html.LabelFor(m => m.Amount)
@Html.TextBoxFor(m => m.Amount)
@Html.ValidationMessageFor(m => m.Amount)
<button type="button" class="remove">Remove</button> // add button
</div>
}
The in the main view, your scripts will be
var url = '@Url.Action("Recipient")';
var recipients = $('#recipients');
$('#add').click(function () {
$.get(url, function (response) {
recipients.append(response);
});
});
$('#recipients').on('click', '.remove', (function() {
var container = $(this).closest('.item');
var id = container.data('id');
if (!id) {
container.remove();
} else {
// see notes below
}
}
For any items that have been added in the view, the value of property ID
will be null
and the code in the if
block will remove the item from the view. However for existing items that you may be editing (where the ID
has a value), then you need to consider how to remove it from the database. Options include
Adding an additional property in the view model (say) public bool
IsDeleted { get; set; }
and including a hidden input in the partial
for it. You could then set it to true
so when you submit, you can
delete all recipients.Where(x => x.IsDeleted);
@Html.HiddenFor(m => m.IsDeleted, new { @class = "flag" })
} else {
container.find('.flag').val('true');
}
Making an ajax call to a server method which deletes the item from the database, and if successful, remove the associated container from the DOM
} else {
$.post(yourDeleteUrl, { id: id }, function(response) {
if(response) {
container.remove()
} else {
// oops, something went wrong
}
});
}