Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 5 with Bootstrap Modal from partial view validation not working?

I've been working on this off and on for a couple days now and can't quite get it to work as I would expect. I've looked through many examples but I must be misunderstanding something.

What I have is a Bootstrap Modal that get's loaded with a partial view. What I'm trying to do is have validation (hopefully client side) with any errors being dispalyed in the @ValidationSummary in the Modal. The biggest issue I'm having is that when there is an error, instead of showing in the original modal it basically loads the modal partial view in a new page. The validation summary is populated correctly but it doesn't have any styling plus it isn't in the modal at that point.

*Note: I'm not using AJAX to load the modal at the moment. I will eventually get that but I'm not as comfortable with AJAX yet and i want to get it working first, then I can come back and refactor in AJAX.

_Layout.cshtml - I found an example that said I needed to load the JS.unobtrusive when loading the Modal. I'm assuming I'm doing that correctly but I might be missing something there.

<div id="modal-container" class="modal fade">
    <div class="modal-dialog">
        <div class="modal-content">
        </div>
    </div>
</div>


@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)


<script type="text/javascript">

    //****  Bootstrap Modal - used with Partial Views  ***********************
    $(function () {
        // Initalize modal dialog
        // attach modal-container bootstrap attributes to links with .modal-link class.
        // when a link is clicked with these attributes, bootstrap will display the href content in a modal dialog.
        $('body').on('click', '.modal-link', function (e) {
            e.preventDefault();
            $(this).attr('data-target', '#modal-container');
            $(this).attr('data-toggle', 'modal');

            //load the unobtrusive JS code
            $jQval.unobtrusive.parse($modal-container); 

            var $form = $modal-container.find("form");
            $.validator.unobtrusive.parse($form);

        });

        // Attach listener to .modal-close-btn's so that when the button is pressed the modal dialog disappears
        $('body').on('click', '.modal-close-btn', function () {
            $('#modal-container').modal('hide');
        });

        //clear modal cache, so that new content can be loaded
        $('#modal-container').on('hidden.bs.modal', function () {
            $(this).removeData('bs.modal');
        });

        $('#CancelModal').on('click', function () {
            return false;
        });
    });

_PasswordReset.cshtml

div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
    <h4>Reset your Password</h4>
</div>
<div class="modal-body">
    <div class="form-horizontal">
        @using (Html.BeginForm("PasswordReset", "Member", FormMethod.Post))
        {
            @Html.AntiForgeryToken()
            @Html.ValidationSummary(false, "", new { @class = "alert alert-danger" })

            <!-- BEGIN HIDDEN FIELDS AREA -->
            @Html.HiddenFor(model => model.MemberId)
            <!-- END HIDDEN FIELDS AREA -->

            <div class="form-group">
                <label class="control-label col-xs-3">Password</label>
                <div class="col-md-9">
                    @Html.TextBoxFor(c => c.Password, new { Class = "form-control", placeholder = "New Password", autofocus = "" })
                </div>
            </div>
            <div class="form-group">
                <label class="control-label col-xs-3">Confirm</label>
                <div class="col-md-9">
                    @Html.TextBoxFor(c => c.PasswordConfirm, new { Class = "form-control", placeholder = "Confirm Password" })
                </div>
            </div>
            <div class="form-group">
                <div class="col-xs-offset-3 col-xs-9">
                    <button type="submit" id="approve-btn" class="btn btn-primary">
                        Reset
                    </button>&nbsp;&nbsp;

                    <input type="button" class="btn btn-default" value="Cancel" data-dismiss="modal" />
                </div>
            </div>
        }
    </div>
</div>

Controller - I'm assuming that if the model is not valid then I'm supposed to pass back a PartialView since that is what I loaded into the Modal initially?

public ActionResult PasswordReset(MemberViewModel vm)
{
    MemberPasswordReset model = new MemberPasswordReset();
    model.MemberId = vm.MemberId;

    return PartialView("_PasswordReset", model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async System.Threading.Tasks.Task<ActionResult> PasswordReset(MemberPasswordReset model)
{
    if (!ModelState.IsValid)
    {
        return PartialView("_PasswordReset", model);
    }

    ApplicationDbContext context = new ApplicationDbContext();
    UserStore<ApplicationUser> userStore = new UserStore<ApplicationUser>(context);
    UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(userStore);

    String userId = User.Identity.GetUserId();
    String hashedNewPassword = UserManager.PasswordHasher.HashPassword(model.Password);

    ApplicationUser currentUser = await userStore.FindByIdAsync(userId);
    await userStore.SetPasswordHashAsync(currentUser, hashedNewPassword);
    await userStore.UpdateAsync(currentUser);

    return RedirectToAction("MyAccount");           
}

ViewModel

public class MemberPasswordReset
{
    public string Password { get; set; }

    [Compare("Password", ErrorMessage = "Confirm password doesn't match.")]
    public string PasswordConfirm { get; set; }

    public int MemberId { get; set; } 
}

Bundle.config

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js",
            "~/Scripts/jquery.validate.js",
            "~/Scripts/jquery.validate.unobtrusive.js"));
like image 832
Caverman Avatar asked Oct 18 '22 01:10

Caverman


1 Answers

You cannot pass back the partial view as that returns the partial view only - not the view + the loaded partial view inside the modal. So what your seeing currently is correct.

You'll need to pass back a JSON response using AJAX in order for it to validate within the modal's partial view.

If the model state isn't valid, return the key-value pairs of the model-state. A good starting example is here: Example

Once that is sent back to your view, you can use jquery to append the model errors (if any) onto the validation summary area.

EDIT:

Example as requested -

Controller

public class HomeController : BaseController
{
    [HttpPost]
    public ActionResult Edit(EditViewModel vm)
    {
        if(ModelState.IsValid)
        {
             //do stuff
             return Json(new
             {
                 status = "success"
                 //return values if needed
             }
        }
        return Json(new
        {
            status = "failure",
            formErrors = ModelState.Select(kvp => new { key = kvp.Key, errors = kvp.Value.Errors.Select(e => e.ErrorMessage)})});
        }
    }
}

View

@using (Ajax.BeginForm("Edit", new AjaxOptions { OnSuccess = "onChangeSuccess"}))
{
    //Modal header, body, footer
    //Make sure your form fields actually contain their Razor validation fields
}

JQuery

function onChangeSuccess(data) {

    if (data.status === "success") {
        $("#modalcontent").modal('hide');
        $("#message-area").html(data.view);
    }
    $.each(data.formErrors, function() {
        $("[data-valmsg-for=" + this.key + "]").html(this.errors.join());
    });
}
like image 180
Ben Sampica Avatar answered Nov 15 '22 13:11

Ben Sampica