26

I am really struggling with knowing how to put this all together. I've built forms inside .net MVC pages plenty of times, with and without validation. And I've built forms using jQuery, with and without validation. And I've built forms inside of a modal, but never with MVC.

I understand from my original question that because this form is inside a modal that I'll need to use jQuery to handle the submit. I am having a heck of a time figuring out how to put all these moving pieces together. So far, I've not found a tutorial (or combo of tutorials) that puts this all together.

Here is what I need:

  • Inside of my MVC view there is a button that opens a modal. (This works fine.)
  • Once the modal opens, it contains a form with several text fields and dropdowns. Each is required. (To make the fields required, would I define these in the view's model, as I normally would with an MVC form? Or do I create the requirements in the jQuery?)
  • If the user attempts to submit the form and they are empty or invalid, the modal stays open and the validation messages appear. (I understand, from my original question, that this is not possible using straight MVC because of the modal, and that some jQuery is needed. I'm lost here.)
  • If the user attempts to submit the form and all fields are valid, then the modal closes, we hit the controller and save the fields to the db. (Not sure how to escape out of the jQuery and just hit the normal controller to handle the final logic.)

EDIT:

Thank you, Jason, for your help! Based on your suggestions, this is how I got it working.

The Parent View:

The modal:

<div class="modal fade" id="AccountEditModal" tabindex="-1" role="dialog" aria-labelledby="AccountEditModalLabel">
    <div class="modal-dialog modalAccountEdit" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h3><strong>Edit Account Profile - <span class="accountname"></span></strong></h3>
            </div>

            <div class="modal-body">
                <div id="formContent">
                    @Html.Partial("_AccountProfileEdit", new GEDCPatientPortal.Models.AccountProfileEditViewModel())
                </div>
            </div>
        </div>
    </div>
</div>

And then here's the scripts:

@section Scripts {
    <script>
        $(document).ready(function () {

            $('#AccountEditModal').on('shown.bs.modal', function () {
                $('#myInput').focus()
            })



        $("#AccountEditModal").on("submit", "#form-accountprofileedit", function (e) {
            e.preventDefault();  // prevent standard form submission

            var form = $(this);
            $.ajax({
                url: form.attr("action"),
                method: form.attr("method"),  // post
                data: form.serialize(),
                success: function (partialResult) {
                    $("#formContent").html(partialResult);
                }
            });
        });


        });

    </script>
}

The Partial View (slimmed down):

@using (Html.BeginForm("AccountProfileEdit", "Account", FormMethod.Post, new { id = "form-accountprofileedit", @class = "full-form" }))
    {


    @Html.CustomTextboxFor(model => model.Address)


    <div style="text-align:right;">
        <button type="submit" id="accountprofileedit-submit" name="accountprofileedit-submit" value="Edit Account" class="btn btn-primary" style="margin-left:5px;">Edit Account</button>
        <button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
    </div>
}

The Controller:

    [HttpPost]
    public ActionResult AccountProfileEdit(AccountProfileEditViewModel model)
    {
        if (ModelState.IsValid)
        {
            // logic to store form data in DB
        }

        return PartialView("_AccountProfileEdit", model);

    }
0

3 Answers 3

41

You can use the built-in MVC validation scripts along with the data annotaions on your model

public class AccountProfileEditViewModel
{
    [Display(Name = "Address")]
    [Required()]
    [StringLength(200)]
    public string Address { get; set; }
}

Make a partial view to hold your modal form.

_AccountProfileEdit.cshtml

@model AccountProfileEditViewModel

@using(Html.BeginForm("AccountProfileEdit", "Account",
           FormMethod.Post, new { id = "form-accountedit-appt" }) {
    @Html.ValidationSummary(true)

    @Html.LabelFor(m => m.Address)
    @Html.TextBoxFor(m => m.Address)
    @Html.ValidationMessageFor(m => m.Address)
    <button type="submit">Edit</button>
}

Then reference this in your modal box. If you want pre-populated model you'll need to render an action:

<div class="modal-body" id="form-container">
    @Html.Action("AccountProfileEdit", "Account", new { id=account.Id })
</div>

If you just want a blank form then you can just use:

<div class="modal-body" id="form-container">
    @Html.Partial("_AccountProfileEdit")
</div>

The action uses the id parameter to fetch and populate the model

[HttpGet]
public ActionResult AccountProfileEdit(int id)
{
    AccountProfileEditViewModel model = db.GetAccount(id);  // however you do this in your app

    return PartialView("_AccountProfileEdit", model);
}

AJAX POST

Now you'll need AJAX to submit this form. If you rely on a standard form submission the browser will navigate away from your page (and close your modal).

$("#myModal").on("submit", "#form-accountedit", function(e) {
    e.preventDefault();  // prevent standard form submission

    var form = $(this);
    $.ajax({
        url: form.attr("action"),
        method: form.attr("method"),  // post
        data: form.serialize(),
        success: function(partialResult) {
            $("#form-container").html(partialResult);
        }
    });
});

You need to use the event delegate $(staticParent).on(event, target, handler) for the submit event because the form content may be replaced later.

Post Action

[HttpPost]
public ActionResult AccountProfileEdit(AccountProfileEditViewModel model)
{
    // Request.Form is model

    if (ModelState.IsValid)
    {
        // do work
        return PartialView("_AccountEditSuccess");
    }

    return PartialView("_AccountProfileEdit", model);
}

Client-side validation scripts should prevent them from ever submitting. But if that somehow failed or if you can't validate something on the client then you have ModelState.IsValid. You might also invalidate something server-side manually.

_AccountEditSuccess.cshtml

And the "success" partial view.

<div>Success! <button>Click to close</button></div>

Not Valid is a Fail, Right?

From your AJAX success handler you have

success: function(partialResult) {
    $("#form-container").html(partialResult);
}

But the problem here is we don't know if you are getting a "success" or "validation failure". Adding an error: function(err){ } handler won't help because the validation failure is considered a HTTP 200 response. In both cases the div content is replaced the user will need to manually close the modal. There are ways to pass additional data to distinguish both conditions but that's another long answer post.

Sign up to request clarification or add additional context in comments.

11 Comments

Jason, first, THANK YOU! I really appreciate the time and help. Second, sorry it took me so long to reply. I've been on vacation. But this is still very much an issue that I need to resolve. And third, I made an edit to my OP based on your suggestions. I am sure I've missed something simple, but it's still not working.
Navigation when you submit often means you haven't properly prevented the default submission. Make sure there aren't any script errors. Move your submit handler inside of $(document).ready(). As a sanity check, remove the ajax call, you want to be able to press the submit button without anything happening.
Bam! Moving the script inside $(document).ready() was the kicker! Thank you SO much!
What if I want the modal to go away after the form is submitted assuming there were no validation issues? I used the code like you have and it works other than it's loading the parent page in to the modal because of the success funtion(partialResult) loading the the html into my modal's div tag. I'm trying to keep the user in the modal if there are any validation issues but update the database, close the modal, and go to the parent where I check the TempData to see if I need to display a success or failed message.
It sounds like you are loading a full page view rather than a partial view into the modal. You should create a new question with your code -- it will be easier to see what isn't working for your example.
|
3

Consider put an iframe inside the modal div, instead of rendering partialView, this way you can develop the modal section the same way you develop simple pages, which submit, model, required etc...

this way:

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modalAE" role="document">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h3><strong>Edit Account Profile - <span class="accountname"></span></strong></h3>
        </div>
            <div class="modal-body">
          <iframe src='myApp/AccountProfileEdit'/>
        </div>
        <div class="modal-footer">
            <button type="submit" id="accountprofileedit-submit" name="accountprofileedit-submit" value="Edit Account" class="btn btn-primary" style="margin-left:5px;">Edit Account</button>
            <button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
        </div>
        }
    </div>
</div>

1 Comment

This is a good solution. I'm certain it triggers some instinct in me to not use iframes this way, but it the natural way it fits into the MVC framework makes it compelling.
1

I construct MVC Bootstrap modal forms with jQuery.validate() in 2 views, the "main" view that holds the modal div and the Html.BeginForm(...) and a partial view for the modal body that has the form elements, with a separate .js file that holds a jQuery method for opening the modal, binding the partial view and its form validation.

It starts with a CSS class on a link on the main view that calls an ActionResult() that returns the partial view, editChangeReason:

<a href="@Url.Action("_editCarrierChangeReason" ...)" class="editChangeReason">Add Exception</a>

From the controller:

public ActionResult _editCarrierChangeReason(string reasonId)
{
     ...
     return PartialView("modals/_modalEditCarrierChangeReason", rule);
}

On that main view, there is a typical Bootstrap modal div with the addition of the @Html.BeginForm(...):

<!-- START Carrier Change Reason/Exception Edit Modal -->
    <div id="editReason" class="modal fade in" data-backdrop="static" role="dialog">
        <div class="modal-dialog modal-lg" role="document">
            <div class="modal-content">
                @using (Html.BeginForm("_editCarrierChangeReason", "TmsAdmin", new { area = "Tms" }, FormMethod.Post, new { id = "formCarrierChangeReason", autocomplete = "off" }))
                {
                    <div id="editReasonContent">
                    </div>
                }
            </div>
        </div>
    </div>
<!-- END Carrier Change Reason/Exception Edit Modal -->

Then, it's a simple partial view for the modal body that holds the form elements:

@Html.HiddenFor(m => m.TypeId)

<!-- START Modal Body -->
<div class="modal-body">

    <!-- START Carrier Exception Edit Form -->
    <div class="form-row">
        <div class="form-group col-6">
            <label class="control-label">Code</label> <span class="required">*</span>
            @Html.TextBoxFor(m => Model.Code, ...)
        </div>
    </div>
    <div class="form-row">
        <div class="form-group col">
            <label class="control-label">Description</label> <span class="required">*</span>
            @Html.TextAreaFor(m => Model.Description, ...)
        </div>
    </div>
    <!-- END Carrier Exception Edit Form -->
    ...
</div>
<!-- END Modal Body -->

For the .js file, that's a little more involved but has a logical flow. Picking up the button click from the class, editChangeReason, the modal is opened. There is also a switch, outside the $(document).ready() that handles the Bootstrap classes that manage the modal and binds the partial view to the "content" div:

$(document).ready(function() {
// Carrier Change Reason/Exception
        $("body").on("click", ".editChangeReason", function (e) {
            e.preventDefault();
            $("#editReasonContent").load(this.href,
                function() {
                    $("#editReason").modal({
                            keyboard: true
                        },
                        "show");
                    bindForm(this, "editChangeReason");
                    return;
                });
            return false;
        });   
});


// Allows modals to use partial views when using <a data-modal=""...></a> on parent View:
        function bindForm(dialog, type) {
            $("form", dialog).submit(function () {
                $.ajax({
                    url: this.action,
                    type: this.method,
                    data: $(this).serialize(),
                    success: function(result) {
                        switch (type) {
                        case "editChangeReason":
                            if (result.success) {
                                $("#editReason").modal("hide");
                                location.reload();
                            } else {
                                $("#editReasonContent").html(result);
                                bindForm();
                            }
                            return;
                        default:
                            break;
                        }
                    }
                });
                return false;
            });
        }

And finally, the validation, also outside the $(document).ready():

$("#formCarrierChangeReason").ready(function() {
    $("#formCarrierChangeReason").validate({
        rules: {
            Code: {required: true},
            Description: {required: true, minlength: 10}
        },
        messages: {
            Code: "Please enter a unique code no longer than 10 characters",
            Description: "Please add a description that explains this reason/exception"
        }
    });
});

enter image description here

1 Comment

Reviving the dead! I asked this question 5 years and 2 jobs ago :) I upvoted your answer just for the sheer amount of effort you put into it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.