Using jQuery and Partial Views to Add A New DropDo

2019-06-13 01:11发布

I am attempting to create a scheduling MVC app. In a ViewModel I combine the fields (from CodeFirst EF fields) to include the meeting hosts, visitors and times, etc. I want to be able to create a new visitor from this form if they don't already exist and postback via Ajax to update the list of available visitors.

ViewModel:

public class AppointmentViewModel
{
    public int HostID{ get; set; }
    public IEnumerable<SelectListItem> HostList{ get; set; }
    public int? VisitorID { get; set; }
    public IEnumerable<SelectListItem> VisitorList { get; set; }
    public string VisitorTitle { get; set; }
    public string VisitorFirstName{ get; set; }
    public string VisitorSurname { get; set; }
    public string VisitorCompany { get; set; }
    public DateTime VisitTime { get; set; }

}

Controller:

public class AppointmentController : Controller
{
    private VisitorManagerContext db = new VisitorManagerContext();

    // GET: Appointment
    public ActionResult Create()
    {
        ViewBag.HostID = new SelectList(db.Hosts.OrderBy(x => x.Name), "id", "Name");
        //ViewBag.VisitorID = new SelectList(db.Visitors.OrderBy(x => x.LastName).ThenBy(y => y.FirstName), "id", "VisitorName");
        return View(new AppointmentViewModel()
        {
            HostList = db.Hosts.OrderBy(x => x.Name).Select(y => new SelectListItem()
            {
                Value = y.id.ToString(),
                Text = y.Name
            }),
            VisitorList = db.Visitors.OrderBy(x => x.LastName).ThenBy(y => y.FirstName).Select(z => new SelectListItem()
            {
                Value = z.id.ToString(),
                Text = z.Title + " " + z.FirstName + " " + z.LastName
                })
            }
        );
    }

    [HttpPost]
    public JsonResult CreateVisitor(AppointmentViewModel model)
    {
        var visitor = new Visitor()
        {
            Title = model.VisitorTitle,
            FirstName = model.VisitorFirstName,
            LastName = model.VisitorSurname
        };
        db.Visitors.Add(visitor);
        db.SaveChanges();
        return Json(visitor.id);
    }

    public PartialViewResult Add()
    {
        return PartialView();
    }
}

View:

@model VisitorManager.Models.AppointmentViewModel
@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>AppointmentViewModel</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.HostID, "HostID", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownListFor(model => model.HostID, Model.HostList, "- Please select -", 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.DropDownListFor(model => model.VisitorID, Model.VisitorList, "- Please select -", htmlAttributes: new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.VisitorID, "", new { @class = "text-danger" })
            </div>
            <button type="button" id="addVisitor">Add new Visitor</button>
        </div>

        <div id="VisitorModal"></div>

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

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>

    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>


@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

    <script type="text/javascript">
        $('#addVisitor').click(
            function () {
                $('#VisitorModal').load('@Url.Action("Add", "Appointment")');
            }
        );


    </script>

Partial View:

@model VisitorManager.Models.AppointmentViewModel


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

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

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

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

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <input type="submit" value="Create" class="btn btn-default" id="visitorform" />
    </div>
</div>

@section Scripts {
    <script type="text/javascript">
        $('#visitorform').submit(function () {
            var data = $(this).serialize();
            var url = '@Url.Action("CreateVisitor", "Appointment")';
            var text = $('#surname').val(); // 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
        });
</script>}

I know that the styling should be improved and the new visitor form should be displayed modally, but that I hope to fix once I've got the logic working.

Clicking "Add New Visitor" displays the partial view, including the button to submit the new visitor back to the controller. Clicking the "Create" button in the partial view closes the view but the form data isn't submitted back to the controller.

Any thoughts as to why the event isn't firing?

Once the new visitor is added to the DropDownList I think need to complete the remainder of the form and submit the appointment.

1条回答
▲ chillily
2楼-- · 2019-06-13 01:50

You do not have a second form with id="visitorform". The elements in you partial need to be wrapped in <form> tags

@model VisitorManager.Models.AppointmentViewModel
<form id="visitorform">
    <div class="form-group">
        @Html.LabelFor(model => model.VisitorTitle, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.VisitorTitle, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.VisitorTitle, "", new { @class = "text-danger" })
        </div>
    </div>
    .... // other elements for VisitorFirstName, VisitorSurname etc
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</form>

Note that you need to remove id="visitorform" from the button in the partial.

You also need to move the partial from the main view's <form> tags and move it to the end (nested forms are invalid html and not supported.

@model VisitorManager.Models.AppointmentViewModel
....
@using (Html.BeginForm())
{
.... // elements associated with AppointmentViewModel only
}
// Move the partial/modal here
<div id="VisitorModal"></div>

you should also move the script in the partial to the main view (scripts should only ever be in the main view or layout) The script also need to use event delegation (.on()) because the form is being dynamically added

$('#VisitorModal').on('submit', '#visitorform', function () {
    var data = $(this).serialize();
    ....

Side note: you have a superfluous ViewBag.HostID = new SelectList(..) left over from a previous question which should be removed from the controller method.

查看更多
登录 后发表回答