MVC4 WebGrid loaded from Ajax form - multiple calls to Controller when sorting and paging
Asked Answered
B

8

5

I have the following in my view

@using (Ajax.BeginForm("Search", "Home", null,
                               new AjaxOptions
                                   {
                                       InsertionMode = InsertionMode.Replace,
                                       HttpMethod = "POST",
                                       UpdateTargetId = "gridContent",
                                   }, new { @class = "search" }))
{
    <input type="submit" value="Search" />
}
<div id="gridContent">
</div>

This is what returns /Home/Search

@model List<TestTable.Models.People>
@{
Layout = null;
}
@{
var grid = new WebGrid(Model, canPage: true, canSort: true, rowsPerPage: 5,             ajaxUpdateContainerId: "tableDiv"); grid.Pager(WebGridPagerModes.NextPrevious);
}
<div id="tableDiv">
    @grid.GetHtml(
        columns: grid.Columns(
        grid.Column("Name", " Name")
))
</div>

This works good in MVC3, however MVC4 sends a script on every new search, causing one new additional request for each submit button click for every paging and sorting query. Here is how it looks:

"http://localhost:59753/Home/Search".
"http://localhost:59753/Home/Search?sort=Name&sortdir=ASC&__swhg=1394297281115"
"http://localhost:59753/Home/Search". 
"http://localhost:59753/Home/Search?sort=Name&sortdir=ASC&__swhg=1394297284491"
"http://localhost:59753/Home/Search?sort=Name&sortdir=ASC&__swhg=1394297284490"

Any ideas how to fix that? Thanks in advance!

Bemoan answered 8/3, 2014 at 16:59 Comment(1)
I have the same problem and I haven't found a solution yet :(Arawn
R
12

The reason this is happening is because the WebGrid control injects the following script into your DOM every time you render it (in your case every time you submit the AJAX form because the WebGrid is situated in a partial that you are injecting in your DOM):

<script type="text/javascript">
    (function($) {
        $.fn.swhgLoad = function(url, containerId, callback) {
            url = url + (url.indexOf('?') == -1 ? '?' : '&') + '__swhg=' + new Date().getTime();

            $('<div/>').load(url + ' ' + containerId, function(data, status, xhr) {
                $containerId).replaceWith($(this).html());
                if (typeof(callback) === 'function') {
                    callback.apply(this, arguments);
                }
            });
            return this;
        }

        $(function() {
            $('table[data-swhgajax="true"],span[data-swhgajax="true"]').each(function() {
                var self = $(this);
                var containerId = '#' + self.data('swhgcontainer');
                var callback = getFunction(self.data('swhgcallback'));

                $(containerId).parent().delegate(containerId + ' a[data-swhglnk="true"]', 'click', function() {
                    $(containerId).swhgLoad($(this).attr('href'), containerId, callback);
                    return false;
                });
            })
        });

        function getFunction(code, argNames) {
            argNames = argNames || [];
            var fn = window, parts = (code || "").split(".");
            while (fn && parts.length) {
                fn = fn[parts.shift()];
            }
            if (typeof (fn) === "function") {
                return fn;
            }
            argNames.push(code);
            return Function.constructor.apply(null, argNames);
        }
    })(jQuery);
</script>

This script is baked into the WebGrid helper and there's not much you could do against it once you enable AJAX on your WebGrid. In this script you will undoubtedly notice how it subscribes to the click event of the pagination anchors in a lively manner:

$(containerId).parent().delegate(containerId + ' a[data-swhglnk="true"]', 'click', function() {
    $(containerId).swhgLoad($(this).attr('href'), containerId, callback);
    return false;
});

which is all sweet and dandy except that every time you click on the submit button you are injecting this script into your DOM (because your WebGrid is in the partial) and basically you are subscribing to the click event of the pagination anchors multiple times.

