Search code examples
javascriptprototypejs

TypeError: $(...).on is not a function with prototype.js


I want to add a filtered listener to the change event of a form's children but I am getting weird results from the $ selector.

I call the selector with the id of the form $("exportForm") and try to call the .on(...) method on it, getting the error in question.

Inspecting the returned element I seem to find an array with numbers as ownProperties names. Indexing them in the console $(...)[1] returns the single children of the form. In the proto property there seem to be no trace of Prototype.js methods which should be added by the selector.

What is going wrong? What to look for to get it working?

PS: Prototype.js version is 1.6.1


Solution

  • You're right. on() was added in 1.7, so you can't use it here. What the on method gives you is "lazy evaluation". When you call

    $(document).on('eventName', 'css selector', function(){ ... });
    

    ...you get an observer that doesn't have to be initialized after the page loads, or after parts of the page are replaced by Ajax callbacks. You can replace a part of the page, then click on it again, and the observer will just work.

    The old way to set up an observer was this:

    $(document).observe('dom:loaded', function(){
      $('theId').observe('eventName', function(){
        // do something
      });
    });
    

    The outer observer waits until the page is finished loading, then the inner one observes some object for an event and does something with that. This will work, as long as the DOM doesn't change after it loads. If that happens, then you have to re-register the inner listener manually, since its targets have changed, and it no longer will work. The dom:loaded event only fires once, when the page itself is loaded the first time.

    In your case, it seems that you want to duplicate the behavior of the Form.Element.Observer() class method in Prototype, which sets up a timed poll of all a form's child elements, and fires a callback when any of them change. You really ought to look at the documentation for that method, as it is a really bulletproof way to do what you're trying to do. But in case you really want to roll your own, here's how you would write an observer that could listen for events on multiple elements:

    $(document).observe('dom:loaded', function(){
      $$('#exportForm input').invoke('observe', 'change', function(evt, elm){
        // evt is the event itself, you can cancel, log, whatever
        // elm is a reference to the form element, you can do elm.getValue(), etc.
      });
    });
    

    This uses the "double-dollar" method to get an array of elements, so any form input inside the form with the ID exportForm is captured.

    invoke() applies the same callback and arguments to an array of elements, so this is setting up a separate observe method for each form element -- not efficient if you have lots of inputs! You can try to listen for the change event on the form itself, so you only have one observer method, but depending on the browsers you need to support, you may find that these events don't always bubble from the individual form input up to the form itself. You'll have to test carefully.

    Each form element observed will be seen in isolation: you won't have access to the rest of the form inputs from within the callback, just that one.

    That's why the Form.Element.Observer is so powerful -- it gives you a hash of the entire form each time it fires, so you can do things like create previews or validations on a frequent-enough basis to appear "live".