What is the best way to add/delete rows to a table when a button is clicked? I need rows created from ChildClass properties (child class is a list within the main class/model).
Currently have a View (model is MyMain) which references a Partial View using RenderPartial.
The partial view displays the properties of the model, a class called MyChild which is an object list within MyMain.
I want to have add and delete buttons to dynamically add the rows which are held within the partial view.
So adding MyChild repeatedly for more rows on the list. Is this possible? Or should I not be using partial views for this?
Updated Code
Below are the current classes and views I'm working with, I've been trying to implement the BeginCollectionItem helper but I'm getting null ref where I'm trying to load the partial view despite the if statement saying to create a new instance of the child class if doesn't exist - why is this being ignored?
Main View
@using (Html.BeginForm())
{
<table>
<tr>
<th>MyMain First</th>
<th>MyChild First</th>
</tr>
<tr>
<td>
@Html.EditorFor(m => m.First)
</td>
<td>
@if (Model.child != null)
{
for (int i = 0; i < Model.child.Count; i++)
{
Html.RenderPartial("MyChildView");
}
}
else
{
Html.RenderPartial("MyChildView", new MvcTest.Models.MyChild());
}
</td>
</tr>
@Html.ActionLink("Add another", "Add", null, new { id = "addItem" })
</table>
}
Partial View
@model MvcTest.Models.MyChild
@using (Html.BeginCollectionItem("myChildren"))
{
Html.EditorFor(m => m.Second);
}
Models
public class MyMain
{
[Key]
public int Id { get; set; }
public string First { get; set; }
public List<MyChild> child { get; set; }
}
public class MyChild
{
[Key]
public int Id { get; set; }
public string Second { get; set; }
}
Controller
public class MyMainsController : Controller
{
// GET: MyMains
public ActionResult MyMainView()
{
return View();
}
[HttpPost]
public ActionResult MyMainView(IEnumerable<MyChild> myChildren)
{
return View("MyMainView", myChildren);
}
public ViewResult Add()
{
return View("MyChildView", new MyChild());
}
}
Updated Answer - The original code is NOT true to 'dynamic', however it allows for everything I needed to do within the question parameters.
Initially, I couldn't get Stephen's BCI suggestion in the question comments working, since then I have and it's brilliant. The code below in the updated section will work if you copy + paste, but you will need to either manually download BCI from GIT or use PM> Install-Package BeginCollectionItem
with Package Manager Console in Visual Studio.
I had some issue with various points using BCI due to the complexity and not having done MVC before - here is more information on dealing with accessing class.property(type class).property(type class).property
.
Original answer - I've gone with a more clear example below than in my question which quickly got too confusing.
Using two partial views, one for the list of employees and another for the creation of a new employee all contained within the viewmodel of companyemployee which contains an object of company and a list of employee objects. This way multiple employees can be added, edited or deleted from the list.
Hopefully this answer will help anyone looking for something similar, this should provide enough code to get it working and at the very least push you in the right direction.
I've left out my context and initialiser classes as they're only true to code first, if needed I can add them.
Thanks to all who helped.
Models - CompanyEmployee being the view model
public class Company
{
[Key]
public int id { get; set; }
[Required]
public string name { get; set; }
}
public class Employee
{
[Key]
public int id { get; set; }
[Required]
public string name { get; set; }
[Required]
public string jobtitle { get; set; }
[Required]
public string number { get; set; }
[Required]
public string address { get; set; }
}
public class CompanyEmployee
{
public Company company { get; set; }
public List<Employee> employees { get; set; }
}
Index
@model MMV.Models.CompanyEmployee
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<fieldset>
<legend>Company</legend>
<table class="table">
<tr>
<th>@Html.LabelFor(m => m.company.name)</th>
</tr>
<tr>
<td>@Html.EditorFor(m => m.company.name)</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Employees</legend>
@{Html.RenderPartial("_employeeList", Model.employees);}
</fieldset>
<fieldset>
@{Html.RenderPartial("_employee", new MMV.Models.Employee());}
</fieldset>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Submit" class="btn btn-default" />
</div>
</div>
PartialView of List of Employees
@model IEnumerable<MMV.Models.Employee>
@using (Html.BeginForm("Employees"))
{
<table class="table">
<tr>
<th>
Name
</th>
<th>
Job Title
</th>
<th>
Number
</th>
<th>
Address
</th>
<th></th>
</tr>
@foreach (var emp in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => emp.name)
</td>
<td>
@Html.DisplayFor(modelItem => emp.jobtitle)
</td>
<td>
@Html.DisplayFor(modelItem => emp.number)
</td>
<td>
@Html.DisplayFor(modelItem => emp.address)
</td>
<td>
<input type="submit" formaction="/Employees/Edit/@emp.id" value="Edit"/>
<input type="submit"formaction="/Employees/Delete/@emp.id" value="Remove"/>
</td>
</tr>
}
</table>
}
Partial View Create Employee
@model MMV.Models.Employee
@using (Html.BeginForm("Create","Employees"))
{
<table class="table">
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<tr>
<td>
@Html.EditorFor(model => model.name)
@Html.ValidationMessageFor(model => model.name, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => model.jobtitle)
@Html.ValidationMessageFor(model => model.jobtitle)
</td>
<td>
@Html.EditorFor(model => model.number)
@Html.ValidationMessageFor(model => model.number, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => model.address)
@Html.ValidationMessageFor(model => model.address, "", new { @class = "text-danger" })
</td>
</tr>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
Controller - I used multiple but you can put them all in one
public class CompanyEmployeeController : Controller
{
private MyContext db = new MyContext();
// GET: CompanyEmployee
public ActionResult Index()
{
var newCompanyEmployee = new CompanyEmployee();
newCompanyEmployee.employees = db.EmployeeContext.ToList();
return View(newCompanyEmployee);
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Employee employee = db.EmployeeContext.Find(id);
db.EmployeeContext.Remove(employee);
db.SaveChanges();
return RedirectToAction("Index", "CompanyEmployee");
}
[HttpPost]
public ActionResult Create([Bind(Include = "id,name,jobtitle,number,address")] Employee employee)
{
if (ModelState.IsValid)
{
db.EmployeeContext.Add(employee);
db.SaveChanges();
return RedirectToAction("Index", "CompanyEmployee");
}
return View(employee);
}
}
Updated Code - using BeginCollectionItem - dynamic add/delete
Student Partial
@model UsefulCode.Models.Person
<div class="editorRow">
@using (Html.BeginCollectionItem("students"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
@Html.TextBoxFor(m => m.firstName)
</span>
</div>
<div class="ui-block-b">
<span>
@Html.TextBoxFor(m => m.lastName)
</span>
</div>
<div class="ui-block-c">
<span>
<span class="dltBtn">
<a href="#" class="deleteRow">X</a>
</span>
</span>
</div>
</div>
}
</div>
Teacher Partial
@model UsefulCode.Models.Person
<div class="editorRow">
@using (Html.BeginCollectionItem("teachers"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
@Html.TextBoxFor(m => m.firstName)
</span>
</div>
<div class="ui-block-b">
<span>
@Html.TextBoxFor(m => m.lastName)
</span>
</div>
<div class="ui-block-c">
<span>
<span class="dltBtn">
<a href="#" class="deleteRow">X</a>
</span>
</span>
</div>
</div>
}
</div>
Register Controller
public ActionResult Index()
{
var register = new Register
{
students = new List<Person>
{
new Person { firstName = "", lastName = "" }
},
teachers = new List<Person>
{
new Person { lastName = "", firstName = "" }
}
};
return View(register);
}
Register and Person Model
public class Register
{
public int id { get; set; }
public List<Person> teachers { get; set; }
public List<Person> students { get; set; }
}
public class Person
{
public int id { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
}
Index
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@model UsefulCode.Models.Register
<div id="studentList">
@using (Html.BeginForm())
{
<div id="editorRowsStudents">
@foreach (var item in Model.students)
{
@Html.Partial("StudentView", item)
}
</div>
@Html.ActionLink("Add", "StudentManager", null, new { id = "addItemStudents", @class = "button" });
}
</div>
<div id="teacherList">
@using (Html.BeginForm())
{
<div id="editorRowsTeachers">
@foreach (var item in Model.teachers)
{
@Html.Partial("TeacherView", item)
}
</div>
@Html.ActionLink("Add", "TeacherManager", null, new { id = "addItemTeachers", @class = "button" });
}
</div>
@section scripts {
<script type="text/javascript">
$(function () {
$('#addItemStudents').on('click', function () {
$.ajax({
url: '@Url.Action("StudentManager")',
cache: false,
success: function (html) { $("#editorRowsStudents").append(html); }
});
return false;
});
$('#editorRowsStudents').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
$('#addItemTeachers').on('click', function () {
$.ajax({
url: '@Url.Action("TeacherManager")',
cache: false,
success: function (html) { $("#editorRowsTeachers").append(html); }
});
return false;
});
$('#editorRowsTeachers').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
});
</script>
}
StudentManager Action:
public PartialViewResult StudentManager()
{
return PartialView(new Person());
}
You can use partial views for it, but the problem is that generating the Id properties with EditorFor
causes duplicates to be generated.
What worked for me was using var view = @Html.Raw(@Html.Partial('Your partial'))
inside javascript in my main view.
This variable will then have the view with the default ids, I then replace the id property and name property with id=YourMainListName_{ItemNumber}__Property
and name=YourMainListName_{ItemNumber}__Property
Where {ItemNumber}
will be the index of the new item in the list.
It takes lots of javascript to get this working correctly, but as long as you have the Id and Name properties set correctly everything should work correctly including model binding when posting back your page.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With