Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Add Child Records Dynamically

Using the Entity Data Framework, I have a model defined called Client which simply contains an id and a name. I then have an association with another model called Appointment containing an id and a date.

For the form, I wish to allow users to create a new client and on the same form, add appointments (before the client is created). I have it partially working (adding one appointment).

In the controller:

[HttpPost]
public ActionResult Create(Client client)
{
    var appointment = new Appointment();
    UpdateModel(appointment);
    client.Appointments.Add(appointment);
    db.AddToClients(client);
    db.SaveChanges();
    return RedirectToAction("Index");
}

In the view:

<div class="editor-label">
    <%= Html.LabelFor(model => model.Name) %>
</div>
<div class="editor-field">
    <%= Html.TextBoxFor(model => model.Name) %>
    <%= Html.ValidationMessageFor(model => model.Name) %>
</div>
<% Html.RenderPartial("Appointment", new Appointment()); %>

'Partial' view:

<div class="editor-label">
    <%= Html.LabelFor(model => model.AppointmentDate) %>
</div>
<div class="editor-field">
    <%= Html.TextBoxFor(model => model.AppointmentDate) %>
    <%= Html.ValidationMessageFor(model => model.AppointmentDate) %>
</div>

However, this is not ideal for several reasons:

  1. Only one appointment can be added
  2. Issues will occur if there are any name conflicts (e.g. a field called Comments in both Client and Appointment table), since no prefixing is done on the field names, which also prevents more than one appointment being added as well

What can I do to allow multiple appointments to be added, i.e. using a button to render a partial view (using AJAX probably) on the page when 'new appointment' is clicked to allow as many appointments as possible? I would also like this to be a shared view that can be used for both creating and editing records as well as adding/deleting appointments in the same form.

Update
Some progress, using a ViewModel:

public class ClientViewModel
{
    public Client Client { get; set; }
    public List<Appointment> Appointments { get; set; }
}

Controller:

public ActionResult Create()
{
    Client c = new Client();
    List<Appointment> appointments = new List<Appointment>();
    appointments.Add(new Appointment() { AppointmentDate = DateTime.Now });
    appointments.Add(new Appointment() { AppointmentDate = DateTime.Now.AddMonths(1) });

    var model = new ClientViewModel
    {
        Client = c,
        Appointments = appointments
    };
    return View(model);
}

[HttpPost]
public ActionResult Create(ClientViewModel m)
{
    try
    {
        foreach (var appointment in m.Appointments)
        {
            m.Client.Appointments.Add(appointment);
        }
        db.AddToClients(m.Client);

        db.SaveChanges();
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

View (for appointments, does not work with a foreach loop, as each of the appointments are prefixed with Appointment_ rather than Appointment[0]_):

<%= Html.EditorFor(model => Model.Client) %>
<% for (int i = 0; i < Model.Appointments.Count(); i++) { %>
    <%= Html.EditorFor(model => Model.Appointments[i])%>
<% } %>

This is fine for creating appointments for a new client, but what about editing them, as well as adding new appointments without posting back the form? For adding, a link that shows a new appointment form under the existing one, with another link to delete (hide from view and set a hidden field value).

Update 2
Got it working, for adding appointments when first creating a client. However, I am not sure how to update existing appointments (without just deleting them all and adding again).

In controller:

[HttpPost]
public PartialViewResult AddAppointment(Appointment appointment, int index)
{
    List<Appointment> appointments = new List<Appointment>();
    for (int i = 0; i < index; i++)
    {
        appointments.Add(null);
    }
    appointments.Add(new Appointment() { AppointmentDate = DateTime.Now });
    ViewData["index"] = index;
    var model = new ClientViewModel
    {
        Appointments = appointments
    };
    return PartialView("Appointment", model);
}

View:

<%= Html.EditorFor(model => Model.Client) %>
<div id="appointments">
<% for (int i = 0; i < Model.Appointments.Count(); i++) { %>
    <%= Html.EditorFor(model => Model.Appointments[i]) %>
<% } %>
</div>
<a href="javascript:;" id="addappointment">Add Appointment</a>
<script type="text/javascript">
    var appIndex = <%= Model.Appointments.Count() %>;
    $("#addappointment").click(function () {
        $.post(
            "/Client/AddAppointment",
            {"index" : appIndex}, 
            function (result) {
                $("#appointments").append(result);
                // increase index by 1
                appIndex++;
            }
        );
});
like image 310
SamWM Avatar asked Oct 29 '10 11:10

SamWM


1 Answers

Just floating some ideas around..

How about if you make the Appointment partial view contain a form.

Use jQuery/AJAX to handle the submission of the form:

$.post(
   "/SomeController/AddAppointment",
        appointment, // grab this from the form
        function (result) { 
           $("#target").html(result); // this will be the div which contains the partial
        }
     );

Which would send the strongly-typed Appointment to the following Action method:

[HttpPost]
public PartialViewResult AddAppointment(Appointment appointment)
{
   // code to persist entity
   // code to add errors to model (if necessary)
   return PartialView("Appointment");
}

Basically, your submitting the form using AJAX, then re-rendering the result back into the partial. The result is similar to the AJAX UpdatePanel (of WebForms fame).

So you'd fill out the details, hit "submit", the details gets posted to the controller (using AJAX), then the form would be blank again, so you can keep adding appointments.

Would that work?

like image 193
RPM1984 Avatar answered Nov 16 '22 10:11

RPM1984