Rails' observe_field with :on - Rails 2.0
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.

Comments
You rock! I can't wait to
You rock! I can't wait to try it :)
observer_field
Exactly which prototype.js is suppose to be modified as I found several in my project:
public/javascripts/prototype.js:Abstract.EventObserver = function() {}
scripts/server/public/javascripts/prototype.js:Abstract.EventObserver = function() {}
server/script/public/javascripts/prototype.js:Abstract.EventObserver = function() {}
Also, does this solution fix the problem with the radio button where only the first click invokes the callback and never after?
Post new comment