0

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.

Community
  • 1
  • 1
steveareeno
  • 1,925
  • 5
  • 39
  • 59

2 Answers2

2

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;
});
steveareeno
  • 1,925
  • 5
  • 39
  • 59
0

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);
                    }
                }
Slicksim
  • 7,054
  • 28
  • 32
  • Thanks for the quick reply! Your suggestion got rid of the duplication, but I was using unobtrusive validation on the form (noted by the read text in the image) which this seems to bypass. I prefer to stick with the validation form if this is possible, instead of listing the errors from the controller. – steveareeno Aug 29 '14 at 16:24
  • I managed to get it to work. What I did was wrap my Html.BeginForm() in a separate div and cleared the div in the success function. I haven't put it as the answer yet because I need to fully test it. – steveareeno Aug 29 '14 at 16:30