Sue wrote on 14 Dec 2003 at Sun, 14 Dec 2003 20:34:47 GMT:
After spending several hours trying to make and understand the
changes you suggested I became totally lost in trying to follow
your second page of instruction. If you have the time I would
greatly appreciate your help and explanations on getting this
thing to work.
My apologies. I thought that the second post might have been
overkill, the inclusion of the break statement (to prevent your,
'name is invalid' message from being repeated) was important to note.
This time, I'll try to make a better effort to explain what I
present. It's easy to take for granted some knowledge, particularly
when you don't know the other person. I'm sorry if I now go the other
way and explain too much (and sound patronising), but it'll be safer.
What I'll do is present a simple version, then progressively refine
it, explaining the differences. If I go too far, you can simply
ignore it for now and use what you do understand. That way, you'll
only be using code that you could write yourself. As I'm sure you
know, blind copying and pasting never does anyone any good.
As I mentioned in my posting setting up and calling functions is
giving me real problems. So an explanation on how and when to
call a second function if I take you suggestion of using the
function validate(FirstName) when I also have to validate the
lastname.
From what you showed, it seems that the validation for both the first
and surname is the same. Because of this, there will be one function
to do the common validation, and another that will call it for the
two fields. This will allow for different messages for the different
fields.
I didn't know how you're performing the validation when I made the
first two posts (I didn't follow your other threads). However, as
you're validating on submission (something I should have assumed - my
fault), one central function is better.
<FORM ... name="example" onsubmit="return validate(this)">
<INPUT ... name="first name">
<INPUT ... name="surname">
<!-- Just so you know (for in a moment),
this sentence is in an SGML comment -->
<!-- Place in HEAD. -->
<SCRIPT type="text/javascript">
// Notice that there are no SGML comments here. The likelihood of
// encountering a browser that doesn't support in-line scripts is
// *incredibly* low, and using such comments can sometimes do more
// harm than good, so you can safely remove them. You can also
// avoid problems by placing the script in a .js file, and using
// the src attribute in a SCRIPT element to include it.
// Validates the form. If it returns false, the form will not be
// submitted. If it returns true, the form will submit as normal.
function validate( form ) {
// 'form' contains a reference to the form above and will provide
// us with a way to access the controls. It is equivalent to:
// var form = document.forms['example'];
// The function, isNameValid, is described later, but it's name
// (and purpose) is fairly self-explanatory.
if ( false == isNameValid( form.elements['first name'].value )) {
// window is an object that represents the browser window that
// contains the page (represented by document). It is always
// accessible, and contains information such as the status bar
// text, and what frames that window contains. alert is simply
// one of its methods.
window.alert( 'Please enter a valid first name.' );
// If you replace "form" with "document.forms['example']",
// these are exactly the same as the lines in my previous
// posts.
form.elements['first name'].value = '';
form.elements['first name'].focus();
// Returning false will cancel the submission of the form.
return false;
}
if ( false == isNameValid( form.elements['surname'].value )) {
window.alert( 'Please enter a valid surname.' );
form.elements['surname'].value = '';
form.elements['surname'].focus();
return false;
}
// Continue other validation steps. Return false (like above) if
// the validation fails. Do nothing if it succeeds (so that the
// return statement below is executed).
return true;
}
// Validates a name (string). If the name is valid, this function
// returns true. If not, it returns false.
function isNameValid( name ) {
// Here, unlike your original function, name is a string, not an
// object. As this function only checks the value of a control
// and doesn't modify that control, only the value is needed.
var n = name.length; // Length of the string
var valid = true;
var validCharacters =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
// In this statement below, there is little difference between
// i++ and ++i. Using the pre-increment operator (++i) can
// sometimes be quicker (probably isn't here), so I use it out of
// habit. Does your text explain the difference between the two
// and, more importantly, does it do it well?
for (var i = 0; i < n; ++i) {
// Using .substr( i, 1 ) or .substring( i, i + 1 ) is
// equivalent to .charAt( i ), but as the first two are
// designed to return more than one character, it is more
// appropriate to use charAt (and probably quicker, too).
var temp = name.charAt( i );
// As originally, if the character being checked isn't part of
// the allowed set, assign false to 'valid'.
if ( -1 == validCharacters.indexOf( temp )) valid = false;
// ! is a logical operator. It inverts a boolean value (true
// becomes false, and visa versa). It also works on non-
// booleans, by converting them to a boolean first, then
// inverting[1]. You can read the below as: if not valid
// then...
if ( !valid ) return false;
}
// The statement above immediately exits the function with the
// value false as soon as an invalid character is found. If we
// get this far, the string is valid.
return true;
}
</SCRIPT>
In your attempt (which I've snipped as this is a long post), you were
nearly correct here:
if (ok) {
var ok = true;
// I am guessing that this will reset "ok" before starting to
// check lastname.
All you needed to do was:
if (ok) {
1) The variable, ok, was already defined, so there was no need to do
it again with var ("ok = true" would have been fine).
2) For that statement to be executed, ok would already have to be
true, so there's no need to give it a value it already has.
While what I've shown above should work exactly as you want it to, it
isn't what you'd find on the Web, and it's not what I'd write. So,
from here on out, I'll be refining the code until I end with the
'regular expression' version I mentioned before. If there's anything
below that I didn't explain thoroughly enough and you'd like to
understand, do ask.
The validate function is the easiest to start with; there's only one
change that I'd make. I've reproduced the whole function, though.
function validate( form ) {
// Just as "if ( boolean == true )" can be reduced to
// "if ( boolean )", we can change
// "if ( false == isNameValid(...))" to
// "if ( true != isNameValid(...))", which can be reduced to
if ( !isNameValid( form.elements['first name'].value )) {
// That would be read as, "if not isNameValid"
window.alert( 'Please enter a valid first name.' );
form.elements['first name'].value = '';
form.elements['first name'].focus();
return false;
}
if ( !isNameValid( form.elements['surname'].value )) {
window.alert( 'Please enter a valid surname.' );
form.elements['surname'].value = '';
form.elements['surname'].focus();
return false;
}
// Other validation steps go here, returning false if they fail.
return true;
}
Initially, the isNameValid function is just as easy to modify. If you
look at the 'valid' and 'temp' variables, you can see that they both
are used and modified once each (ignoring the initialisation of
'valid'). This means that they're presence is not needed. A variable
is only really needed when its values will be read or modified
several times during its life-time[2]. To correct this, it identifier
will be replaced by the expression that was assigned it (that should
make sense in a moment).
First, temp is removed:
function isNameValid( name ) {
var n = name.length;
var valid = true;
var validCharacters =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (var i = 0; i < n; ++i) {
if ( -1 == validCharacters.indexOf( name.charAt( i )))
valid = false;
if ( !valid ) return false;
}
return true;
}
Notice that "name.charAt( i )" (the value assigned to 'temp') has now
replaced 'temp'. Removing valid is a bigger leap, so it will be done
in two stages. If you remember in my second post, I said that one
should never use an if statement to assign to a boolean (like the
first of the two if statements above does). To see why, consider
this:
if ( boolean ) {
something = true;
} else {
something = false;
}
If 'boolean' is true, 'something' will be assigned true. If it is
false, false will be assigned. It should be obvious that the same
thing can be done this way:
something = boolean;
With 'valid', we have this situation:
if ( boolean ) {
something = false;
} else {
something = true;
}
'valid' is assigned the opposite of the if expression: if the index
*is* -1, valid is false. So if the index *is not* -1, valid is true:
function isNameValid( name ) {
var n = name.length;
var valid = true;
var validCharacters =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (var i = 0; i < n; ++i) {
// If the index is not -1, valid is true:
valid = ( -1 != validCharacters.indexOf( name.charAt( i ));
if ( !valid ) return false;
}
return true;
}
We can now apply to 'valid' what we did to 'temp':
function isNameValid( name ) {
var n = name.length;
var validCharacters =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (var i = 0; i < n; ++i) {
if ( !( -1 != validCharacters.indexOf( name.charAt( i )))
return false;
// You should notice that there are two NOT (!) operators
// above (well, 'not' and 'not equal'). These cancel each other
// out, so the above can (and should) be written as:
// if ( -1 == validCharacters.... )
}
return true;
}
That is a much more compact function, but it can still be improved by
one more thing: use of regular expressions. In fact, the operation is
trivial with their use. I'll present the solution first, then explain
it. You might want to find a good reference for regular expressions.
Netscape's JavaScript Guide[3] does a fairly good job of explaining
them. Someone else might have a better guide though (if they get this
far into the post!
function isNameValid( name ) {
return /^[a-z]+$/i.test( name );
}
That's it! Pretty neat, huh? However, time to explain it...
First of all, that is a literal regular expression. The two forward
slashes (/) denote it. You can also create them with the RegExp
object, but literals should be compiled by default (rather than call
the RegExp.compile() method), so they execute faster. The
..test( name ) is a normal method call (test returns true if the
string, name, matches the expression, false otherwise), so the part
before that, "/^[a-z]+$/i" is the actual expression.
After the second slash, you can specify flags. JavaScript has three:
g, which makes the match global (repeated throughout the string); i,
which makes it case-insensitive (so 'a' and 'A' are the same); and m,
which caters for multi-line strings (that's new, though). I used i as
you allow both character cases.
Brackets ([]) specify a character set. This can be a range or
individual characters: [abcd] is the same as [a-d]. So far, we are
looking for a match with any character (only one, though) between A
and Z, irrespective of case.
We want more that one character so this is where the plus (+) comes
in; it means that one or more of the preceding character (or set, in
this case) must match.
Finally, the carat (^) and dollar ($): these mean that the match goes
from the beginning of the string (^) to the end of the string ($),
respectively.
So overall, /^[a-z]+$/i means: look for a match where the whole
string contains one or more alphabetic characters, in any letter
case. If you want to allow an empty string, replace the plus with an
asterix (*); this allows zero or more characters in the set.
As you can see, regular expressions can be extremely powerful; I
replaced six statements in the reduced function with one. However,
they can be *very* complex, but you can break them down like I did.
The best advice though, would be to explain in plain English what
they do - it makes far more sense.
Hope that helps,
Mike
[1] I can explain that in more detail separately, if you want.
[2] There is at least one exception to that rule, and the variable,
n, is an example. In many languages, accessing an attribute
(property) of an object takes more time and processing than that
of a local variable. To increase efficiency, particularly with
values that won't change (like the length of the string in
isNameValid), these values can be stored and referenced locally,
removing the need to query the object for the value. There may
not be much difference with JavaScript (it isn't like a compiled
executable), and it would really depend upon the implementation
of the interpreter whether this kind of local storage would be
beneficial. It is one of my habits, but shouldn't have any
negative impact.
[3] I used an older version of the guide (v1.3). This is a link to
the Regular Expression chapter in the newest guide (v1.5). It's
wrapped, of course.
http://devedge.netscape.com/library/manuals/2000/javascript/1.5/guide
/regexp.html#1010922
Look at the examples and experiment with them. Create your own
expressions: you'll get the hang of it. When it comes to checking
for patterns in strings, nothing is better than a regular
expression.