steveareeno
steveareeno

Reputation: 1977

mvc submit form partialview in jquery modal duplicating form

I followed Darin Dimitrov's example to submit a form (with validation) in a modal dialog:

Using Ajax.BeginForm with ASP.NET MVC 3 Razor

It works perfectly with on exception. When I submit the form with intentional errors, I end up with two copies of the form in the dialog:

duplicate forms

Here is my partial view:

@model MvcAppTemplate.ViewModels.SupportClass1ViewModel
<script src="~/Scripts/Jquery/jquery.validate.min.js"></script>
<script src="~/Scripts/Jquery/jquery.validate.unobtrusive.js"></script>
<script>
    $(document).ready(function () {
        $('#SupportClass1Name').focus();

        $('form').submit(function () {
            if ($(this).valid()) {
                $.ajax({
                    url: this.action,
                    type: this.method,
                    data: $(this).serialize(),
                    success: function (result) {
                        $('#result').html(result);
                    }
                });
            }
            return false;
        });
    });
</script>
<div id="result"></div>
@using (Html.BeginForm("CreateDialog", "SupportClass1", FormMethod.Post, new { @class = "form-horizontal" }))
{
    @Html.AntiForgeryToken() 
    @Html.ValidationSummary(true, "The following errors occurred:", new { style = "color: red" })
    <fieldset>
        <legend>MyMainClass1</legend>
        @Html.ValidationMessage("CustomError", new { @class = "error" })
        @Html.HiddenFor(model => model.IsNew)
        <div class="form-group">
            <div class="col-lg-3 control-label">
                @Html.LabelFor(model => model.SupportClass1Name)
            </div>
            <div class="col-lg-6">
                @Html.TextBoxFor(model => model.SupportClass1Name, new { style = "width: 400px;", @maxlength = "50" })
                @Html.ValidationMessageFor(model => model.SupportClass1Name)
            </div>
        </div>
        <div class="form-group">
            <div class="col-lg-3 control-label">
                @Html.LabelFor(model => model.Active)
            </div>
            <div class="col-lg-6">
                @Html.EditorFor(model => model.Active, new { style = "width: 150px;" })
                @Html.ValidationMessageFor(model => model.Active)
            </div>
        </div>
        <p>
            <input type="submit" value="Create" class="btn btn-primary" />
            @Html.ActionLink("Cancel", "Search", "SupportClass1", null, new { @class = "btn btn-default" })
        </p>
    </fieldset>
}

The view I call the modal from:

@model MvcAppTemplate.ViewModels.SupportClass1ViewModel

@{
    ViewBag.Title = "Test";
}
<link href="~/Scripts/jquery-ui-1.11.1.custom/jquery-ui.min.css" rel="stylesheet" />
<link href="~/Content/dataTables.bootstrap.css" rel="stylesheet" />
<script src="~/Scripts/jquery-ui-1.11.1.custom/jquery-ui.min.js"></script>
<script type="text/javascript">
    $(document).ready(function () {
        $('#dialog').dialog({
            autoOpen: false,
            width: 600,
            height: 400,
            resizable: true,
            title: 'Support Class 1',
            modal: true,
            open: function (event, ui) {
                $(this).load("@Url.Action("CreateDialog", "SupportClass1")");
             },
             buttons: {
                 "Close": function () {
                     $(this).dialog("close");
                 }
             }
         });
        $("#opener").click(function () {
            $("#dialog").dialog("open");
        });
    });
</script>

<div id="dialog" title="Create" >Please wait</div>
<button id="opener">Show Class</button>

And finally my controller:

// create in a pop up dialog
public ActionResult CreateDialog()
{
    var lvm = new SupportClass1ViewModel
    {
        IsNew = true,
    };
    return PartialView("_CreateDialog",lvm);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateDialog(SupportClass1ViewModel lvm)
{
    SupportClass1 supportClass1 = new SupportClass1();
    // Use Injector to handle mapping between viewmodel and model
    supportClass1.InjectFrom(lvm);

    try
    {
        if (ModelState.IsValid)
        {
            supportClass1Service.CreateSupportClass1(supportClass1);
            // redirect to the myMainClass1 view
            //return RedirectToAction("Details", "SupportClass1", new { id = supportClass1.SupportClass1Id });
            return Content("Thanks", "text/html");

        }
    }
    catch (DataException de)
    {
        //Log the error (add a variable name after DataException)
        var s = de.InnerException.ToString();

        ModelState.AddModelError("CustomError", "Unable to save changes. Try again, and if the problem persists, see your system administrator. Error: " + s);
    }
    // rehydrate the view
    lvm.IsNew = true;
    //return Content("Thanks", "text/html");
    return PartialView("_CreateDialog", lvm);

It appears the partial view is loaded twice when there is an error: once in the result div and once from the original @using Html.BeginForm(). I verified this by putting a visible border around the result div.

Any help is appreciated.

Upvotes: 0

Views: 7859

Answers (2)

steveareeno
steveareeno

Reputation: 1977

I figured out a fix. As I said in my comment, I wrapped my partial view's form in a div:

<div id="myForm">
@using (Html.BeginForm("CreateDialog", "SupportClass1", FormMethod.Post, new { @class = "form-horizontal" }))
{
some content...
}
</div>

Then, in my jquery form submit function, I cleared the div before repopulating it with the partial view form the controller (the one with the validation errors:

$('form').submit(function () {
    if ($(this).valid()) {
        $.ajax({
            url: this.action,
            type: this.method,
            data: $(this).serialize(),
            success: function (result) {
                $('#myForm').html('');
                $('#result').html(result);
            }
        });
    }
    return false;
});

Upvotes: 2

Slicksim
Slicksim

Reputation: 7172

this line is the issue:

success: function (result) {
                    $('#result').html(result);
                }

This handler is being called in both success and fail scenarios, so you end up with the form displayed twice, once from your original render, and then inside the result div when error happens.

Change your controller code to:

 try
{
    if (ModelState.IsValid)
    {
        supportClass1Service.CreateSupportClass1(supportClass1);
        // redirect to the myMainClass1 view
        //return RedirectToAction("Details", "SupportClass1", new { id = supportClass1.SupportClass1Id });
        return Json(new {success = true, message ="Thanks" } );

    }
}
catch (DataException de)
{
    //Log the error (add a variable name after DataException)
    var s = de.InnerException.ToString();

    ModelState.AddModelError("CustomError", "Unable to save changes. Try again, and if the problem persists, see your system administrator. Error: " + s);
}

return Json(new {success = "false", message = "Error"}); // do a concatenation of the model state errors

Then your success handler can look like

 success: function (result) {
                    if(result.success) {
                        $('#result').html(result.message);
                    }
                    else {
                        // provide some highlighting or what have you or just set the message
                        $('#result').addClass("error").html(result.message);
                    }
                }

Upvotes: 0

Related Questions