Search code examples
jqueryasp.net-mvc-3unobtrusive-ajax

How to use $(this) inside MVC3 Ajax.ActionLink OnBegin,OnComplete Events


My Controller creates a list of links like this

<ul id="MainMenu">
@foreach (var item in Model.MenuItems)
{
    <li>@Ajax.ActionLink(item.Caption,
    item.ActionName,
    item.ControllerName,
    new AjaxOptions
    {
        UpdateTargetId = "pageBody",
        OnBegin = "BeginUpdatePage",
        OnComplete = "EndUpdatePage",
        OnFailure = "FailUpdatePage"
    })
    </li>
}
</ul>

I have some javascript like this

function BeginUpdatePage() {
 $("#MainMenu li").removeClass('selected');
 $(this).parent().addClass('selected');
 $("#pageBody").fadeTo('slow', 0.5);
}

function EndUpdatePage() {
 $("#pageBody").fadeTo('slow', 1);    
}

function FailUpdatePage() {
 alert('There has been an error loading this page please try again.');
}

in the BeginUpdatePage function line 1 and 3 execute fine, but line 2 "$(this).parent().addClass('selected');" does not visiably do anything... (I have declared that class in my css).

I have looked at Jquery docs (Jquery.ajax()) it says that "this" is the element that was clicked. I have also checked in "jquery.unobtrusive-ajax.js" that was in my project by default. This file also passes "this" when executing my function.

"result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(this,arguments);"

What am I doing wrong here... I have seen other examples that have used this technique but I have been failing all day!

How can I get to the element that was clicked, inside my Begin, complete functions.


Solution

  • Thanks guys for all your suggestions, Darin, although your solution works perfectly, I wanted to try and use the built in Ajax utilities.

    I have found a work around after looking at Jquery.Ajax() Docs and also inspecting "jquery.unobtrusive-ajax.js" this is more of a Hack than proper use, im not sure who put the "jquery.unobtrusive-ajax.js" file together but if anybody knows how to email them, maybe send them a link to this page :-)

    this is the interesting part of "jquery.unobtrusive-ajax.js" in this function "function asyncRequest(element, options) "

    $.extend(options, {
            type: element.getAttribute("data-ajax-method") || undefined,
            url: element.getAttribute("data-ajax-url") || undefined,
            beforeSend: function (xhr) {
                var result;
                asyncOnBeforeSend(xhr, method);
                result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(this, arguments);
                if (result !== false) {
                    loading.show(duration);
                }
                return result;
            },
            complete: function () {
                loading.hide(duration);
                getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(this, arguments);
            },
            success: function (data, status, xhr) {
                asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html");
                getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(this, arguments);
            },
            error: getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"])
        });
    

    When this code calls the apply method on each step beforeSend, Complete, error, it passes in two perams, "this" and "arguments"...

    Wich works create, but in the context "this" is the XHR (XMLHttpRequest) not the element that was clicked... also by inspecting the function headers you can see that it passes the XHR object as the first peram. So why do we need it to be the calling context as well?

    My solution... change the "jquery.unobtrusive-ajax.js" and "jquery.unobtrusive-ajax-min.js" files...

    rather than pass "this" (XHR) on the apply method, lets send the actual element instead!

    So the whole function now looks like this:

    function asyncRequest(element, options) {
        var confirm, loading, method, duration;
    
        confirm = element.getAttribute("data-ajax-confirm");
        if (confirm && !window.confirm(confirm)) {
            return;
        }
    
        loading = $(element.getAttribute("data-ajax-loading"));
        duration = element.getAttribute("data-ajax-loading-duration") || 0;
    
        $.extend(options, {
            type: element.getAttribute("data-ajax-method") || undefined,
            url: element.getAttribute("data-ajax-url") || undefined,
            beforeSend: function (xhr) {
                var result;
                asyncOnBeforeSend(xhr, method);
                result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(element, arguments);
                if (result !== false) {
                    loading.show(duration);
                }
                return result;
            },
            complete: function () {
                loading.hide(duration);
                getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(element, arguments);
            },
            success: function (data, status, xhr) {
                asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html");
                getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(element, arguments);
            },
            error: getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"])
        });
    
        options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" });
    
        method = options.type.toUpperCase();
        if (!isMethodProxySafe(method)) {
            options.type = "POST";
            options.data.push({ name: "X-HTTP-Method-Override", value: method });
        }
    
        $.ajax(options);
    }
    

    Now when you create your functions to handle these events you can use "this" to get the actual element.

    example:

    function BeginUpdatePage(xhr) {
     $("#MainMenu li").removeClass('selected');
     $(this).parent().addClass('selected');
     $("#pageBody").fadeTo('slow', 0.5);
    }
    function EndUpdatePage(xhr,status) {
     $("#pageBody").fadeTo('slow', 1);    
    }
    function FailUpdatePage(data,status,xhr) {
     alert('There has been an error loading this page please try again.');
    }
    

    Now for some people it my be easier to just go with Darin's solution, to just write you own Jquery to handle the click event, hey you probably have better control over it all,

    but Im a believer that the built in stuff should work too. without having to mess with it. So I hope either somebody can prove me wrong, and there is actually a better way of doing this, or the guys who put this script together could change it.

    unless they have good reason for doing it this way? explanations welcome :-)

    Thanks again everybody who had good suggestions for this question I hope this helps people in the future.