Destination parameter rewriting with jQuery: fixing the AHAH link destination bug

Tue, Apr 20, 2010 - 7:59pm -- Isaac Sukin

Soon after I first implemented AHAH-based form refreshing in my Facebook-style Statuses module for the Drupal content management system, I realized that the process created something of a workflow problem. Users would go and post a message like normal, and then the form -- and the links on it -- would refresh via AHAH. Essentially, when a user clicked the submit button on the form, some JavaScript would silently open another page (the "callback" page) in the background. That page would generate a new copy of the form, which would then be copied and pasted over the form that had just been submitted.

The problem was, that new copy of the form was generated on the callback page, and it had no way of knowing from what page it had been called. In other words, if the script generating the form wanted to know what the current page was, instead of getting the URL of the page on which the form originally appeared, it would get the URL of the callback page. This became a problem when dealing with links, because links often include a "destination" parameter so that when a user takes an action after clicking the link they will be returned to the appropriate place. For example, an "edit" link for a message would typically have a "destination" parameter in the URL so that when a user saved the edits, the browser would automatically send the user back to the page on which the edit link was clicked. In HTML, such a link would look like this:

<a href="/messages/567/edit?destination=messages%2Frecent">Edit</a>

In this example, the link sends users to the message page (/messages/567/edit) and then back to the "messages/recent" page later. Unfortunately, when the destination was set to the current page, it would end up sending the user to the AHAH callback page -- a blank white page with an error message. Not good.

My temporary solution was to check in the form-generating code whether the current page was the AHAH callback page, and if it was, have the link redirect to some default location instead. This worked, mostly -- but it threw some people off when they weren't returned to the page they expected.

The thing is, there isn't a way to fix this problem server-side, if you're using Drupal's built-in AHAH methods that are part of the Form API. (And if you're not, you should be. They save you a lot of work most of the time.) Technically you could override the JavaScript functions in Drupal core, but that would be a lot of duplicated time and effort.

Instead, the way I've chosen to fix the problem is using jQuery to fix destination links to point back to the current page. My solution is below. Note that I haven't put it into very wide use, so YMMV (and it could surely be optimized). This script should be loaded onto the page with the original form.

  //Change "my-form-id" to the id of your form.
  $('#my-form-id a').each(function() {
    var loc = $(this).attr('href').split('?'), base = loc[0], query = '';
    //If we have query parameters, let's look through them.
    if (loc[1]) {
      //Check out each parameter individually.
      var q = loc[1].split('&');
      for (var i = 0; i < q.length; i++) {
        //Examine the parameter-value pairs.
        var item = q[i].split('='), param = item[0];
        if (i == 0) {
          query += '?';
        }
        else {
          query += '&';
        }
        query += param +'=';
        //If there was a destination in the original link, change it to our current page.
        if (param == 'destination') {
          query += escape(window.location.href);
        }
        else if (item[1]) {
          query += item[1];
        }
      }
      //Set the newly modified link.
      $(this).attr('href', base + query);
    }
  });

As far as licensing goes, you can do whatever you want with the code if you simply include a comment in the script with the URL of this page. Also, if you use the code, I'd love to hear from you so I can brag to my friends about how cool I am (not that I need an excuse).