Why does Ajax work correctly on the first request, but returns a partial view of the new page on the second request?

I have an Ajax form nested in each row of a table to provide an add / remove functionality. Partial list of all available roles (Microsoft identity 2.0) and for each whether the specified user is associated with that role, as well as a button to switch the user to and from the role (Ajax).

Everything works fine when I use Ajax.Beginform, however when I use regular jquery it works the first time I click the button to toggle the user association with the role, but the second time loads the partial view to the new page on its own without CSS -style.

Searching on stackoverflow, I see others with similar problems, but they don't seem to work at all on ajax, unlike me who has it working on the first request and then not afterwards. I should also mention that I am fairly new to Asp.net and this is the first time I have used jquery directly (following Scott Allan's tutorials about PluralSight).

Here's what I've already tried: Checked that the action is being executed with an ajax request with a debug breakpoint. Checked that I reference jquery in my layout and not more than once. Checked that I am referencing jquery and jquery unobtrusive ajax in correct order. Make sure the appropriate flags are set in the web.config file to true.

I've been stuck on this for hours, any guidance would be greatly appreciated.

BundleConfig.cs:

    namespace GCCP
{
    public class BundleConfig
    {
        // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
        public static void RegisterBundles(BundleCollection bundles)
        {   
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.validate*",
                        "~/Scripts/jquery.unobtrusive*"));

            bundles.Add(new ScriptBundle("~/bundles/gccp").Include(
                        "~/Scripts/gccp.js"));

            // Use the development version of Modernizr to develop with and learn from. Then, when you're
            // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                        "~/Scripts/modernizr-*"));

            bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                      "~/Scripts/bootstrap.js",
                      "~/Scripts/respond.js"));

            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/site.css"));

            // Set EnableOptimizations to false for debugging. For more information,
            // visit http://go.microsoft.com/fwlink/?LinkId=301862
            BundleTable.EnableOptimizations = true;
        }
    }
}

      

Action methods:

public ActionResult UserRoles(string returnUrl, string UserName)
{
    ViewBag.ReturnUrl = returnUrl;

    UserRolesViewModel Model = new UserRolesViewModel();
    Model.UserName = UserName;
    Model.RoleAssignments = UserRoleAssignments(UserName);

    return View(Model);
}

[HttpPost]
public ActionResult ToggleUserToRoleAssignment(string returnUrl, string UserName, string RoleName)
{
    ViewBag.ReturnUrl = returnUrl;

    var userStore = new UserStore<ApplicationUser>(context);
    var userManager = new UserManager<ApplicationUser>(userStore);
    var roleStore = new RoleStore<IdentityRole>(context);
    var roleManager = new RoleManager<IdentityRole>(roleStore);
    string userID = userManager.FindByName(UserName).Id;

    if ( roleManager.RoleExists(RoleName) == true )
    {
        if ( userManager.IsInRole(userID, RoleName) == true )
        {
            userManager.RemoveFromRole(userID, RoleName);
        }
        else
        {
            userManager.AddToRole(userID, RoleName);
        }
    }

      

ViewModel:

namespace GCCP.Models
{
    public class UserRolesViewModel
    {
        [Required]
        [Display(Name = "User")]
        public string UserName { get; set; }
        public Dictionary<string, bool> RoleAssignments { get; set; }
        public string AjaxTest { get; set; }
    }

}

      

View:

@using GCCP.Models;
@using System.Web.Mvc.Ajax;
@model UserRolesViewModel

@{
    ViewBag.Title = "User Roles";
}

<h2>
    @Model.UserName  |  Assigned Roles 
</h2>

    @Html.Partial("_UserRolesList", Model)

      

Partial view:

@using GCCP.Models;
@using System.Web.Mvc.Ajax;
@model UserRolesViewModel

<div id="roleAssignmentList">
    <table class="table table-condensed">
        <thead>
            <tr>
                <th>
                    Roles
                </th>
                <th>
                    Assigned
                </th>
                <th>
                    Add / Remove
                </th>
            </tr>
        </thead>
        <tbody>
            @foreach ( var item in Model.RoleAssignments )
            {
                if ( item.Value )
                {
                    <tr class="active">
                        <td>
                            @item.Key
                        </td>
                        <td>
                            <i class="glyphicon glyphicon-ok"></i>
                        </td>
                        <td>
                            <form action="@Url.Action("ToggleUserToRoleAssignment")" method="post" data-gccp-ajax="true" data-gccp-target="#roleAssignmentList">
                                <input type="hidden" name="RoleName" value="@item.Key"/>
                                <input type="hidden" name="UserName" value="@Model.UserName"/>
                                <input type="submit" value="Change"/>
                            </form>
                        </td>
                    </tr>
                }
                else
                {
                    <tr>
                        <td>
                            @item.Key
                        </td>
                        <td>
                            <i class="glyphicon glyphicon-remove"></i>
                        </td>
                        <td>
                            <form action="@Url.Action("ToggleUserToRoleAssignment")" method="post" data-gccp-ajax="true" data-gccp-target="#roleAssignmentList">
                                <input type="hidden" name="RoleName" value="@item.Key"/>
                                <input type="hidden" name="UserName" value="@Model.UserName"/>
                                <input type="submit" value="Change"/>
                            </form>
                        </td>
                    </tr>
                }
            }
        </tbody>
    </table>
</div>

      

Jquery:

$(function () {
    var ajaxFormSubmit = function () {
        var $form = $(this);

        var options = {
            url: $form.attr("action"),
            type: $form.attr("method"),
            data: $form.serialize()
        };

        $.ajax(options).done(function (data) {
            var $target = $($form.attr("data-gccp-target"));
            $target.replaceWith(data);
        });

        return false;
    };

    $("form[data-gccp-ajax='true']").submit(ajaxFormSubmit);

});

      

Thank you in advance:)

+3


source to share


1 answer


"only works the first time" behavior is typical of a situation where content is added dynamically, but events are bound directly to elements (for example, in the DOM ready).

The relevant part would be as follows:

$("form[data-gccp-ajax='true']").submit(ajaxFormSubmit);

      

which only binds to existing forms present when this line of code is executed.

An alternative is to use a delegated event handler attached to the immutable ancestor of the elements:

eg.



$('#roleAssignmentList').on("submit", "form[data-gccp-ajax='true']", ajaxFormSubmit);

      

It works by listening for the dispatch event to bubble up to this ancestor, then apply the jquery selector to the elements in the bubble chain, then apply that function.

If you don't have a convenient non-mutable ancestor, use document

. I'm not 100% sure that you are not completely replacing #roleAssignmentList

(or just its children), so you should use instead document

.

eg.



$(document).on("submit", "form[data-gccp-ajax='true']", ajaxFormSubmit);

      

but never use "body" as a delegated event handler object (it has some styling bugs which means it might miss certain events).

+2


source







All Articles