0
votes

While creating a custom plugin, that Ajaxifies any forms that are not already Ajaxified by unobtrusive Ajax, I noticed that my partial/layout code failed to display on post-back with a normal Unobtrusive Ajax form:

For my other partial-update plugins, I conditionally render pages as partial (for ajax requests) or full with layout (for normal requests) like this:

    public ActionResult AjaxForm()
    {
        if (!Request.IsAjaxRequest())
        {
            ViewBag.Layout = "_Layout.cshtml";
        }
        return PartialView();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult AjaxForm(AjaxFormModel model)
    {
        if (!Request.IsAjaxRequest())
        {
            ViewBag.Layout = "_Layout.cshtml";
        }

        if (ModelState.IsValid)
        {
            return PartialView("Success", model);
        }

        return PartialView(model);
    }

When it came to testing a normal unobtrusive Ajax form, I used this test view, which updates just its own element UpdateTargetId="FormPanel":

@using (Ajax.BeginForm("AjaxForm", null, new AjaxOptions() { UpdateTargetId = "FormPanel" }, new { id = "FormPanel" }))
{
    @Html.AntiForgeryToken()

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

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

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

If I remove the test if (!Request.IsAjaxRequest()), and return the full page with master page/layout, it renders the result just fine where the form was.

Fiddler shows the "success" view is returned in either case, but it does not do anything without the layout present.

Q. What is it in a masterpage/layout that unobtrusive Ajax requires in order to extract content and replace the specified element

Update:

To make things worse it actually works correctly the first time, but not if I ajax load the same form again.

1
Generally the layout page will have the script tags to load the relevant libraries whereas the partial page does not have these and so no JavaScript is loaded.Nigel Ellis
@Nigel Ellis: Yes, but this is an Ajax update and the scripts are already loaded. Finding out more clues at the moment.Gone Coding
I don't think there's enough information yet; We'd need to see how your script is changing the behavior of the form to be more conclusive.Brad Christie
@Brad Christie: Spot on.. Just found the cause and and it was obscure (answer coming up).Gone Coding
The problem is not what I thought it was, but the answer (below) is very useful and worth knowing. Thanks for all suggestions :)Gone Coding

1 Answers

0
votes

A: Forms must have unique IDs!

There are loads of issues about unique IDs on SO, which browsers generally cope with, but the one you cannot ever break is having two forms loaded with the same id. Only one is visible to JavaScript/jQuery!

My problem was caused by a duplicate form ID. You should not insert the result page back in to the form itself (leaving the form element intact).

When I reloaded the original form, dynamically in a slideshow-style control, I wound up with duplicate form ID's.

This meant Unobtrusive Ajax could only see the first form in the DOM!

Solution (do not set Ajax forms to update themselves):

Set UpdateTargetId to an element above the form, never the form itself if you load panels dynamically.

Fix to my example view (surround form with a target panel):

<div id="FormPanel">
    @using (Ajax.BeginForm("AjaxForm", new AjaxOptions() { UpdateTargetId = "FormPanel" }))