Subscribe to the MPC blog rss feed feed-icon-14x14
 

Multiple Unobstrusive Onload Events Using Prototype in Ruby on Rails



Posted on 02:44PM on 10/06/2008
Tags: javascript, rails

I'm currently working on a web application in Ruby on Rails that has a high degree of client-side scripting requirements. In almost every view, many things need to take place just after the page loads. I'm using Prototype, but any good javascript framework will do.

Since the application has a large amount of forms to be filled in by users, almost every view needs to set focus on a textbox in the form. On top of this requirement, almost every view has other javascript-related requirements when a particular view loads. For example, several of the views need to be able to set the form to its previous state when reloaded.

The typical way of registering an OnLoad event looks something like:

//application.js
  window.onload = function() {
    setFocus();
  }

  function setFocus()
  {
     focusElement = getFocusElement();
     if(focusElement && focusElement.focus)
     {
        focusElement.focus();
     }
  }

  function getFocusElement()
  {
     $$('input.focus').first();
  }

This sets up a callback that, when the window is loaded, locates an input element with a class of 'focus' and calls focus on it. This works fine. I put this in my application.js file and load it with every view.

Now what about running additional OnLoad actions for specific views? I don't want to code everything in application.js. That approach has several problems. Firstly, it means that application.js gets really bloated and if it loads with every view, there is additional overhead. Secondly, it means that you may be loading a bunch of code, required for other views, that is not needed for the current view. Thirdly, some of the OnLoad code may expect certain elements to be on the page, and they won't always be there. The bottom line: this is not a good approach. So what is the solution?

For me (and you may have other, better solutions), the approach that works best is a combination of application.js and content_for in my views.

Firstly, I ensure that in my layout I include an addtional named yield in the head section of the HTML. For example:

<%= yield :head %>

This gives me a place to include additional javascript files. In my view, I can do

  <% content_for :head do %>
     <%= javascript_include_tag "user" %>
  <% end %>

In this, case, I would be adding a javascript file called user.js (located in public/javascripts) to the head section of this view.

That solves one problem: that of only loading javascript code appropriate to the view currently being rendered.

There is only one more problem to get past: In my application.js, I am registering an OnLoad handler (as show above). In my user.js file, I also need to register an OnLoad event handler. As it sits right now, whichever file gets loaded last will clobber all the previous OnLoad handler registrations. How can this be addressed?

My solution (and, I'm sure, others') is as follows:

  //application.js
  var onLoadHandlers = $A();

  window.onload = function() {
    onLoadHandlers.reverse();
    onLoadHandlers.each(function(method) {
       method();
    })
  }
  
  function registerOnLoadHandler(method) {
     onLoadHandlers.push(method)
  }

  registerOnLoadHandler(focusHandler);

  function focusHandler() {
    //as shown previously
  }

If application.js is loaded with every view, we can effectively ensure that it's possible to call registerOnLoadHandler from any other javascript file loaded after it.

As a result, in my user.js file, I can do the following:

  //user.js
  registerPageLoadHandler(setViewState);
  registerPageLoadHandler(roleChangedHandler);

  function roleChangedHandler() {
    //code
  }

  function setViewState() {
    //code
  }

These functions will also be run when the page is loaded.

I like this technique and it seems to work well. It minimizes the amount of javascript code that I need to load on a per page basis and ensures that only javascript required for a particular view is loaded for that view. I also think that it makes it very easy to find javascript code related to a particular view.

2 Comments (Show) (Comments are closed for this post)

 
Please note that I am currently unavailable for any large, long term work.