Let's be honest here. ColdFusion is a great development tool and language with a lot of capability, but it's missing an important piece: the ability to write custom functions. I'm not talking about custom tags or extending ColdFusion's abilities with outside code. I'm referring to a function that can be used in a tag, can be used in other functions and can be outputted to a page. I want to be able to do IsEmail(email) and have it return a yes or no. ColdFusion can't do this.
Actually, ColdFusion can do this, but it's very ugly. Writing custom functions involves hacking the way ColdFusion evaluates variables. To do this, you need an intimate understanding of how ColdFusion evaluates variables, functions and expressions as well as a twisted imagination. Once you have this, hacking custom functions will be relatively easy.
Let's start with a single assumption. We want to take an email address and evaluate if it is 'proper.' That is, we want to see if it is formatted correctly with the proper extensions and lack of 'wrong' characters. After hours of fighting with Regular Expressions (or about two minutes looking through CF-Talk) you have your code.
<CFIF Not
REFindNoCase('^([[:alnum:]][-a-zA-Z0-9_%\.]*)?[[:alnum:]]@[[:alnum:]][-a-zA-
Z0-9%\>.]*\.[[:alpha:]]{2,}$',email)>
<CFABORT showerror="Your email address is not formatted properly">
</CFIF>
|
Now what you really want to do is take all of that and make it into some sort of module. You don't want it to be a custom tag because a custom tag can't be used inside a function or inside a CF tag body. Additionally, you don't want to have to keep referring to that complex Regular Expression for each email check. The solution is to write the RegEx to a server variable in such a way that it can be called later along with some arbitary data--in other words, as a custom function.
Let's start by setting up our function generator. This is the code that will 'pre-load' our function library into server memory for use. It's set up with the proper locking and checks so that it will use as little page resources as possible.
<CFLOCK scope="SERVER" type="READONLY" timeout="5">
<!--- Only set server variables if they do not already exist --->
<CFIF Not IsDefined('Server.Custom')>
<!--- ALways exclusive lock when setting memory variables --->
<CFLOCK SCOPE="SERVER" TYPE="EXCLUSIVE" TIMEOUT="3">
<CFSET Server.Custom=1>
<!--- Return 0 if email is false or 1 if it is a proper
address --->
<CFSET
Server.IsEmail="REFindNoCase('^([[:alnum:]][-a-zA-Z0-9_%\.]*)?[[:alnum:]]@[[
:alnum:]][-a-zA-Z0-9%\>.]*\.[[:alpha:]]{2,}$','">
<CFSET Server.IsEmailEnd="')">
<!--- Returns 0 if number is not an US number, 1
otherwise --->
<CFSET
Server.IsPhone="REFindNoCase('^([0-9]{3}[-_./]?)?[0-9]{3}[-_. /]?[0-9]{4}',
'">
<CFSET Server.IsPhoneEnd="')">
<!--- returns a decimal number that can be used in place of
an IP for site lookups --->
<CFSET
Server.DotLessIP="(ListGetAt(SetVariable('dotlessipworkvar', '">
<CFSET Server.DotLessIPEnd="'),
1,'.')*16777216)+(ListGetAt(dotlessipworkvar,
2,'.')*65536)+(ListGetAt(dotlessipworkvar,
3,'.')*256)+(ListGetAt(dotlessipworkvar, 4, '.'))">
</CFLOCK>
</CFIF>
</CFLOCK>
|
Lets return to our email address checking function and walk it through from a full function to a custom function. The importing thing here is the logic behind what we're doing. Once you have that, you can build custom functions on your own. Lets assume for this example a variable called email with a value of "mdinowitz@houseoffusion.com".
REFindNoCase('^([[:alnum:]][-a-zA-Z0-9_%\.]*)?[[:alnum:]]@[[:alnum:]][-a-zA-
Z0-9%\>.]*\.[[:alpha:]]{2,}$',email)
|
Evaluate("REFindNoCase('^([[:alnum:]][-a-zA-Z0-9_%\.]*)?[[:alnum:]]@[[:alnum
:]][-a-zA-Z0-9%\>.]*\.[[:alpha:]]{2,}$',email)")
|
Evaluate("REFindNoCase('^([[:alnum:]][-a-zA-Z0-9_%\.]*)?[[:alnum:]]@[[:alnum
:]][-a-zA-Z0-9%\>.]*\.[[:alpha:]]{2,}$','"&email&"')")
|
REFindNoCase('^([[:alnum:]][-a-zA-Z0-9_%\.]*)?[[:alnum:]]@[[:alnum:]][-a-zA-
Z0-9%\>.]*\.[[:alpha:]]{2,}$','mdinowitz@houseoffusion.com')
|
Evaluate(Server.IsEMail&email&Server.IsEmailEnd) |
There's actually a lot more you can do beyond single variable functions. The third function in the library above actually uses a single variable multiple times. I'll wait a bit to explain that, as this technique may take some time to sink in. Within the next week or so I'll post how to use a single variable multiple times in a custom function and how to use multiple variables. Be warned. These are all hacks that work and work well, but are ugly. If you use a custom function, document it. Someone who dosn't know how they work will have an epileptic seizure trying to understand what's going on without it.
A final note. Many functions such as RegEx return numbers. These can be treated as if they give a yes/no result. If you want to return an actual yes or no response, try wrapping the entire custom function call inside a YesNoFormat() function.
<cfset email="mdinowitz@houseoffusion.com"> <cfset IP="207.31.122.140"> <CFOUTPUT> #YesNoFormat(Evaluate(Server.IsEMail&email&Server.IsEmailEnd))# #YesNoFormat(Evaluate(Server.Isphone&email&Server.IsPhoneEnd))# #YesNoFormat(Evaluate(Server.Isphone&'951-3235'&Server.IsPhoneEnd))# #Evaluate(Server.DotLessIP&IP&Server.DotLessIPEnd)# </CFOUTPUT> |
Stay tuned for Part II, where Michael discusses more advanced types of custom functions.