I’ve been fiddling with the observe_field element in Rails using the :function and :on parameters. Basically, I simply wanted to execute some javascript when the onkeyup event fired for a text box. The Rails Documentation lead me to the two parameters that I needed:

  • :function, Instead of making a remote call to a URL, you can specify a JavaScript function to be called.
  • :on, Specifies which event handler to observe. By default, it's set to "changed" for text fields and text areas and "click" for radio buttons and checkboxes. Use this parameter to change the watched event to whatever you want e.g. "blur", "focus", etc..

However, the :on parameter didn’t appear to work, and after some further inspection of the Prototype code, I found that the parameter that the Rails code was generating wasn’t even being used in Prototype.js.

So, I’ve modified the Prototype code slightly, to make things work. Here are my changes.

Abstract.EventObserver = function() {}
Abstract.EventObserver.prototype = {
  initialize: function() {
    this.element  = $(arguments[0]);
    this.callback = arguments[1];
    this.trigger  = arguments[2];

    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() {
    var elements = Form.getElements(this.element);
    for (var i = 0; i < elements.length; i++)
      this.registerCallback(elements[i]);
  },

  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));
    }
  }
}

Notice the ‘trigger’ argument in the constructor that gets passed into the registerCallback function. Its only used if its supplied. I suppose you could also define the :on parameter while specifying a :url parameter in the observe_field to take further control of when you do your ajax calls. Haven’t tested that yet.

With these changes we are now able to add the observe_field in our view like so…

observe_field 'text_field_id', 
     {:function => "(value == '') ? 
         $('email_preview').innerHTML = 
              '<em>not entered yet</em>' :
         $('email_preview').innerHTML = 
              value.replace(/\\n/g, '<br/>');", 
     :on => 'keyup'}

I’ve added a little snippet that will replace carriage returns with HTML line breaks and also set a default value if no text is entered or the field gets cleared out.

Hope this helps someone else!