andrew burke
dot
me

about

contact

blog

rss

Analgesic Code: Backtrack

Posted on: 2007-04-09

Over the years I've found snippets of code that make my life easier and, like Aspirin (a name still trademarked in Canada so I guess I can't use it), get rid of the headaches.

The Back button in browsers can be a headache for web developers. If you're not careful it can cause things like duplicated information, old pages showing inaccurate information, and other badness. I've found it handy to build a 'back' button directly into my applications - that way users are more tempted to use that rather than the browser's button.

This can be implemented in Java, PHP, or whatever - but I've done it mostly in Ruby on Rails applications, so that's what the examples will show. First, set up a class called PageTrack - I'm lazy and I put it in my 'models' directory, although I guess it should be in 'lib'. This is code I wrote a while ago while I was still getting the hang of Ruby, so it's not as elegant as it could be - but I've learned not to 'fix' small code that already works unless there's a really good reason.

class PageTrack

  def self.add(session, address)
    if session && address
      session[:page_track] = Array.new if !(session[:page_track])

      # check top element - if the same as current page don't add
      previous_address = session[:page_track][-1]
      if previous_address != address
        session[:page_track].push(address)
      end
    end
  end

  def self.prev(session)
    if session && session[:page_track]
      dummy_result = session[:page_track].pop
      result = session[:page_track].pop
    else
      result = nil
    end
    result
  end
end

Nothing too complicated here - just a stack that holds all of the pages that have been visited in the current session, while skipping duplicates.

Then set up a controller action - I often have a 'redirect' controller that handles general navigation things like 'not allowed' and 'not found' pages, or figuring out what 'go home' means to different kinds of users, and since this is a redirect, it's good to go in here too:

# uses the PageTrack object to go back to the previous page
   def go_back
    prev_address = PageTrack.prev(session)
    if prev_address
      redirect_to prev_address
    else
      go_home
    end
  end

Next, put this onto your pages - the best place would be near the top of your application layout if you're using one:

<% PageTrack.add(session, @request.env['REQUEST_URI']) %>

This adds the current address to the top of the stack. Then, wherever you want to have a 'back' button, you can add a link like this:

<a href="/redirect/go_back">

Clicking on the link gets the previous url visited and sends you there.

I generally work on web-based business applications, where I can actually train my users, so I put the go_back link on the application logo in the top left of the screen - and they've learned that clicking on that logo goes back to the previous page, in a correct, up-to-date, and not-resubmitting-the-order-and-duplicating-the-Packing-Slip manner. Public-facing sites might want to have a more clearly labelled 'Back' link on them. Regardless, it makes a handy addition to the set of interactivity and navigation tools.

This is also handy for navigation within the system. Say you have a Packing Slip screen that can be accessed from several different places: a job overview, a listing of packing slips, a shipment report, or whatever - and you want the system to return the user to where they were once they hit 'Approve' or 'Ship' or whatever. You can't just redirect to the listing, say, since the users may have come to the slip from the shipment report or the job overview, and users like returning to where they were before. Simply calling go_back makes sure the user ends up in the expected place.

The other handy thing is if you have set up automatic mail messages that get sent to you when there's an error in your code. If you include a snapshot of the session contents, you'll get the entire PageTrack stack, which shows all of the pages that user accessed during their session - which is great for figuring out how errors are happening.

Previous: Baby Steps With EMACS
Next: Disclaimer