It would have been great if the authors of this WebGrid helper have left you the possibility to replace this delegate with a standard click handler registration which would have been ideal in this case as it wouldn't create multiple event registrations, but unfortunately the authors didn't left you with this possibility. They just assumed that the WebGrid would be part of the initial DOM and thus their script.

One way would be to subscribe to the OnBegin handler of the Ajax form submission and simply undelegate the existing event handlers because they will be overwritten once you refresh the DOM:

@using (Ajax.BeginForm("Search", "Home", null,
    new AjaxOptions
    {
        InsertionMode = InsertionMode.Replace,
        OnBegin = "callback",
        HttpMethod = "POST",
        UpdateTargetId = "gridContent",
    }, new { @class = "search" }))
{
    <input type="submit" value="Search" />
}

<div id="gridContent"></div>

<script type="text/javascript">
    var callback = function (a) {
        $('#tableDiv').parent().undelegate('#tableDiv a[data-swhglnk="true"]', 'click');
    };
</script>

But to be honest, personally I just hate all this automatically generated scripts and simply never use any Ajax.* helpers stuff as well as activating AJAX on the WebGrid. I prefer to unobtrusively AJAXify the elements I want using jQuery which provides me with far greater control over what's happening. This way I would simply have externalized the bunch of automatically generated javascript by the WebGrid helper into a separate js file that I would have included in my View and there wouldn't be any needs of unregistering and cleaning the mess of the duplicate event handlers created by following the standard way of doing things.

Regelation answered 11/3, 2014 at 22:13 Comment(3)
Thank you for your answer!! Do you have any idea if this is fixed in mvc5?Bemoan
@Bemoan nope, still broken :(Celesta
Great answer for a silly problem. undelegate has been deprecated in favor of off since jQuery 1.7. Example: $('#tableDiv').parent().off('click', '#tableDiva[data-swhglnk="true"]');Frias
M
2

A bit late but I had a similar problem and couldn't find any description of it so thought I'd add it here in case it can help somebody.

No matter what I tried the sorting and paging requests were always duplicated. I tried fixes as described here but even before I had done any type of AJAX update I got the duplication.

I put that problem on hold and after failing to style the pagination to my satisfaction I created my own as a partial view. When deleting the old pagination the duplication was no more..... Haven't taken the time to try and figure out why, just happy it is solved.

So I removed this:

        @grid.Pager(mode: WebGridPagerModes.All, firstText: "First", previousText: "Prev", nextText: "Next", lastText: "Last")

As I said, in case it helps someone.

Malinin answered 26/1, 2016 at 17:24 Comment(0)
D
1

Looks like the paging and sorting links are bound using "on"/"live" event every time the grid is rendered. It could be solved unbinding the events of the elements of the grid before rendering the grid html or on the ajaxUpdateCallback method.

$('#tableDiv').andSelf().unbind();

Donettedoney answered 23/3, 2014 at 5:32 Comment(0)
C
1

I have solved this for MVC 5, you would need to use ajax call rather than using the ajax form, catch the ajax response and replace the partial page's DOM generated by the webgrid helper using below:

var data = data.replace('<script type="text/javascript">', '<script type="text/javascript"> $(".table").undelegate();');
    $('#YourParentDivIDWherePartialIsRendered').undelegate();
    $.ajax
           (
               {
                   contentType: "application/json; charset=utf-8",
                   type: 'POST',
                   url: '/YourController_Name/YourAction_Name',
                   data: JSON.stringify(YourModel),
                   success: function (data) {
                       //Added to undelegate the old events tagged to the partial view's grid.
                       var data = data.replace('<script type="text/javascript">', '<script type="text/javascript"> $(".table").undelegate();');
                       $('#YourParentDivIDWherePartialIsRendered').undelegate();
                       $('#accountSearch-grid').html(data);
                       $(document).foundation();
                   },
                   error: function (xhr, status, error) {
                       alert(error);
                   }

               });
Coretta answered 10/11, 2014 at 4:56 Comment(0)
V
0

put this script to your Index.cshtml or js file

<script type="text/javascript">
(function($) {
    $.fn.swhgLoad = function(url, containerId, callback) {
        url = url + (url.indexOf('?') == -1 ? '?' : '&') + '__swhg=' + new Date().getTime();

        $('<div/>').load(url + ' ' + containerId, function(data, status, xhr) {
            $containerId).replaceWith($(this).html());
            if (typeof(callback) === 'function') {
                callback.apply(this, arguments);
            }
        });
        return this;
    }

    $(function() {
        $('table[data-swhgajax="true"],span[data-swhgajax="true"]').each(function() {
            var self = $(this);
            var containerId = '#' + self.data('swhgcontainer');
            var callback = getFunction(self.data('swhgcallback'));

            $(containerId).parent().delegate(containerId + ' a[data-swhglnk="true"]', 'click', function() {
                $(containerId).swhgLoad($(this).attr('href'), containerId, callback);
                return false;
            });
        })
    });

    function getFunction(code, argNames) {
        argNames = argNames || [];
        var fn = window, parts = (code || "").split(".");
        while (fn && parts.length) {
            fn = fn[parts.shift()];
        }
        if (typeof (fn) === "function") {
            return fn;
        }
        argNames.push(code);
        return Function.constructor.apply(null, argNames);
    }
})(jQuery);

