Imagine thateach of the text boxes has a state that’s either valid or invalid.. Both text boxes need to be valid before the form is submitted.. When the page loads, both text boxes are e
Trang 1We 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.preventDefaultcall to the top of the
function (see Figure 5-8)
Figure 5-8.The validation message we expected
Trang 2This 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’sEvent#elementmethod 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 submithandler the way it is, since the blurhandler 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;
}
Trang 3var 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 addObserversfunction
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 observemethod 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_typetext box and type some text Press
Tab to change focus, and notice how the red border disappears (see Figure 5-10)
Trang 4Figure 5-10.Only the second text box is invalid.
Now click inside the food_typetext 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
Trang 5Cleaning 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 submitEntryFormis
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—trueis valid; falseis 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 inputelements 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.getInputsis also available as an instance method on formelements (e.g.,
form.getInputs('text');)
The second line gets a little tricky Instead of using Array#eachto iterate over the text
boxes, we’re using Array#all Since validateTextBoxreturns 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 validateFormfunction) 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 returns false, we’ll know not to submit the form
Now we can simplify the code in submitEntryForm:
Trang 6function 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 validateFormreturns 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 onTextBoxBlurfunction 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