views:

283

answers:

8

I have a web page with a form. When the user submits the form, I want the server to make the browser redirect to a different page from the form action. Right now, I am doing this by using PHP's header function to send a 302 status code. It works fine.

I am trying to make the page on the server redirect the browser in the same way, regardless of whether it was submitted normally (without Javascript) or via Ajax. I tried to do this by setting the window location to whatever URL is in the Location header. I am using jQuery, and doing a call like this:

$.ajax({
    url: this.action,
    type: "POST",
    data: getFormData(this),
    complete: function(request) {
        window.location.assign(request.getResponseHeader("Location"));
    }
});

However, that didn't work. After thinking about it, I realized that this is not very surprising. In an Ajax request, the browser is supposed to transparently handle redirect responses such as 302 codes before changing the readyState. When the complete function runs, it is looking for the Location header in the final destination and not finding it.

As an experiment, I then tried sending a 200 status code with a Location header. I tried the Ajax request and it worked fine. However, when I did the non-Ajax submit, it didn't work. The browser went to the form action page and stayed there, like it was ignoring the Location header.

Is there any way to make the same page redirect in both cases, without the server having to know or care whether the request is an Ajax request?

In case this matters, I tried the form in various browsers (IE8, IE7, IE6, Firefox 3.5, Chrome) with similar results each time. Also, I am doing a post request to avoid bumping into IE's 2083-character URL length limit.

A: 

If the user is getting redirected regardless, why the Ajax? The whole point of Ajax like this is to make changes to the page without a page refresh, so the technique you're using seems a little like building a printer that outputs into a shredder hopper.

ceejayoz
Maybe if there is an error there is no redirect e.g., validation type stuff
rojoca
Good question. The same script on the server is used by many forms on different pages, some of which are not well designed and try to submit using Ajax, without any good reason to do so. This may be a temporary workaround until I can fix those forms.In addition, I am curious about whether this can be done because it may come in handy some day. I may want to have my site use Ajax for certain bells and whistles, but still work for clients without Javascript support. Also, I may need to make an Ajax request, and either stay on the same page or redirect, depending on what the server decides.
mikez302
A: 

I'd like to know more about your use-case for this. From what I understand your trying to get your application to load a page based on the 'Location' header of an Ajax call within it? I'd ask why?

An HTTP header doesn't seem to be the right place to get that information from. Isn't your application essentially making a query that says "Where shall I redirect to?". There's no reason the Ajax response actually has to respond with a 302 and a 'Location' header. Why not just have it respond with JSON or XML which contains the new URL?

Edit: Just re-read your penultimate paragraph. I'm not sure there's a good way of achieving what you want. The concept sounds broken to me. :)

Andy Hume
A: 

Pass additional parameter to your ajax request for easy identify type of request. When ajax - do not redirect - just send target url, then redirect client side in ajax callback via location.href

like this:

$.post('/controller/action', {formdata}, function (redirect_to) {
    location.href = redirect_to;
});
Anatoliy
A: 

Will "complete" work:

$.ajax({
    type: frm.attr('method'),
    url: frm.attr('action'),
    data: frm.serialize(),
    complete: complete(xhr, status) {
       window.location.assign(xhr.getResponseHeader("Location"));

    }
});
Lance Rushing
It works with the 200 response, but not with the 302 response. Read my post for more details.
mikez302
A: 

Did you try using the error function instead of complete?

$.ajax({
    url: this.action,
    type: "POST",
    data: getFormData(this),
    error: function(request) {
        if(request.status == 302)
            window.location.assign(request.getResponseHeader("Location"));
    }
});

jQuery sends any 3xx response to the error function. Maybe the 'Location' header will still be available at this stage.

rojoca
I tried something like that and it didn't work. It is not supposed to work that way. The browser is supposed to transparently follow the 302 code before it has a chance to run the complete, success, or error functions. See my previous comment (http://stackoverflow.com/questions/373087/catching-302-found-in-javascript/1406899#1406899) for more details.
mikez302
I see. Looks like you'll need to reply with a different response for ajax then. What a pain
rojoca
+2  A: 

HTTP 302 response are consumed silently by XmlHttpRequest implementations (e.g. jQuery's ajax function). It's a feature.

The way I've solved this in the past is to detect for XmlHttpRequests and issue a "Content-Location" header (rather than a "Location" header). The most cross-library way of doing this is to check for the "X-Requested-With" http header in your server-side code (jQuery, Prototype, Mootools among others set this):

if (@$_SERVER['HTTP_X_REQUESTED_WITH']  == 'XMLHttpRequest') {
    header('Content-Location: ' . $redirect_url);
} else {
    header('Location: ' . $redirect_url);
}

You still need to special-case your client-side code:

$.ajax({
    // ...
    complete: function(xhr) {
        var redirect_url = xhr.getResponseHeader("Content-Location");
        if (redirect_url) {
            window.location = redirect_url;
        }
    }
})
Crescent Fresh
A: 

Why not have your "ajax action" simply fill in the needed form fields and submit the form instead? This way you'll get exactly the same behavior as when submitting by hand.

levik
A: 

Here is another option although you will need to do some cross browser tests:

complete: function(request) {
    if(request.status == 200) {
        var doc = document.open(request.getResponseHeader('Content-Type'));
        doc.write(request.responseText);
        doc.close();
    }
}

Major drawbacks: URL in address bar doesn't change; could mess with back button/history

Although I think @crescentfresh's idea is the way to go

rojoca
Haven't seen `document.open()` in a long time. That code is just going to totally wipe the current page isn't it? Interesting...
Crescent Fresh
Yeah, it just replaces the page with the responseText. On Firefox and Safari it fired onload events too, haven't tried it on other browsers. Saves you doing another request for a resource you already have.
rojoca