then, processing your grid html string in class

string html = _grid.GetHtml(
            columns: _columns,
    ...
            ).ToHtmlString();
Regex reg1 = new Regex("<script(.|\n)*?</script>", RegexOptions.IgnoreCase);
string _script = reg1.Match(html).Value.ToString();
html = html.Replace(_script, "");

in the index file: @MvcHtmlString.Create(@html)

that' all

Vedette answered 28/12, 2015 at 20:11 Comment(0)
M
0

Actually the solution $('#tableDiv').parent().off('click', '#tableDiva[data-swhglnk="true"]'); is working perfectly but it remains the __swhg in the call URL of pagination so here is the code for removing the extra __swhg in the call of page using AJAX.

$(document).ajaxComplete(function () {
    $('a[data-swhglnk="true"]').click(function () {        
        $(this).attr("href", $(this).attr("href").replace(/(^|&)__swhg=([^&]*)/, ''));
    });
});
Malvinamalvino answered 9/1, 2018 at 9:4 Comment(0)
J
0

If anyone is going through these and still having problems, I believe I found the real issue. The script was never being rendered twice on the page for me so the accepted answer didn't make sense.

The issue was instead with this line:

 $('table[data-swhgajax="true"],span[data-swhgajax="true"]').each(function() {

If the pager is not in the footer of the table (by defining it in the setup of the webgrid) and is instead defined by calling grid.pager() it will put the pager in a span. This means when the above line is called it binds the click event to the parent of the table (gridContent) and to the parent of the span (gridContent).

There are a few options, but what I opted to do was essentially what top answer said, and remove the delegates for that element like so:

$("#gridContent").off("click", "**");

And then rebind the same exact click function, but only bind it to the span. So the line referenced above I changed to:

$('span[data-swhgajax="true"]').each(function () {

And it works as intended. This will of course break any pagers on the same page that are part of the table.

Jeffjeffcoat answered 28/2, 2018 at 14:24 Comment(0)
W
0

First Remove ajax Update Callback from Web Grid and add following java script code below to web grid or web grid Container div:

$("#WebgridContainerDiv table  a").click(function (event) {
        event.preventDefault();
        var href = $(this).attr("href");
        $.ajax({
            url: href,
            dataType: 'html',
            success: function (data) {
                $("#WebgridContainerDiv").empty();
                $("#WebgridContainerDiv").html(data);
            }
        })
    });
Wolverhampton answered 23/4, 2018 at 5:2 Comment(1)
Welcome to SO. Please take a look here to learn how to format your posts: stackoverflow.com/help/formattingPrince

© 2022 - 2024 — McMap. All rights reserved.