1

I am creating a scheduling MVC app and I have 3 CodeFirst EF6 classes in a many-to-many configuration with the middle table containing additional information, such as time, date etc. of the meeting between the host and their visitors. I have created a ViewModel to allow me to create hosts, visitors and meetings on a single page instead of having to go to each page separately to create the required resources for the meeting. The controller and view for this arrangement are generating some questions.

I am passing, using ViewBag, a SelectList to the view

ViewBag.HostID = new SelectList(db.Hosts.OrderBy(x => x.Name), "id", "Name");

and

ViewBag.VisitorID = new SelectList(db.Visitors.OrderBy(x => x.LastName).
ThenBy(y => y.FirstName), "id", "VisitorName");

which is being successfully rendered in my Razor view using

    <div class="form-group">
        @Html.LabelFor(model => model.HostID, "HostID", htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("HostID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.HostID, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.VisitorID, "VisitorID", htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("VisitorID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.VisitorID, "", new { @class = "text-danger" })
        </div>
    </div>

However, I'd like to be able to select a row in the DropDownList that has no associated HostID or VisitorID such that I can then enter a new Host or Visitor and have my controller create the necessary records when the form is posted back.

Therefore, my question is how do I add a non-database field to the top of the SelectList which will signify to the controller (once posted back) that a new record should be created instead of a reference to an existing Host be used?

Jason James
  • 1,082
  • 1
  • 14
  • 27
  • After creating the `SelectList`, use `.ToList()` on the resulting `IEnumerable` and use `Insert()` to per-pend a new `SelectListItem` But its not clear how you would intend to use this - You don't know the any information about the `Host` or `Visitor` from the posted data so what would you insert in the database? –  Jul 16 '16 at 08:29
  • There are additional form elements in the Razor file that would require populating if a visitor not in the list was required. – Jason James Jul 16 '16 at 11:24
  • Then you can just do it as per the first comment (or `ViewBag.HostID = new List –  Jul 16 '16 at 11:30
  • I'd also been thinking along similar lines but that seemed more out of reach than my suggestion. Do you have any suggestions or resources about how to achieve what you are recommending or should I post another question? – Jason James Jul 16 '16 at 11:38

1 Answers1

1

You can create your SelectList using

ViewBag.HostID = new List<SelectListItem>()
{ 
    new SelectListItem()
    {
        Value = "-1", // note, do not make this "0" unless you make HostID nullable
        Text = "New Host"
    }
}.Concat(db.Hosts.OrderBy(x => x.Name).Select(x => new SelectListItem()
{
    Value = x.id.ToString(),
     Text = x.Name
}));

Note that you should not be naming your ViewBag property the same name as the property your binding to (refer this answer) and you should be using view model with an IEnumerable<SelectListItem> HostList so that in the view you strongly bind to your view model using

@Html.DropDownListFor(m => m.HostID, Model.HostList, "-Please select-", new { @class = "form-control" })

However a better approach to solve the problem of adding a new option to the <select> would be to include a 'Add new host' button that displays a modal form to create the Host and use ajax to submit it, and if successful, append the new option to the DOM. The outline code would be

@using (Html.BeginForm())
{
    ....
    @Html.DropDownListFor(m => m.HostID, Model.HostList, "-Please select-", new { @class = "form-control" })
    <button type="button" id="addhost">Add new Host</button>
    ....
}
<div id="hostmodal"></div>

$('#addhost').click(function() {
    $('#hostmodal').load('@Url.Action("Create", "Host"));
    // reparse the validator
});

where the Create() method of HostController returns a partial view of a form for creating a new Host (refer this answer for reparsing the validator)

Then handle the .submit() event of the 'Create Host' form, cancel it and submit the values using ajax to a method which returns the new ID of the Host in a JsonResult(). Using the returned ID and Name value from the form, you can append a new <option> element and select it.

$('#hostmodal').on('submit', '#hostform', function() {
    var data = $(this).serialize();
    var url = '@Url.Action("Create", "Host")';
    var text = ... // a value from the form that you want as the option text
    $.post(url, data, function(response) {
        if (response) {
            $('#HostID').append($('<option></option>').val(response).text(text)).val(response);
        } else {
            // Oops
        }
    }).fail(function() {
        // Oops
    });
    // ... close the modal
    return false; // cancel the default submit
});

Note, If you want to have the new Host added in sort order, refer this answer.

and the POST method would be something like

[HttpPost]
public JsonResult Create(HostViewModel model)
{
    // Initialize a new Host data model and set its properties based on the view model
    var host = new Host()
    {
        Name = model.Name,
        ....
    }
    // Save it
    db.Hosts.Add(host); 
    db.SaveChanges();
    // Return its ID
    return Json(host.ID); // or return Json(null); if error
}
Community
  • 1
  • 1
  • Many thanks. I think I follow it all. Give me some time to implement it and work out any details I need to understand and I'll come back. – Jason James Jul 16 '16 at 13:11
  • I think I'm close, but no cigar. I can display the fields when clicking "Add New Visitor" but when I click "Save Visitor" the jQuery doesn't execute. I looks like the script isn't registered. I'll start a new questions and post a link here shortly. – Jason James Jul 17 '16 at 05:07
  • Related question asked here - http://stackoverflow.com/questions/38418163/using-jquery-and-partial-views-to-add-a-new-dropdownlist-item-in-mvc – Jason James Jul 17 '16 at 05:23