Forum: Ruby on Rails Rails & Prototype Ajax Back Button Support in IE and FF

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
F0699bc4e920a0cea55b958d204b0a4b?d=identicon&s=25 Slain Wilde (slain)
on 2007-02-03 07:16
So I adapted some of the other methods of using the anchor href hash.
Much of this concept I got from
http://www.mikage.to/jquery/jquery_history.html and adapted to Prototype
javascript.

This method works as follows:
You use a custom helper method, called history_remote, instead of
link_to_remote whenever you want a new 'page' that can be navigated to
using the back/forward buttons in the browser, or bookmarked. This adds
another parameter to the Ajax.Request in the onclick of the ahref,
history:true, which tells Ajax.Request to do the back button handling if
it is IE or FF.  Other browsers should be unaffected, but without
support for back button.



Add this to your application_helper.rb:
module ActionView
  module Helpers
    module PrototypeHelper

  def history_remote(name, options = {}, html_options = {})
    options[:history] = true
    function = remote_function(options);
    function.gsub!(/asynchronous\:true/, 'asynchronous:true,
history:true')
    link_to_function(name, function, html_options)
   end
       end
   end
end




Add this to your application layout.rhtml:
<head>
....
<script>
  var history_current_cache = "";
  var root_page = '/upload_media/show_mixer';
  url_hash_check = function() {
    if (browser_id == 1) {
      var history = $("ie_hash_history");
      var iframe = history.contentDocument ||
history.contentWindow.document;
      var current_hash = iframe.location.hash + iframe.location.search;
// ie splits it
      if(current_hash != history_current_cache && !(current_hash == "#"
&& history_current_cache == "")) {
        location.hash = current_hash;
        history_current_cache = current_hash;
        new Ajax.Request(current_hash.replace(/^#/, ''));
      }
    } else if (browser_id == 2) {
      var current_hash = location.hash;
      if(current_hash != history_current_cache) {
        var ajax_hash = current_hash;
        if (ajax_hash == "")
          ajax_hash == "/";
        new Ajax.Request(current_hash.replace(/^#/, ''));
        history_current_cache = current_hash;
      }
    }
  };

  init_back_button = function() {
    if (navigator.appVersion.match(/\bMSIE\b/)) {
      browser_id = 1;
      var history = $("ie_hash_history");
      var iframe = history.contentWindow.document;
      iframe.open();
      iframe.close();
      iframe.location.hash = location.hash + location.search; // ie
breaks up the hash

      //ie_history_click(root_page);

      if (location.hash == "") {
        location.hash =  history_current_cache = '#' + root_page;
      }

      new PeriodicalExecuter(function() {
          url_hash_check();
        }, 5.2);
    } else if (navigator.appName == "Netscape") {
      browser_id = 2;
      if (location.hash == "") {
        location.hash = root_page;
        history_current_cache = '#' + root_page;
      }
      new PeriodicalExecuter(function() {
          url_hash_check();
        }, 0.2);
    }
  }

</script>

</head>
<body onload="init_back_button()">
<iframe id="ie_hash_history" style="display: none;"></iframe>
....

Add the following function to the prototype.js:
/////////////////////////////////////////////////////////////
// this needs to be here even if history is disabled
var browser_id = 0;
ie_history_click = function(hash) {
  var newhash = '#' + hash;
  location.hash = newhash;
  var history = $("ie_hash_history");
  var iframe = history.contentWindow.document;
  iframe.open();
  iframe.close();
  iframe.location.hash = newhash;
}
////////////////////////////////////////////////////////////

And modify the Ajax.Request request function to this:
  request: function(url) {

    if (browser_id == 1 && this.options.history == true) {
      ie_history_click(url);
    } else if (browser_id == 2 && this.options.history == true) {
      parent.location = '#' + url;
    } else {
      // we want to actually call for all other browsers and for
non-history
      // links in IE and FF
      var parameters = this.options.parameters || '';
      if (parameters.length > 0) parameters += '&_=';

      try {
        this.url = url;
        if (this.options.method == 'get' && parameters.length > 0)
          this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

        Ajax.Responders.dispatch('onCreate', this, this.transport);

        this.transport.open(this.options.method, this.url,
          this.options.asynchronous);

        if (this.options.asynchronous) {
          this.transport.onreadystatechange =
this.onStateChange.bind(this);
          setTimeout((function()
{this.respondToReadyState(1)}).bind(this), 10);
        }

        this.setRequestHeaders();

        var body = this.options.postBody ? this.options.postBody :
parameters;
        this.transport.send(this.options.method == 'post' ? body :
null);

      } catch (e) {
        this.dispatchException(e);
      }
    }
  },
F0699bc4e920a0cea55b958d204b0a4b?d=identicon&s=25 Slain Wilde (slain)
on 2007-02-03 07:24
Some more info:

Basically this sets up a periodical updater, that checks the location
'hash', the stuff after the # sign in a URL.  To support the back
button, I convert the :url to a hash instead.

So if your action is at http://localhost/my_controller/my_action/my_id,
you will see http://localhost/#/my_controller/my_action/my_id in your
location bar.  When you click a link in IE or FF, the only thing that
really happens is that the location bar url changes, then the periodical
update notices a split second later and makes the actual Ajax.Request to
do the change. (In IE it actually does some more magic with an IFRAME.)
It also works with Ajax.Updater since it uses the Ajax.Request's request
method.
F0699bc4e920a0cea55b958d204b0a4b?d=identicon&s=25 Slain Wilde (slain)
on 2007-02-03 07:36
Small fix: I let 'debug' for IE in there.  Change the '5.2' in the
init_back_button function, to '0.2'.
72ea925c0ca3d19fdd2f12fa76681624?d=identicon&s=25 Stephan Wehner (stephanwehner)
on 2007-02-03 08:06
Slain Wilde wrote:
> Small fix: I let 'debug' for IE in there.  Change the '5.2' in the
> init_back_button function, to '0.2'.

Your work sounds very useful. I remember when I was looking at the
backbutton  a year ago, there were several javascript solutions
available; I think I liked this one
http://www.contentwithstyle.co.uk/Articles/38/ which I found later than

http://www.unfocus.com/Projects/HistoryKeeper/

which I am actually using.

I also remember one approach needed different treatments for FF, IE, and
Safari.

How does the solution you chose compare?

Stephan
F0699bc4e920a0cea55b958d204b0a4b?d=identicon&s=25 Slain Wilde (slain)
on 2007-02-03 09:38
Stephan Wehner wrote:
> Slain Wilde wrote:
>> Small fix: I let 'debug' for IE in there.  Change the '5.2' in the
>> init_back_button function, to '0.2'.
>
> Your work sounds very useful. I remember when I was looking at the
> backbutton  a year ago, there were several javascript solutions
> available; I think I liked this one
> http://www.contentwithstyle.co.uk/Articles/38/ which I found later than
>
> http://www.unfocus.com/Projects/HistoryKeeper/
>
> which I am actually using.
>
> I also remember one approach needed different treatments for FF, IE, and
> Safari.
>
> How does the solution you chose compare?
>
> Stephan

They all use pretty much the same technique that this does.  This just
makes it really easy to use in Rails/Prototype without having to add
JQuery, or Dojo, or some other framework. Using 'history_remote' instead
of 'link_to_remote' is dead simple as far as what it takes to convert
your site.  It took me all of 5 minutes.

One of the key things about this solution, with my redefinition of
Ajax.Request#request, is that it doesn't affect page or fragment caching
at all, since the links look identical across all browsers.

Many of the other techniques require you to add a class="remote" or
similar to all of the links, requiring you to run thru all of the links
on the page and changing the onclick behavior.  Because of this, some of
them don't support 'backable' links inside the content updated in an
Ajax.Request (at least without adding some additional javascript either
at the end of your update which can get tricky since the request
completes before the links are rendered).

Also, using the actual url_for stuff as the location bar hash definition
seems to be novel in the ones I've reviewed.  It makes it easy to know
where you are when debugging, and allows external bookmarks to work
without lots of hoops.

Safari support I will look into.  It (and Opera) will work fine on the
same site withOUT back button support, but a different technique is
required for Safari that for my clients isn't a huge issue since it
still works.

The only mild annoyance right now, is if you bookmark one of these hash
links, when you use that bookmark, it will load the / root page then
load the page you wanted.  Depending on how you design your site, you
can tweak the init_back_button function to handle it.  If anyone needs
help with that, post here.
This topic is locked and can not be replied to.