This tutorial shows you how to use the Closure Library to listen for and respond to JavaScript events. You'll see how events work in general, and learn specific JavaScript techniques for making your code scalable and maintainable.
Events in JavaScript and Closure Library
When users type or use the mouse in JavaScript programs, JavaScript generates event objects to represent the user's actions. These events are distributed to DOM objects. To make DOM objects respond to events, you attach event listeners to them, which are simply JavaScript functions. In your program, you specify event listeners and which events they should handle. For example, when the user clicks a link, a click event is sent to the click listener for the DOM object that represents the link.
The event model is part of JavaScript, but various browsers implement events in different ways. For example, most browsers pass an Event object as a parameter to listener functions, but Internet Explorer stores an Event object in a Window property instead of passing it as a parameter.
Closure Library provides the advantage of a consistent event model that works the same way in all browsers. Closure Library implements its own event model that offers uniform behavior by hiding the variations in the event models of different browsers, so you only have to consider one set of behaviors when writing your program. In addition, Closure Library's event model corrects for problems in browsers such as potential memory leaks in nested functions in Internet Explorer.
How Events Work
The Closure Library event handling process begins when an action takes place that generates an event, such as the user clicking on a link. A click event object is created to represent the event, and the event target is determined – in this case, the target is the link the user clicked on.
The event object's properties contains relevant information about the event. The event object is passed as an argument when event listeners are called. Closure Library represents browser events with event objects of class goog.events.BrowserEvent
.
Events are dispatched in two phases: first, the capture phase, and then the bubble phase. In the capture phase, the event is dispatched first to the root element of the DOM, and then down the DOM hierarchy until the target itself is reached. Any elements that are listening for capture phase events will have their listeners called in DOM hierarchy order during this phase.
After the capture phase is completed, the bubble phase takes place. In the bubble phase, the event is dispatched to the event target and then up the DOM hierarchy to the root element. In this phase, listeners listening for bubble phase events and listeners without an assigned phase are called.
In Closure Library, you use goog.events.listen()
to assign an event listener to an object. The full signature of listen()
is:
goog.events.listen(eventSource, eventType, listener, [capturePhase, [handler]]);
eventSource
is the DOM object you want the event listener attached to.eventType
is a String or array of Strings defining the types of events that will trigger the event listener.listener
is the event listener itself, the function that will be called when the specified event is dispatched to the object.capturePhase
is an optional parameter that you should set totrue
if you want the listener to be called only during the capture phase.handler
is an optional parameter that lets you specify which object is represented bythis
within the listener function.
Handling Events
In this section we'll create a sample notepad application that opens text for editing when the user clicks on the text. We'll build on the application from the previous tutorial. You can find the source files for this tutorial here:
- notepad2.js (click to download)
- notepad2.html (click to download)
Our notepad sample displays text as document elements. To make the text editable, we want to reveal a <textarea>
element for editing the text when the user clicks on the text. To do this, we'll attach an event listener to the text content element when we create the element. An event listener is simply a function that's called when a specified event occurs.
Closure Library has its own event framework that resolves the incompatibilities in the event models of different browsers. To use this framework, call goog.events.listen()
to attach a listener function to the element.
For example, the following call to goog.events.listen()
ensures that a click on a note triggers a call to openEditor()
:
goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor);
The first argument is the element on which we want to listen for events. The second argument is the type of event we're listening for. The third argument is the function to call when the given event occurs.
Here's an example listener function that hides the content element and displays the editor element:
tutorial.notepad.Note.prototype.openEditor = function(e) { var elt = e.target; // Get the current contents of the note text Element, so we can put it into // the editor field. var content = goog.dom.getTextContent(elt); // Given the way we've built our DOM structure, the editor div // will be the next Element after the note text Element. var editorContainer = goog.dom.getNextElementSibling(elt); var editor = goog.dom.getFirstElementChild(editorContainer); // Put the note contents into the editor field. editor.innerHTML = content; // Hide the note text Element and show the editor. elt.style.display = "none"; editorContainer.style.display = "inline"; };
The file notepad2_1.js contains the updated version of our sample.
Using Instance Methods
In our openEditor
function, we have to find the content and editor elements every time the function is called. We find these elements by looking in the DOM structure. But we already store references to these elements as instance fields in our Note
objects when we create them:
tutorial.notepad.Note.prototype.makeNoteDom = function() { // Create DOM structure to represent the note. this.headerElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.title); this.contentElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.content); // Create the editor text area and save button. this.editorElement = goog.dom.createDom(goog.dom.TagName.TEXTAREA, null, ''); ... }
Why can't we just use these references in the body of openEditor()
? Unfortunately, by default an event listener is executed in the context of the event target. In other words, when the user clicks on the note element, this
refers to the note element within the body of openEditor()
, even though openEditor()
is a method of Note
.
Closure Library provides a solution to this problem. To enable the use of object methods as event listeners, Closure Library allows a context object as an optional parameter to goog.events.listen()
. For example, the following call attaches the openEditor()
listener in such a way that this
refers to the Note
object within the body of openEditor()
:
goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor, false, this);
The fifth argument supplies the object to serve as this
in the body of the listener.
We can attach our listeners in the same makeNoteDom()
method where we create the elements. In our sample, replace tutorial.notepad.Note.prototype.makeNoteDom()
with the following method:
tutorial.notepad.Note.prototype.makeNoteDom = function() { // Create DOM structure to represent the note. this.headerElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.title); this.contentElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.content); // Create the editor text area and save button. this.editorElement = goog.dom.createDom(goog.dom.TagName.TEXTAREA, null, ''); var saveBtn = goog.dom.createDom(goog.dom.TagName.INPUT, {'type': 'button', 'value': 'Save'}, ''); this.editorContainer = goog.dom.createDom(goog.dom.TagName.DIV, {'style': 'display:none;'}, this.editorElement, saveBtn); this.contentContainer = goog.dom.createDom(goog.dom.TagName.DIV, null, this.contentElement, this.editorContainer); // Wrap the editor and the content div in a single parent so they can // be toggled in unison. var newNote = goog.dom.createDom(goog.dom.TagName.DIV, null, this.headerElement, this.contentContainer); // Add the note's DOM structure to the document. this.parent.appendChild(newNote); // Attach the event listener that opens the editor. // CHANGED: We need to preserve the meaning of 'this' when the listener is called. goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor, false, this); // NEW: goog.events.listen(saveBtn, goog.events.EventType.CLICK, this.save, false, this); // Attach the Zippy behavior. this.zippy = new goog.ui.Zippy(this.headerElement, this.contentContainer); };
Because Closure Library lets us preserve the meaning of this
in our listeners, we can make them very simple. Add the following methods below makeNoteDom():
tutorial.notepad.Note.prototype.save = function(e) { this.content = this.editorElement.value; this.closeEditor(); }; tutorial.notepad.Note.prototype.closeEditor = function() { this.contentElement.innerHTML = this.content; this.contentElement.style.display = "inline"; this.editorContainer.style.display = "none"; }; tutorial.notepad.Note.prototype.openEditor = function(e) { this.editorElement.value = this.content; this.contentElement.style.display = "none"; this.editorContainer.style.display = "inline"; };
Our notepad sample can now edit and save notes! Your files should now look like this:
- notepad2_2.js
- notepad2_2.html (Note that we didn't modify the .html file in this tutorial. It's just re-linked here for your convenience.)