For my final (well, for now) post on jQuery and forms validation, I thought I'd actually create a real form with actual back end processing. I'm going to demonstrate with a form that makes use of both client-side and server-side validation, and also demonstrate one of the cooler features of the jQuery Validation library - remote validation.
Let's get started by describing our form and building it for entirely server-side validation. Imagine that I run a blog aggregator (oh wait, I do) and I want to make it easy for folks to send me information on their blogs so I can add it to the database. I'd need a form that asks for their blog name, URL, and RSS URL. (To be anal, I also use a description field at CFBloggers, but I'll keep it simple for now.) When not working within a framework like Model-Glue, I'll typically build a self-posting form (pseudo-code):
default form value
notice a form submission:
create a list of errors
if no errors, email, save to db, etc, and push to thank you page
display form:
optionally display errors
Here is the initial version of the form with ColdFusion performing the validation server side. I assume none of this is unusual and since and the focus here is on jQuery I won't go over the code.
<cfparam name="form.blogname" default="">
<cfparam name="form.blogurl" default="">
<cfparam name="form.rssurl" default="">
<cfif structKeyExists(form, "save")>
<cfset errors = []>
<cfif not len(trim(form.blogname))>
<cfset arrayAppend(errors, "You must include a blog name.")>
</cfif>
<cfif not len(trim(form.blogurl)) or not isValid("url", form.blogurl)>
<cfset arrayAppend(errors, "You must include a blog url.")>
</cfif>
<cfif not len(trim(form.rssurl)) or not isValid("url", form.rssurl)>
<cfset arrayAppend(errors, "You must include a rss url.")>
</cfif>
<cfif arrayLen(errors) is 0>
<cfmail to="ray@camdenfamily.com" from="ray@camdenfamily.com" subject="RSS Submission">
Blog Name: #form.blogname#
Blog URL: #form.blogurl#
RSS URL: #form.rssurl#
</cfmail>
<cflocation url="rssaddthanks.cfm" addToken="false" />
</cfif>
</cfif>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Untitled Document</title>
</head>
<body>
<h2>Add RSS Feed</h2>
<form id="mainform" action="rssadd.cfm" method="post">
<fieldset>
<cfif structKeyExists(variables,"errors")>
<b>Please correct the following error(s):</b><br/>
<ul>
<cfloop index="e" array="#errors#">
<li><cfoutput>#e#</cfoutput></li>
</cfloop>
</ul>
</cfif>
<legend>Fill out the details of you blow below.</legend>
<cfoutput>
<p>
<label for="blogname">Blog Name</label>
<em></em><input id="blogname" name="blogname" size="25" value="#form.blogname#" />
</p>
<p>
<label for="blogurl">Blog URL</label>
<em></em><input id="blogurl" name="blogurl" size="25" value="#form.blogurl#" />
</p>
<p>
<label for="rssurl">RSS URL</label>
<em>*</em><input id="rssurl" name="rssurl" size="25" value="#form.rssurl#" />
</p>
</cfoutput>
<p>
<input class="submit" type="submit" name="save" value="Submit"/>
</p>
</fieldset>
</form>
</body>
</html>
Alright, so nothing too scary in there, right? You can demo this online here.
Let's add some jQuery love to the page. I'll begin by including my libraries of course:
<script src="/jquery/jquery.js"></script>
<script src="/jquery/jquery.validate.js"></script>
Next I'll set up my validation and rules:
$(document).ready(function(){
$("#mainform").validate({
rules: {
blogname: "required"
,blogurl: "required url"
,rssurl: "required url"
}
});
});
The details of how this works are described in my last entry, but basically I'm saying that all 3 fields are required and blogurl and rssurl also need url validation. (Hey IE folks, did I do my commas right?)
Again, this just plain works. You can demo this here. If you disable JavaScript, you still get the server side validation. It took me about 30 seconds to add in the JS validation though so I don't mind writing it twice.
Alright, but now it's time to get sexy. jQuery's validation plugin comes in with a number of default rules you can use. I also demonstrated how you can write your own rules. Sometimes though there are things you want to do that are impossible with JavaScript. jQuery Validation supports a style of validation simply called 'remote'. By specifying a URL for a validation rule, the plugin will automatically run your URL (passing the field name and field value). Your server-side code does what it needs to and outputs either true or false. Let me demonstrate. First, I'll modify my rules declaration:
rules: {
blogname: "required"
,blogurl: {
required:true
,url:true
,remote:"rssprocess.cfm"
}
,rssurl: {
required:true
,url:true
,remote:"rssprocess.cfm"
}
}
So, in English, this means that:
The name value will be required.
The blogurl value will be required, must be a URL, and the value will be passed to rssprocess.cfm and it must return true.
The rssurl value will be required, must be a URL, and the value will be passed to rssprocess.cfm and it must return true.
I'm using the same file to process both requests. I can do this because the plugin will send the name of the field as well. I could have used two different CFMs, or even two different CFC methods. Let's look at rssprocess.cfm:
<cfsetting enablecfoutputonly="true">
<cfif structKeyExists(url, "blogurl")>
<!--- if blogurl, just do a check for status code 200 --->
<cfhttp url="#url.blogurl#" result="result">
<cfif structKeyExists(result.responseheader,"status_code") and result.responseheader.status_code is 200>
<cfoutput>true</cfoutput>
<cfelse>
<cfoutput>false</cfoutput>
</cfif>
<cfelseif structKeyExists(url, "rssurl")>
<!--- if blogurl, just do a check for status code 200 --->
<cftry>
<cffeed source="#url.rssurl#" query="foo">
<cfoutput>true</cfoutput>
<cfcatch>
<cfoutput>false</cfoutput>
</cfcatch>
</cftry>
<cfelse>
<cfoutput>false</cfoutput>
</cfif>
I begin by turning on cfoutputonly. I'm not sure how well the plugin will handle values with whitespace around so it so I'm going to be anal about my output. I then check my URL scope. If blogurl was sent, I just do a HTTP check to ensure the URL exists. If rssurl was sent, I try to read it with cffeed and return true if the RSS feed can be parsed by CF. Notice that I return false in all error conditions, and if no value was passed at all. (Because people like me will run your site with Firebug, notice the Ajax requests, and try to run the file manually.)
You can demo this here. I also added custom messages. You can view source on the demo to see that. That's it. I don't think I'll write another form without jQuery validation in it!
Edit at 9:36AM CST Epic fail on my part. Thank you to Esmeralda for reminding me. I forgot to htmlEditFormat the form data to help prevent XSS type attacks. I normally do something like this in all my form checks:
<cfif not len(trim(form.blogname))>
<cfset arrayAppend(errors, "You must include a blog name.")>
<cfelse>
<cfset form.blogname = htmlEditFormat(trim(form.blogname))>
</cfif>
Note the use of both trim and htmlEditFormat. Anyway, I've added it to all 3 dems, and thank you again Esmeralda for the reminder!
Archived Comments
You should now take a look at the jquery form plugin (http://snipurl.com/br5hq). I had some issues initially linking this plugin with the validation plugin and went for using the validationaide plugin instead, but after reading your last couple of entries and having got a form working correctly I am converted! By the way, here is a snippet of how to get the form plugin and validate plugin working together (in $(document).ready()):
var v = $("#myformid").validate({
submitHandler: function(form) {
$(form).ajaxSubmit(frm_options);
},
rules {rules here},
messages {messages here}
});
I also use the BlockUI plugin to give some user feedback while the form is submitting and any errors that are returned. If you want to see some code give me a shout.
@David Phipps - I'd like to see that - can you post the URL to a sample online?
Here's a very rough demo:
http://www.chapel-studios.c...
Change the Show Error radio button to see the success and error messages.
Nice. Thanks for sharing that.
@ray, maybe i'm reading something wrong, but what are you calling your back end validation? perhaps i should ask would you consider back end and server side the same? i guess i am missing where you actually validate when the data posted from the/any form is actually validated. i see where you call the remote process that validates the uri is working, but if i'm not mistaken, that is before the form is submitted? what's to stop someone from creating their own form and submitting it, or stopping the transaction after form submission and changing what is sent after your client/server validation calls.
Shag - the back end validation is two fold. One, we have basic back end stuff for when JS is turned off. Do you see that in the very first code sample? The second style is specifically to be used with jQuery and the Validation plugin. That's the last few code samples.
The 'no JS' validation does NOT do the HTTP/RSS check. It DOES check for basic validation (must be a value and must be a url). I do NOT repeat the 'check URL for existence' and 'check RSS feed' for non-JS folks, but they still get some validation.
So - worst case - I get an email with proper formatted URLs that aren't pointing to a real domain or rss feed. I could add that logic if I wanted to though. I did not because I didn't want to repeat the network calls on the submission for people who have JS turned on. I could get around that by submitting elsewhere (via an Ajax submission), or simply using jQuery to add a quick flag ('if form.ivalidatedclientside=1 then dont repeat). But I didn't think that would be worth the effort.
Let me know if that doesn't make sense.
@ray, i was thinking more in terms of db input rather than mail. i would have been much more restrictive regarding the blog name regarding how long it could be, special characters... etc. i tend to think of server side validation as protecting from the bad stuff, and maybe this particular example is more checking to make sure the form is filled out. with less concern on is the data that is going to enter the application going to be destructive.
i wouldn't consider your server side validation enough for inputting to a db, and didn't want anyone trying to learn something about validation from your site to think it was.
im in a glass half empty state today.
Err, well, everyone has different requirements for their logic. My point here was to demonstrate both client side and server side doing _something_. What CF does is to ensure the value is not empty and that, for the URLs, the pattern is right. Obviously a person could have more 'rules' for their logic. I do not agree that my code is not 'enough', especially in regards to the main point of this blog entry. Then again, I'm probably being a bit defensive. (I'll admit to that.)
Hi Ray,
thanks for the tutorial! Still having a hard time getting it to work though. Any chance you can take a peek here and see what i'm doing wrong?
http://www.getmura.com/foru...
Thanks,
David
Try changing the remote rule to something like this:
remote: {
url: "checkUser.cfm",
type: "post"
}
@David - did Dave Phipps' tip help?
Still won't work.
this is the following of what i have:
username: {
required: true, remote: "checkUser.cfm", type: "post"
}
and my checkUser.cfm is set to:
<cfsetting enablecfoutputonly="true">
<cfset userBean=application.userManager.readByUsername(url.username,"default")>
<cfif userBean.getIsNew()>
<cfoutput>false</cfoutput>
<cfelse>
<cfoutput>true</cfoutput>
</cfif>
So what does Firebug tell you? Or the Chrome developer tools. Do you see the network request? What is the response?
Try changing your rule to look like this:
username: {
required: true, remote: {url: "checkUser.cfm",type: "post"}
}
note the extra curly brackets around the remote properties/arguments
ok so i've got some requests going in and out via firebug. Sorry Ray, still really new to Jquery :). Seems that things are working, but intermittently. If i type in a password that is already taken or not, the first time i type in anything in the username, it does nothing. However, once i change that value, then it seems the validation starts kicking in. However, the message will always stay even when the username is good.
You can see exactly what i mean here: http://sbiwd3.kattare.com/g...
Thanks guys,
David
Can you tell me a value for password that is good and one that is bad?
in addition to what Ray just asked, checkUser.cfm seems to be returning a full html page. It should just return true or false right?
By Password i'm assuming you mean username? :) one that is good is test and one that is bad is tests (ie. test is a valid username that should be taken). Do you happen to have a messenger that we can just chat on for a few minutes if you have time? If so, just email me it, otherwise we can just continue dialogue here.
Thanks again Ray,
David
In fact something weird is happening try going to checkUser.cfm directly:
http://sbiwd3.kattare.com/g...
then try
http://sbiwd3.kattare.com/g...
They return the same page, yet the second clearly shouldn't exist!
@Dave,
That is correct. I was wondering the same thing. I'll see if Matt Levine can confirm why that would be.
Dave got it - your checkUser.cfm is returning an entire page.
onMissingTemplate maybe?
Yup.. not sure why that is, but i'm assuming its something to do with Mura. It seems to return the page that i'm on.
BINGO!!!! Thanks guys!! Figured it out. I had to move the checkUser.cfm out of a particular directory and then set a path to it. Works like a charm now!
Thanks again,
David
Hi Ray,
If rather than having 'remote:' point to a cfm I want to have 'remote:' point to a function called "process()" within rss.cfc, how would I do that?
In other words, how can we use a cfc rather than a cfm?
Thanks in advance. I've been reading your blog a lot lately.
You just need to point to rss.cfc?method=process
then change the process function access to remote
The function should return either true or false IIRC.
Perfect, thanks Dave.
The remote attribute = data: , it only can pass one value, I need to used a long list when i want to pass in more than one value, then split it via server scrip.
Is that any other way i can set more than one form/url variable in DATA?
@keong Just pass data as an object:
data: {
username: function() {
return $("#username").val();
},
email: function(){
return $("#email").val();
}
}
Hi Dave
I try to used this way but it seem not worked..
I also try to used firebug but have no idea how to used it.. will need some time to study how to used firebug too~ ><
Anyway.. thank for your reply, will try you suggestion later.
Regard
keong
Video on how to use Firebug: http://www.developria.com/2...
So, I've been trying desperately to get this to work...
This is my jquery:
$("#addForm").validate({
errorContainer: "#error",
rules: {
faculty: {
remote: "management_val.cfm"
}
},
messages: {
faculty: "x"
}
});
I've been looking at it in firebug and it doesn't seem to be sending anything...but I can't figure out why it's not sending anything. All the names are correct, jquery.validate and jquery are included, and it's in a $(document).ready(function).
Any insight would be great! :)
(I'm kind of a rookie with jquery)
Update: I tested it on a normal form and it works, so I think the fact that I have the field using autosuggest might be what's conflicting. It's bound to a query that identifies valid names for the field...
(The form is used to add registered faculty to a department.)
Do you think that is problem?
Possibly. Is it online where we can see?
Hi Raymond
Thank for your firebug video. Very usefully for me.
btw, do you have time take a look for my jquery form validation issue ?
http://forum.jquery.com/top...
I try to figure out this few days but no luck.
Anyway, thank for your time and have a nice day..
Regard
keong
Bri Garrett
Maybe you can try below code :
remote: {
url: "<cfoutput>#application.http#</cfoutput>app/excel_upload/act_check_manager_respond_time.cfm",
type: "post",
data: { //3
key: function() {
var date1 = $("#CFB_Date_Sub").val();
var date2 = $("#Date_Resolved").val();
var action_reason = $("#lastname").val();
if (action_reason == ''){
jsaction_reason_check = "Yes"
}else{
jsaction_reason_check = "No"
}
var jointValue = jsaction_reason_check + "|" + date1 + "|" + date2;
return jointValue ;
}
}//3
}
If you have a form with lots of form elements, it seems like double work asking a user "Please Select Credit Card Type" in both coldfusion and jquery. I wonder if it's possible to fuse jquery validation into coldfusion logic so as to avoid repeating yourself?
cfform does that a bit (it's not jquery though) but at the end of the day, you're going to be doing both. I think it's ok though to have "good" errors in jQuery, and "ugly" errors server side. Ie, if you want to demand JavaScript support, you can/should still validate server side, but you can have simpler uglier output.
I used jQuery Validation for a form with the silly assumption nobody would have javascript disabled. I like your method and want to implement it in my form which saves to a database.
Currently my form action goes to a second 'processing.cfm' page for db insert. Do you think there is a way to get your method working with a separate processing page? Or do I just need to move my db insert script onto the form page and replace your cfmail script?
"Do you think there is a way to get your method working with a separate processing page? " Errr... well, the whole point of this is for _client_ side validation. It's easy to do validation server side with CF, but it's a different topic really.
I guess my question should have been, "In your example how would you add that form data to a database once it has passed the client side validation?"
Once client side validation is done, the form submits as normal. So at that point... it's just a regular form post to ColdFusion. Makes sense, right?
I'm obviously missing something because the form would not submit as normal. The form action takes the client back to that form page where it checks if there are no errors, then sends an email and redirects to the thanks page. But that redirect doesn't pass any form variables. So any db insert would have to take place within the cfif where the email is sent. Right?
At least that is what I did and it is working with JavaScript enabled or disabled. So thank you!
A cflocation can't pass form vars - you are right. Why not do the db insert before the redirect?
Very cool! Never knew about the remote method before. Only issue is, it doesn't work for optional fields - even if no length is true e.g.
<cfif structKeyExists(url, "telephone")>
<cfif not len(url.telephone) or reFind("^[0][2378]\d{8}$", url.telephone)>
<cfoutput>true</cfoutput>
<cfelse>
<cfoutput>false</cfoutput>
</cfif>
</cfif>
Would love to be able to get around this. Can you or anyone else help?
I'm confused - so you are saying that if the field is optional, but you are using remote logic to validate it, the remote check never fires?
If required is true, everything fires and functions as expected, but required true will not accept an empty value even when validated as true (as sort of expected), so the empty option remains required.
If required is false or not stipulated, no validation fires when clicking the submit button immediately after loading the form, although it does fire and function correctly when clicking in each individual field. In addition, successive submissions turn validation on and off like switch. In short, like a bit of a traffic jam.
Hope that is enough to go on, it's a bit of a brain hemorrhage I know.
"but required true will not accept an empty value even when validated as true"
I'm sorry, but I have no idea what this means. You said, if required is true, it won't accept an empty value... which is obvious, right? I'm totally not getting it.
Can you rephrase this whole thing? :) (Or maybe someone else in the thread groks it.)
I was pretty tired the other morning, so it was difficult to see the forest for the trees. In short, however, the standard seems that remote validation must be required and the string must have length. Mind you, I am led to believe there is a way around this, but not being a jQuery guru doesn't help. What I was trying to do at the time, was run a regular expression, however, I have since found you can add methods with additional-methods.js included in the download, or write your own e.g.
jQuery.validator.addMethod("telephoneAU", function(value, element) {
return this.optional(element) || /^[0][2378]\d{8}$/.test(value);
}, "Please specify a valid Australian telephone number.");
Anyway, my problem is now solved.
Thanks for all the cool tutorials.