In a previous post I showed how to get around a bug in Prototype that was ignoring the “on” parameter of the observe_field function. The solution I originally posted no longer works with the latest version of Rails and Prototype. Although the documentation for observe_field received a facelift, somehow the Rails team still missed the fact that the :on function doesn’t work with the current version of Prototype. Or maybe its the Prototype guys we should be after here. :-)

The new fix requires a change to both prototype.js and a small Rails core update.

I will start with the update to prototype.js. Search for Abstract.EventObserver (around line 3632 in my version). That should now read

Abstract.EventObserver = Class.create({
  initialize: function(element, callback, trigger) {
    this.element  = $(element);
    this.callback = callback;
    this.trigger = trigger;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element, this.trigger);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element, trigger) {
    if (trigger == '') {
      if (element.type) {
        switch (element.type.toLowerCase()) {
          case 'checkbox':
          case 'radio':
            Event.observe(element, 'click', this.onElementEvent.bind(this));
            break;
          default:
            Event.observe(element, 'change', this.onElementEvent.bind(this));
            break;
        }
      }
    }
    else Event.observe(element, trigger, this.onElementEvent.bind(this));
  }
});

Since we’re passing the parameters explicitly now, we must make a change to the build_observer method in ActionView::Helpers::PrototypeHelper. (vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb)

This method is adding the “on” parameter to the EventObserver initialization only if the “on” option is passed in. Yes, even though prototype isn’t supporting it in its current state. So, remove the if options[:on] from the end of line 779 of prototype_helper.rb. The method should now look like this:

def build_observer(klass, name, options = {})
  if options[:with] && (options[:with] !~ /[\{=(.]/)
    options[:with] = "'#{options[:with]}=' + value"
  else
    options[:with] ||= 'value' unless options[:function]
  end

  callback = options[:function] || remote_function(options)
  javascript  = "new #{klass}('#{name}', "
  javascript << "#{options[:frequency]}, " if options[:frequency]
  javascript << "function(element, value) {"
  javascript << "#{callback}}"
  javascript << ", '#{options[:on]}'"
  javascript << ")"
  javascript_tag(javascript)
end

That should do it. Someone please let me know if this doesn’t work for them.

UPDATE: 12/14/2007

As an alternative to modifying core, you can always extend Prototype by creating your own customer observer that takes in the trigger argument shown above. Doing this prohibits you from using the built in Prototype helper method in rails (observe_field), but if you don’t feel comfortable modifying core this is a way around it.