Handling events

Scripting web pages is primarily about handling events triggered by the user. These events include mouse events such as clicking or mousing over an element, keyboard events and document-wide events, such as detecting when the document has loaded. We use event listener functions to respond to users’ actions.

There is nothing special about event listeners, they are just regular functions that you bind to a particular event so you can run some code whenever that event happens.

Required files

  • http://yui.yahooapis.com/2.6.0/build/yahoo-dom-event/yahoo-dom-event.js
  • http://yui.yahooapis.com/2.6.0/build/selector/selector-beta.js
  • http://yoursite.com/ojay/js-class.js
  • http://yoursite.com/ojay/core.js

How do I respond to events?

Ojay makes this really simple, using the on method. You can use this method to listen for any event on an Ojay collection as follows:

            Ojay('h3').on('click', function(element, e) {
                alert('You clicked on: ' + element.node.innerHTML);
            });

Try clicking on one of the headings on this page to see this in action.

on takes two arguments: the event name (click, mouseover, etc) and a function that will fire whenever the event occurs. The function receives two arguments:

  • element – an Ojay collection wrapping the single element that fired the event
  • e – an Event object, which can be used to stop default behaviour and get information about the event

Note that the above code listens to all h3 tags in the document, but element only refers to the one that fired the event.

on takes an optional third argument that specifies the meaning of the this keyword inside the function. This becomes more useful when using classes and object oriented programming, but the following example should illustrate the idea:

            var firstPara = Ojay('p').at(0);
            
            Ojay('h1').on('click', function(element, e) {
                this.setStyle({color: 'red'});
            }, firstPara);

If you click on the big heading at top of the page, you should see the first paragraph turn red.

Getting the event target

While element in your callback function refers to the member of the collection that triggered the event, that element may not be the event’s target. For example, consider the following situation:

            <p>Lorem ipsum dolor sit amet, consectetuer adipiscing
            elit. Integer vehicula scelerisque est. <em>Vestibulum ante</em>
            ipsum primis in faucibus orci luctus et ultrices posuere
            cubilia Curae; Etiam tincidunt erat lobortis urna. Nam
            <strong>fermentum erat</strong> sed enim.</p>
            
            <script type="text/javascript">
              Ojay('p').on('click', function(element, e) {
                alert(element.node.tagName);
                alert(e.getTarget().node.tagName);
              });
            </script>

If you were to click on the paragraph, the first alert() will always display “P”. The second alert() will depend on which child element of the paragraph you clicked on. It could display “P”, “EM” or “STRONG” depending on where you click. This method can be used to set up event listeners for lots of elements just by listening to their common parent element. This registers fewer event listeners with the browser and thus uses a lot less memory.

Controlling event execution

You’ll often need to stop the browser running the default behaviour for an event, such as stopping it loading a new page when a particular link is clicked. Ojay gives you three methods on the event object for doing this.

  • stopDefault stops the event’s default behaviour from happening
  • stopPropagate stops the event bubbling up the DOM
  • stopEvent does both of the above

An example:

            Ojay('#someLink').on('click', function(element, e) {
              e.stopDefault();
              Ojay.HTTP.GET(element.node.href).insertInto('#foo');
              // etc...
            })

If all your callback does is stop the event, use one of Ojay’s pre-stored callback functions to improve readability and avoid wasting memory on new functions. They have the same names as the event methods:

            Ojay('#someLink').on('click', Ojay.stopEvent)
                .setStyle({fontSize: '12px'})

Using MethodChain

on() is one of a few special methods in Ojay that return a MethodChain object. This means you can chain methods after the on() call rather than using callback functions explicitly:

            // change H1 children's style when they are clicked
            Ojay('h1').on('click').children().setStyle({fontWeight: 'normal'})

If you pass an object as the last argument to on() in this situation, that object is used as the base of the chain:

            Ojay('a').on('click', Ojay.stopEvent, Ojay.HTTP)
                .GET('/index.html', {ajaxLayout: true})
                .insertInto('h1');

See the MethodChain page for more information on how to use it.

How to delegate events

Event delegation is the practise of getting a single DOM element to catch and handle events fired by any of its descendant elements. This technique can be used to minimize memory usage with bubbling events and allows event handlers to be applied to elements added to the document later through Ajax, since a fixed ancestor element can be used to catch events fired by its content even if the content changes. For more information, see this article on icant.co.uk.

Ojay’s event delegation is inspired by Dan Webb’s solution for jQuery, with a little added help for nested elements. To catch any clicks on a certain class of links, for example, you could do this:

            Ojay('a.external').on('click', ...)

but having a separate listener on every element (as this example creates) wastes memory, and won’t help you if any a.external elements are added to the page after this JavaScript call. A better approach, using event delegation:

            Ojay('body').on('click', Ojay.delegateEvent({
              'a.external': function(link, evnt) {
                // ...
              }
            }));

This creates a single listener (on the body element) that catches all the clicks you want to listen to. Inside the callback, link still refers to the event’s target element – the clicked link – and not the body element.

You can specify more selectors to be handled by a single delegator by simply listing them:

            Ojay('body').on('click', Ojay.delegateEvent({
              'a.external': function(link, evnt) {
                // ...
              },
            
              '.content p': function(para, evnt) {
                // ...
              }
            }));

For most purposes this works fine but say you have something like this:

            <a href="http://example.com" class="external">
                <img src="some-picture.png" />
            </a>

If someone clicks the link, the image will (more than likely) be the event target, not the anchor element. You could work around this by doing this:

            Ojay('body').on('click', Ojay.delegateEvent({
              'a.external, a.external *': function(element, evnt) {
                // ...
              }
            }));

This would work, but then if you want to refer to the link you need to find out whether the link or one of its children was clicked and do a bit of tree crawling to find the element you’re interested in. To make it easier, you can pass true as a second parameter to delegateEvent() and it will crawl back up the DOM tree until it finds a matching element.

            Ojay('body').on('click', Ojay.delegateEvent({
              'a.external': function(link, evnt) {
                // ...
              }
            }, true));

This will catch clicks on a.external elements, and any elements contained within them. The link argument will refer to the a.external element, rather than to the direct event target.

It should be noted that you do not necessarily need to use the body tag for event delegation, just any permanent element that contains the elements you want to monitor.