5

Background
I'm working on client's website project and I've used MVC to create the site. I've got EF working with a SQLDB to store data.

My WebApp is fitted with a back-office area for managing/adding/updating Items etc.

A particular page in question allows the user to add a new Item to the database by entering data and clicking 'Save' on the form.

The Item has some properties which all get added the DB nicely in a new row.

Item also contains a List<Appointment>s declared against it (I will serialize these and save them in the appropriate column).

The objects look like this (I have removed some properties for brevity):

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Appointment> Appointments { get; set; }
    // Plus some other properties
}

public class Appointment
{
    public string Day { get; set; }
    public string From { get; set; }
    public string To { get; set; }
}

I've set out a ViewModel to use and I'm practising all that usual good-practice stuff. It looks like this:

public class AddItemViewModel
{
    [Required(ErrorMessage = "Please enter an item name")]
    [Display(Name="Item Name")]
    public string Name { get; set; }

    [Display(Name="Appointments")]
    public List<Appointment> Appointments { get; set; }

    ...
    // Other properties in here
    ...
}

I am using the Razor view engine.

I have everything working fine using the @Html.EditorFor(x => x.Property) helper; for all (but one - tell you in a moment) elements on the root object's (Item's) properties.

I can post the ViewModel to the controller and save a new Item, and all that jazz - so all is good there.

Problem
I have a List<Appointment> declared in my ViewModel.

How can I allow the user to add new Appointments to this List<Appointment> from within the page?

I want to assign these to the Item object to be created when the ViewModel is posted - I'll do this in my controller/services etc.

How can I achieve this?

Desired view
I need the user to be able to add Appointments from within the page - up to however many they like, really.

I would like there to be a list of the "created" Appointments in a list (ul or anything similar etc.) in an area on the page.

Underneath that list, I would like some textboxes that are relevant to the Day, From and To properties for the Appointment - with an 'Add' button, which appends it to the list (and clears the textboxes, ideally).

As a bonus, I would also like some sort of "×"-esque "delete" button next to each item, to remove it from the List<Appointment> and the view.

Here is a sample (drawn in MSPaint - old-school tools ftw!) of what I imagine the view to look like:

My desired view design

Things I have tried
I have been digging far and wide, but nothing has come to my rescue, as yet.

  • I have looked at many solutions to do with adding the objects in JavaScript to the page - which does work. Only problem is I can't pick them up in the object that gets posted to the controller.

  • I have also been pointed in the direct of using Ajax to create an Appointment object and insert it into the page, but this didn't seem to get picked up, either.

Your expertise, knowledge and wisdom is much appreciated.

Geoff James
  • 3,122
  • 1
  • 17
  • 36
  • Refer the answers [here](http://stackoverflow.com/questions/29161481/post-a-form-array-without-successful/29161796#29161796) and [here](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) for some options –  Jun 21 '16 at 23:02
  • Thanks @StephenMuecke - it would appear you've had a bit of experience with this type of thing before? I've had a quick read through the articles you linked - 2nd link seems to be more toward what I'm after. I'll have a proper look in more detail when time allows. In the meantime, is it possible you might be able to provide me with some example code for my scenario, please (I understand that might be a *big* ask, though)? – Geoff James Jun 21 '16 at 23:14
  • 2
    Have a look at [this DotNetFiddle](https://dotnetfiddle.net/UjxtUW) for an example (and your `Appointment` model should be `DateTime Day`, `TimeSpan From` and `TimeSpan To` - not `string`) –  Jun 21 '16 at 23:23
  • I **like** that .Net Fiddle! I'll have a play around and see what I get. FYI, I'd purposely created the `Day`, `From` and `To` properties as `string` for some other reasons *TL;DR - don't panic, I'm not that much of a noobie* :) – Geoff James Jun 21 '16 at 23:28
  • @StephenMuecke - you need to go and reward yourself with a refreshing beverage, my friend! I've managed to adapt the Fiddle you posted to tailor to my needs. If you want to go ahead and post an answer a little more elaborate than in the comments, I'll mark it as accepted :) – Geoff James Jun 22 '16 at 21:42

1 Answers1

1
  1. To be able to add new Appointments input controls on the UI dynamically, on click of Add button, you can

[code below is from the top of my head, and more like pseudo-code]

a) use jQuery/js to clone a hidden div that you have pre-rendered on the view which contains all the relevant appointment controls.

e.g. this would be a pre-rendered div which will serve as a template

<div style="display:none;" id="appointment-template">
    <input type=text>
</div>

And then the jQuery clone function on click of Add would be something like

var addNewAppointmentRowFromTemplate = function(){
        $("#appointments-list-container").append($("#appointment-template").clone());
    }

OR

b) Get the partial view from the server and append it to the form (again using jQuery $.get / js xhr)

 $.get( "controller/templatDivActionMethodUrl", function( data ) {
      $("#appointments-list-container").append( data );
    });
  1. To be able to send the model with List in Item that is accepted in ActionMethod's Item parameter, you can

a) Dynamically create a json object using jquery/javascript that has the same structure as the server side model (maintainability will be an issue when model is changed).

    var appointmentsJson = [];
    //foreach loop is a pseudo code, can be replaced with $.each()
    foreach (var appointment in $("#appointments-list-container").children()){
            appointmentsJson.push(new {'day' : $(appointment).children("#day").val(),'from' : $(appointment).children("#from").val(),'to' : $(appointment).children("#to").val()})  ;}

    //and then post this json to actionMethod

    $.post("serverController/Actionmethod",{appointments: appointmentsJson});

OR

b) Use the ModelBinder feature that comes with ASP.Net MVC. There's good amount of documentation/tutorials on this our there, a starting point: https://docs.asp.net/en/latest/mvc/models/model-binding.html

Saharsh
  • 750
  • 7
  • 18
  • Thanks for your suggestions. I'll have a proper look at them in anger, when I get more time. I've tried appending partial views to the form, before. Unfortunately MVC's ModelBinder wouldn't pick up on it - something to do with the indexing? (as pointed out on the links from @stephen-muecke) - hence me coming to the good people of SO for some help – Geoff James Jun 21 '16 at 23:33
  • ok cool.,...yes a hidden field with a sequential index is required for CustomModelBinder to work. – Saharsh Jun 21 '16 at 23:40
  • Now I've got a clear head on things, I've looked over your answer again. Exactly right (which I wasn't doubting for a second). @Stephen Muecke's https://dotnetfiddle.net/UjxtUW link gave me just the right push in the right direction though, so I've asked him to post an answer - as I see, it's only fair. If he chooses not to, and you can add some sample code to your answer (for future-comers more than anything) - I'll consider marking this as accepted. Thanks again :) – Geoff James Jun 22 '16 at 21:50
  • ok, sounds fair. I've added some code from the top of my head. – Saharsh Jun 23 '16 at 22:45