ajaxsortingasp.net-mvc-4paginationwebgrid

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


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!


Solution

  • 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.