We don’t need a server round-trip here, though. We can just as easily check whether a field is blank on the client side. We want to code defensively, catching possible user mistakes as early as possible. We can perform this check when the form is submitted; we’ve already got a function handling that event. function submitEntryForm(event) { event.preventDefault(); if ($('food_type').value === '' || $('taste').value === '') { alert('Both fields are required.'); return; } var updater = new Ajax.Updater( { success: 'breakfast_history', failure: 'error_log' }, 'breakfast.php', { parameters: { food_type: $('food_type').value, taste: $('taste').value } } ); } Our handler now branches. If the two fields we’re looking at are empty, we show a message and stop; if not, we submit the form via Ajax. Either way, we want to stop the default action of the form, so we move the event.preventDefault call to the top of the function (see Figure 5-8). Figure 5-8. The validation message we expected CHAPTER 5 ■ EVENTS 103 This works just like we expected. But let’s try something a bit subtler. Imagine that each of the text boxes has a state that’s either valid or invalid. At any point in time, it’s either one or the other. Both text boxes need to be valid before the form is submitted. Let’s write a CSS rule for an invalid text box: input#food_type.invalid, input#taste.invalid { border: 2px solid #900; } So that we aren’t too subtle, an invalid text box will be shown with a thick red border. When the page loads, both text boxes are empty, but neither one can be called invalid yet, because the user hasn’t had a chance to enter text. But we know it’s definitely invalid if the text box receives focus, then loses focus, and is still empty. That’s when we should alert the user. There’s an event for losing focus—it’s called blur. So let’s use it. function onTextBoxBlur(event) { var textBox = event.element(); if (textBox.value.length === 0) textBox.addClassName('invalid'); else textBox.removeClassName('invalid'); } We use Prototype’s Event#element method to figure out which element received the event. The method ensures that we get an element node as the target, not a text node. When a text field is blurred, we make sure it has a nonempty value. If so, we add the class name to mark it as invalid. If not, the field is valid, so we remove the class name. We’re going to leave our submit handler the way it is, since the blur handler won’t catch everything. But let’s change it to use the new approach. function submitEntryForm(event) { event.preventDefault(); var valid = true; $('food_type', 'taste').each(function(box) { if (box.value.length === 0) { box.addClassName('invalid'); valid = false; } else box.removeClassName('invalid'); }); if (!valid) { alert('Both fields are required.'); return; } CHAPTER 5 ■ EVENTS104 var updater = new Ajax.Updater( { success: 'breakfast_history', failure: 'error_log' }, 'breakfast.php', { parameters: { food_type: $('food_type').value, taste: $('taste').value } }); } Don’t forget to attach the new handler. Add it to the addObservers function. function addObservers() { $('entry').observe('submit', submitEntryForm); $('toggler').observe('click', toggleEntryForm); $('food_type', 'taste').invoke('observe', 'blur', onTextBoxBlur); } Remember Enumerable#invoke? Here we’re using it to call the observe method on a collection of two elements, passing the last two arguments in the process. Now let’s do some functional testing to make sure this works right. Reload the page in your browser. First, try clicking the submit button immediately, before focus has been given to either text box. Figure 5-9 shows the result. Figure 5-9. Both text boxes are invalid. Works as expected! Now click inside the food_type text box and type some text. Press Tab to change focus, and notice how the red border disappears (see Figure 5-10). CHAPTER 5 ■ EVENTS 105 Figure 5-10. Only the second text box is invalid. Now click inside the food_type text box once again to give it focus. Delete the box’s value, and then press Tab. The text box should once again have a red outline, as shown in Figure 5-11. Figure 5-11. Both text boxes are once again invalid. You might consider all this redundant, since the server validates the data itself, but keep in mind that the client-side and server-side validations serve different purposes. The server is validating the data so that it can let the client know if the request was suc- cessful; this is useful no matter how the breakfast log entry gets posted. The client is validating the data so that it can present a helpful and humane UI. The two are not at odds with one another. Also, remember that client-side validation is not a replacement for server-side valida- tion. Ideally, you’d do both, but validating data on the server is essential. You’re in control of the server; you’re not in control of the client. CHAPTER 5 ■ EVENTS106 Cleaning It Up We could leave it like this, but if you’re code-compulsive like I am, you’ve probably noticed several redundant lines of code. The check we’re doing inside submitEntryForm is nearly identical to the one we’re doing inside onTextBoxBlur. By changing the way we observe the form, we can easily combine these checks into one. First, we’ll write a function that checks one text box for validity: function validateTextBox(textBox) { if (textBox.value.length === 0) { textBox.addClassName('invalid'); return false; } else { textBox.removeClassName('invalid'); return true; } } We’ll use the return value of this function to indicate whether the text box in question is valid or invalid— true is valid; false is invalid. Now we’ll write a function that checks the entire form for empty text boxes: function validateForm(form) { var textBoxes = Form.getInputs(form, 'text'); return textBoxes.all(validateTextBox); } The highlighted part introduces a new method: Form.getInputs. It accepts a form ele- ment as the first parameter and returns all of the input elements contained within. The second argument is optional; if present, it will return only inputs of the given type. Here we want the form’s text boxes, so the second argument is "text". Form.getInputs is also available as an instance method on form elements (e.g., form.getInputs('text');). The second line gets a little tricky. Instead of using Array#each to iterate over the text boxes, we’re using Array#all. Since validateTextBox returns a Boolean, we can look at the return values to figure out whether all text boxes are valid. If so, the statement (and thus the validateForm function) returns true; if not, false. So, this function doesn’t just mark our text boxes; it also returns a “good data/bad data” Boolean value. If the function r eturns false, we’ll know not to submit the form. Now we can simplify the code in submitEntryForm: CHAPTER 5 ■ EVENTS 107 function submitEntryForm(event) { event.preventDefault(); if (!validateForm('entry')) return; var updater = new Ajax.Updater( { success: 'breakfast_history', failure: 'error_log' }, 'breakfast.php', { parameters: { food_type: $('food_type').value, taste: $('taste').value } } ); } Our new code does the same task more succinctly. If validateForm returns false,we bail on the form submission so that the user can correct the errors (which have been helpfully outlined in red). Otherwise we proceed as planned. As a last step, we can rewrite the onTextBoxBlur function and save a couple lines of code: function onTextBoxBlur(event) { return validateTextBox(event.target); } We’ve done more than clean up our code in this section; we’ve also eliminated redundancy. Furthermore, the new code will continue to work even if we add extra text boxes to the form later on. Make your code as flexible as possible—it will save time in the long run. Custom Events I’ve saved the best part for last. Native browser events are purely reactive; they’re trig- gered by user action. Wouldn’t it be great if we could harness the event model and use it to make our own events? Let’s go back to our fantasy football example. Imagine being able to trigger an “event” in your code whenever the user changes his lineup, or whenever the lead changes in a game. You’d also be able to write code that listens for those kinds of events and calls han- dlers accordingly. If I were to get academic on you, I’d call this an event-driven architecture. I could also call it a publish/subscribe model (or pub/sub for short). No matter what I call it, the key idea is the decoupling of publisher and subscriber. The code that responds to these kinds of events doesn’t need to know how the event was triggered—the object sent along with the event will contain all the necessary information. I wouldn ’t be telling you any of this, naturally, if it were a pipe dream. Prototype introduced support for custom events in version 1.6. Using Prototype, you can fire CHAPTER 5 ■ EVENTS108 . because the user hasn’t had a chance to enter text. But we know it’s definitely invalid if the text box receives focus, then loses focus, and is still empty. That’s when we should alert the user. There’s. run. Custom Events I’ve saved the best part for last. Native browser events are purely reactive; they’re trig- gered by user action. Wouldn’t it be great if we could harness the event model and use. telling you any of this, naturally, if it were a pipe dream. Prototype introduced support for custom events in version 1.6. Using Prototype, you can fire CHAPTER 5 ■ EVENTS108