Categories
Uncategorized

Subclassing with Prototype

I really like Ajax.InPlaceEditor from script.aculo.us. But it doesn’t quite do what I need. Nonetheless, bending it to my will ought to be fairly simple if I can just override a method or two.

This involved getting very familiar with Prototype’s Class.create and Object.extend.

After spending a while looking at the source for those two, my big book of JavaScript, and google, I was still flummoxed. Until I scrolled down a bit in controls.js (where Ajax.InPlaceEditor) lives. I saw this example of inheritance:

  Ajax.InPlaceCollectionEditor = Class.create();
  Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
  Object.extend(Ajax.InPlaceCollectionEditor.prototype, { ... });

That is:

  1. Create a new class (or constructor function to be accurate).
  2. Copy all the template functions from the source class into the destination class.
  3. Start adding our own functions into the class.

This is a good start! But there’s a tricky problem that I need to overcome: I wanted to override the constructor and run some code after the base class constructor. There’s no easy way to do this, because there is no link between the new class and the inherited one after you have called Object.extend(). So, you just have to be very explicit about exactly what you want to do. This is what I ended up with:

  var MyInPlaceEditor = Class.create();
  Object.extend(MyInPlaceEditor.prototype, Ajax.InPlaceEditor.prototype)
  Object.extend(MyInPlaceEditor.prototype, {
      initialize: function(element, url, options) {
          Ajax.InPlaceEditor.prototype.initialize.apply(this, arguments);
          // Don't respond to events on the main element of interest.
          // Only listen to events on the externalControl.
          Event.stopObserving(this.element, 'click', this.onclickListener);
          Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
          Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
      }
  });

It’s that line at the start of initialize() that’s the interesting one. It grabs the initialize() from Ajax.InPlaceEditor, and then uses apply() to call it using the new MyInPlaceEditor object as this. So it gets treated like it’s part of MyInPlaceEditor, even though it’s really defined in Ajax.InPlaceEditor.

So now I’m in a position to start overriding the other bits that I care about.

Categories
Uncategorized

Prototype and window.onload

We’ve known for some time that simply assigning to window.onload is bad. Simon Willison created addLoadEvent a long time back to work around the problem. But I’m in Rails, and we have prototype. So what’s the correct idiom?

Well, after a bit of playing, it seems to be this:

  <% content_for("page_scripts") do %>
    Event.observe(window, 'load',
      function() { $('username').focus() }
    );
  <% end %>

I do love that $() function.

This assumes that you have a layout that looks something like this, in order to insert bits of JavaScript into the head.

  <script type="text/javascript">
    <%= @content_for_page_scripts %>
  </script>

Anyway, the solution seems a little more verbose than addLoadEvent(), but not disastrously so.