The first argument indicates what we’re listening for—we want to run this code when our form is submitted. The second argument is our responder—the name of the function that will get called when the form’s submit event fires. Add the submitEntryForm function and the observe call to breakfast.js. Save, go back to your browser, reload the page, and . . . what? Error? (See Figure 5-3.) Figure 5-3. Guh? Of course it’s defined! It’s right there on the page! I’m staring straight at it! Firebug can tell us what went wrong. Select the Script tab, click Options, and then click Break on All Errors. This way you’ll know exactly when that error happens. Reload the page. Almost immediately the page load will halt, as Firebug points you to the offending line (see Figure 5-4). Figure 5-4. When Break on All Errors is on, any error in your code is treated as a debugger breakpoint. CHAPTER 5 ■ EVENTS 97 The Script tab is Firebug’s JavaScript debugger. We’ve just set a breakpoint, paus- ing the evaluation of scripts (and rendering in general) at a certain spot. From here, we can resume the page load, step through functions one by one, and even use Fire- bug’s console. But right now we don’t need to do any of that—the screen tells us all we need to know. Notice how the viewport is empty. None of our content is there. At the time we tried to set the event, the element we were referencing hadn’t yet been created. This is an easy trap to fall into. Script tags are typically placed in the head of an HTML document. In the common case where a script needs to modify stuff in the body of a document, it’s got to wait. OK, new plan—we’ll add our listeners when the document is fully loaded, so that we can be sure that the entire DOM tree is at our disposal. Take the offending line of code and place it inside a function: function addObservers() { $('entry').observe('submit', submitEntryForm); } Now we can set this function to run when the page loads using the load event: Event.observe(window, 'load', addObservers); Make these changes to breakfast.js, and then reload the page. Our error is gone— and, mor e to the point, the Ajax form submission works this time! Wait, no. Never mind. Something else is wrong (see Figure 5-5). What could be causing this? The only thing on the page is the HTML fragment that should’ve been injected into our other page. Look at the address bar. When we submitted the form, the browser went to break- fast.php, the URL in the form’s action attribute. Following that URL is the submit event’s default action. That means we’re at fault again. When we submitted the form, submitEntryForm was called as we intended. But we didn’t hijack the submit event; we just listened for it. If we want to suppress this default action, we must explicitly say so. CHAPTER 5 ■ EVENTS98 Figure 5-5. This is the HTML fragment we wanted, but it’s on its own page. Using Event#stopPropagation, Event#preventDefault, and Event#stop To pull this off, we’re borrowing a couple of methods from the DOM2 Events spec. Internet Explorer doesn’t support these events natively, but we can fake it on the fly— augmenting Internet Explorer’s event object with instance methods the same way we augment DOM nodes with instance methods. First, we add an event argument to our handler so that we can use the event object. (We could have done this from the start, but we didn’t have a use for it until just now.) Then, at the end of the handler, we tell the event not to do what it had originally planned. function submitEntryForm(event){ var updater = new Ajax.Updater({ success: 'breakfast_history', failure: 'error_log' }, 'breakfast.php', { parameters: { food_type: $('food_type').value, taste: $('taste').value } }); event.preventDefault(); } CHAPTER 5 ■ EVENTS 99 Prototype gives you two other methods to control the flow of events: • Normally, events start deep in the DOM tree and “bubble” up to the top (e.g., click- ing a table cell will also fire an event in that cell’s table row, in the table body, in the table, in the table’s parent node, and so on all the way up to window). But you can halt the bubbling phase using the stopPropagation method. • When you need to stop the event from bubbling and prevent the default action, use Prototype’s custom stop method. It’s a shortcut for calling both stopPropagation and preventDefault. OK, let’s try one more time. Reload index.html and try to submit a breakfast log (see Figure 5-6). Figure 5-6. Finally, we can submit meal information without having to reload the page! Eureka! That was easy, right? Right? Be aware: The behavior layer of web development (JavaScript) is far more complex than the structural layer (HTML) or the presentational layer (CSS). Ordinary web pages CHAPTER 5 ■ EVENTS100 are snapshots—the server sends it, the browser renders it, and it’s done. Pages that make use of JavaScript, however, have some aspect of mutability to them. The page may be loaded, but it’s never done. You will run into problems like the ones we encountered in this example. You will make mistakes simply because all this may be new and unfamiliar. Don’t get discour- aged! Rely on your tools—Firebug, Microsoft Script Debugger, and even the trusty alert dialog—to get you out of the quagmire. A Further Example We’ll keep coming back to events in subsequent chapters, since they’re a part of every- thing you do in DOM scripting. But let’s add just one more thing to our page. Being able to post entries without leaving the page is quite handy, but what if you’re just there to read your old entries? In the interest of removing clutter, let’s hide the form by default, showing it only if the user asks for it. Let’s assign an id to the Log Your Breakfast heading so that we can grab it easily. Let’s also write some CSS to make it feel more button-like and invite clicking. // HTML: <h2 id="toggler">Log Your Breakfast ↓</h2> // CSS: #toggler { cursor: pointer; border: 2px solid #222; background-color: #eee; } We also want the form to be hidden when the page first appears, so let’s add a handler that will hide the form when the page loads: Event.observe(window, "load", function() { $('entry').hide(); }); And the last ingredient is a handler for the new link’s click event: function toggleEntryForm(event) { $('entry').toggle(); event.preventDefault(); } The toggle method conveniently alternates an element between hidden and shown. (In other words, it will show hidden elements and hide shown elements.) Note the use of CHAPTER 5 ■ EVENTS 101 preventDefault—since we don’t want the browser to follow the link, we’ve got to suppress the default action. We can assign this event just like we assigned the other one—with our addObservers function: function addObservers() { $('entry').observe('submit', submitEntryForm); $('toggler').observe('click', toggleEntryForm); } Now two events will be assigned on page load. Save breakfast.js, reload index.html, and marvel that this exercise was much easier than the last (see Figure 5-7). Figure 5-7. Each click of the link will toggle the display state of the form. Events and Forms A whole group of events is devoted to the user’s interaction with form elements. These can be tricky to manage, but they also stand to gain the most from UI enhancements. Client-Side Validation In Chapter 4, we wrote some PHP to check the submitted values on the server side. If the user had left either field blank, the submission would have been invalid, and the server would have sent back an error HTTP status code. CHAPTER 5 ■ EVENTS102 . just set a breakpoint, paus- ing the evaluation of scripts (and rendering in general) at a certain spot. From here, we can resume the page load, step through functions one by one, and even use. argument to our handler so that we can use the event object. (We could have done this from the start, but we didn’t have a use for it until just now.) Then, at the end of the handler, we tell. node, and so on all the way up to window). But you can halt the bubbling phase using the stopPropagation method. • When you need to stop the event from bubbling and prevent the default action, use