You've used the areatext tag for raw text before, but Ella Furlong suggests using it as a column with unlimited rows. When you do this, you don't have to guess how many entries your user would like to make at one time, and the user doesn't have to press the equivalent of "continue" to feed you more entries if you've guessed wrong. An additional benefit of this technique is that the user can paste multiple entries into the field at once.
Finally, giving users feedback about their entries before pressing the submit button saves them time, saves bandwidth, and keeps the load off of your server. Validating E-mail address formats is a common request; so, this demo does that. Call this code getMany.cfm.
<script language="javascript">
<!--
// Confirms correct E-mail addresss format.
function mailcheck(myField, myName)
{
fullField = eval('document.demo.'+myField);
The replace function seeks a regular expression in a value and replaces things that match this expression. The up-carat matches the beginning of a string. Adding an asterisk to a group says you want to match zero or more occurences of the group; the group backslash-s matches any white space (tabs, spaces, etc.). Therefore, the first replace statement trims leading white space. The dollar sign matches the end of a string. Therefore, the second replace statement trims trailing white space. If you're left with nothing when this is done, the function exits successfully; there's no bad address to complain about.
fullField.value = fullField.value.replace(/^\s*/, ''); fullField.value = fullField.value.replace(/\s*$/, ''); if (fullField.value.length < 1) return true;
Now because this field can contain many addresses, let's parse it. First convert all white space (g for globally, i for case-insensitive) to spaces. Then assign the values that aren't spaces to elements in an array by using the split function. Finally, begin to check each element. Just before each barrage of checks, add 1 to the element number (which starts at zero) to yield an address number (starting with 1) in case you need to tell the user which address you didn't like.
mySep = /[\s]+/gi;
myText = fullField.value.replace(mySep, ' ');
mySplit = myText.split(' ');
nr = 0;
for (i = 0; i < mySplit.length; i++)
{
nr = i+1;
A proper E-mail address has a specific format. It consists of chunks of mostly letters and numbers with an occasional underline or hyphen that doesn't touch the outside of the chunk. Because this is not a simple concept, it makes sense to define a chunk as a single character to hide this complexity. This text uses a percent sign, something that shouldn't be in an E-mail address anyway, as that character. So, step one is to complain if that character is in the address. The match function sees if it can find a regular expression in a string.
if (mySplit[i].match(/%/))
{
alert(myName+' '+nr+' has %');
return false;
}
The address must end in a dot followed by two to four characters. The i gives you a case-insensitive search; the dollar sign says what you're looking for must touch the end of the string. It's looking for two to four characters in the range from a to z. That whole mess must start with a dot. Because a period has a special meaning in regular expressions, it's preceded with a backslash to take that special meaning away. The match is testing element i. Finally, the exclamation point negates the function; so, the "if" is satisfied when the match does NOT occur (i.e., the desired suffix is MISSING).
if (!mySplit[i].match(/\.[a-z]{2,4}$/i))
{
alert(myName+' '+nr+' has a bad suffix');
return false;
}
At long last, here's the main event. Use g and i to make this a global, case-insensitive search. Define a match as one or more occurrences of numbers and/or letters. Alternatively, define a match as two groups of numbers and/or letters surrounding hyphens and/or underscores. Then, replace each match with a percent sign [%].
Now make sure that the address:
- begins with zero or more occurrences of [%.],
- next has a % followed by an "at" sign,
- next has zero or more occurrences of [%.],
- and ends in [%.%].
Finally, close the for loop and the function.
chunk = /([0-9a-z]+[-|_]+[0-9a-z]+)|[0-9a-z]+/gi;
mySplit[i] = mySplit[i].replace(chunk, "%");
// uncomment to understand // alert(mySplit[i]);
if (!mySplit[i].match(/^(%\.)*%+@{1}(%\.)*(%+\.%)+$/))
{
alert(myName+' '+nr+' has the wrong format');
return false;
}
}
return true;
}
The form has two input fields: doers (labeled "TO") and watchers (labeled "CC"). Therefore, consider will run mailcheck against both of these fields. If a check fails, the form doesn't get submitted. If the checks are OK, it does. The double slash is a javascript comment to hide the closing HTML comment from javascript. Closing the script tag brings us back to work on the rest of the page.
function consider()
{
if (!mailcheck('doers', 'TO')) return false;
if (!mailcheck('watchers', 'CC')) return false;
document.demo.submit();
return true;
}
//-->
</script>
The textarea tag has an opening tag, a value, and a closing tag. In order to display the value, wrap it in cfoutput tags. Close the tag.
When you've done these things for both fields (with a break in between), add a button and close the form. Notice that this is not an input submit tag; it's a button tag. Its onClick attribute calls consider to test the fields and perhaps submit the form. Close the form.
<h3>Paste E-mail Lists into Windows</h3>
<cfparam name="form.doers" default="">
<cfparam name="form.watchers" default="">
<form name="demo" action="getMany.cfm" method="post">
TO: <textarea name="doers" wrap="physical" rows="3" cols="60"
onChange="mailcheck('doers','TO')"><cfoutput>#trim(form.Doers)#</cfoutput></textarea><br>
CC: <textarea name="watchers" wrap="physical" rows="3" cols="60"
onChange="mailcheck('watchers','CC')"><cfoutput>#trim(form.watchers)#</cfoutput></textarea>
<input type="button" name="run" value="OK" onClick="consider()">
</form>
<h3>DOERS: "TO" ADDRESSES</h3> <cfloop list="#form.doers#" index="address" delimiters=" #chr(10)##chr(13)#"> <cfoutput>#address#</cfoutput><br> </cfloop> <p> <h3>WATCHERS: "CC" ADDRESSES</h3> <cfset nr=0> <cfloop list="#form.watchers#" index="address" delimiters=" #chr(10)##chr(13)#"> <cfoutput>#address#</cfoutput><br> </cfloop>
Consider that the only thing left out to make this a complete cycle is getting the addresses into and out of a database. You've already proven you can confirm the format of the data on the client side and can then break it into separate addresses, one per row. You can do the row insert inside the loop that breaks them up.
You have a couple of options when you send them back to the user from the database. If you use the textarea tag for re-display and edit/delete, the simplest approach is to handle changes to individual addresses as a delete-insert combination. The other option is to bring them back the rows in a multi-line form using conventional tags where you can readily tell if the intent was to change an existing row or to delete the old value.
This general approach isn't limited to addresses, of course. Imagine letting your users paste data from documentation tools or old reports directly into your database using a Web page. Imagine being able to let users make extended entries without having to bring up yet another page to accommodate the extra data. Try this technique and tell others what you've learned. =Marty=
[This demonstration has been updated to accommodate top-level domains longer than three characters (e.g., ".info").]