the singularity of being and nothingness
A Little Taste of Spry 1.6 Goodness
A few days I blogged about Adobe's release (and sexy-fication) of the javascript framework Spry 1.6. While I have not had a lot of time to play around with the absolutely unique elements, some cool additions are the improvements they made to the password verification and confirmation widgets. With the root password widget, the developer can create a pretty robust set of validation rules for user-entered passwords, such as setting minimum and maximum lengths, required special characters/numbers, etc. The best part is that the Spry framework makes these validations incredibly easy to implement on a normal HTML form. For example, here is an example password field I have created:
<input type="password" name="password" id="password" />
<span class="passwordRequiredMsg">Please enter a password.</span>
<span class="passwordMinCharsMsg">Your password must be at least 7 characters long.</span>
<span class="passwordInvalidStrengthMsg">Your password must contain at least 1 number.</span>
</span>
That's it. Basically, the entire password widget is a span wrap on the password field. Within this, special validation messages can be defined. On this example, I have set an error message for "required," "minimum characters" and "password strength".
On the function side, the following is all that is required to fully validate this field:
Basically, I simply pass the ID of the widget ("passwordValidation", the ID I gave to the span above) to the Spry.Widget.ValidationPassword() function and then define the various rules that I want to apply to it. I finish the function call with the action I want to trigger the function (here, "blur"). That's it–very simple.
To further round out my example, I have also included a password confirmation field, a required security question field, and a custom-validation zip code field. But most interesting (at least to me) is the functionality on the email field. In addition to validating that the user has entered a syntactically correct email address (that is, one containing an "@" followed by "domain.com"), it also makes an asynchronous call to a ColdFusion component to check whether or not the email exists in the database. It finishes up with displaying a message depending upon the result of the component call. I do this with the following:
var email = document.getElementById('email');
var emailText = document.getElementById('emailVerification');
emailText.innerHTML = "";
var email = email.value;
Spry.Utils.loadURL("POST", "registration.cfc?method=checkEmail&email=" + email, true, emailResult);
}
Here, I am simply getting the values of the email input field, as well as clearing out any values that exist in the "emailVerification" field. After this, I pass the value of the email field to my component. The component queries the database, evaluates the input, and then returns the status of the email lookup. Once returned, loadURL() fires the callback function "emailResult", listed below.
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var verifyEmail=xmldom.getElementsByTagName("email");
var emailText = document.getElementById('emailVerification');
var emailNode = verifyEmail.item(0);
if (emailNode.getAttribute("id") == "yes") {
emailText.innerHTML += "That email has already been taken";
}
else {
emailText.innerHTML += "That email is available";
}
}
In a nutshell, this function simply parses the returned value from the component call. If the result is "yes" (i.e., the email exists in the database), I return an error message. If "no," the user is told so.
Now obviously, strikingly absent from this example is any server-side validation. If I were to implement this into an application, I would want to duplicate the various bits of functionality for server-side processing, for the end-user could easily disable javascript in their browser and render all of this fun, flashy stuff entirely moot. Now of course, this means more work for the developer. However, I think it is worth it as the kind of UI options outlined above can make the end-user experience more robust and significantly less frustrating (don't you hate getting error messages AFTER hitting submit and then have to go back and do it all over again?).
Anyway, this is long enough now. Click here to see this code in action!
Print article | This entry was posted by existdissolve on October 3, 2007 at 6:30 am, and is filed under Spry Framework. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about -1917 years ago
wow… I’m not sure there could be anything more boring than this post. Well, besides those of the reformed persuasion arguing eschatology amongst themselves. Spry’s at least sexier, unless I can find a pic of either MacArthur or Piper in a thong…
about -1917 years ago
Did you at least try the example?
And Piper in a thong is sexy = truth. He’s the one who brought sexy back.
about -1917 years ago
Yes, I tried it. very nice, although, it might be nice to know what security question one is going to be providing an answer for, unless it’s meant to be Jeopardy. Cage fight between Piper and Trebek.
about -1917 years ago
New to spry. I’m working on a coldbox application trying to use it to call a cfc and return a boolean. Based on the return value from the cfc I want to alert the user either ‘success’ or ‘failure’. I am having problems accessing the <cfreturn> value. Is there a way to do this?
Here is my spry load url…
Spry.Utils.loadURL("POST","index.cfm?event=Species.ehSpecies.InsertNavItem&pageName=" + pageName + "&eventName=" + eventName, true, InsertSuccess);
The loadURL calls a handler which in turn calls the cfc that contains an insert statement that looks like this…
<cffunction name="InsertNavItem" access="public" output="true" returntype="boolean">
<cfargument name="txtPageName" type="string" required="false">
<cfargument name="txtEventName" type="string" required="false">
<cfset success = "">
<cftry>
<cfquery name="qInsertNavItem" datasource="unify10d">
INSERT INTO NAVIGATION
VALUES (7, 1, <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.txtPageName#">, <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.txtEventName#">, ‘prefix’)
</cfquery>
<cfcatch type="database">
<cfset success = "false">
<cfreturn success />
</cfcatch>
</cftry>
<cfreturn success />
</cffunction>
I want to be able to access the "True" or "False" returned by the cfc using spry, without refreshing my page. I’ve probably thoroughly confused anyone reading this, but would appreciate any help you could provide.
Thanks,
TB
about -1917 years ago
TB–
I am myself quite new to Spry, especially to the data-connectivity side, so I don’t know if my answer is a "best practice". With that caveat, here is my suggestion for how to accomplish what you are wanting to do.
To begin, the way CFC’s (at least in 7 and below) return data to Spry is somewhat less than helpful. For example, if you specify "returntype=string" and then do a normal "<cfreturn "" />, you will notice, in Firebug for example, that ColdFusion returns a WDDX packet. No good for Spry. And if you simply specify a returntype of "XML", ColdFusion throws in a bunch of extra information [even with whitespace suppressed AND output of "false" on the component and methods] that absolutely kills SpryData’s parsing of the returned values.
So, return data from a CFC requires a bit of scrubbing. Here’s what I do:
First and foremost, make absolutely sure that "output=no" is explicitly set on both the component tag and the function tag.
Second, make sure to include "<cfsetting enablecfoutputonly="yes" />" after the beginning of the component.
Third, run any method processing as normal. Then, when you are ready to return queries, strings, booleans, etc., wrap it into a xml format like so:
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<email id="no"/>
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="text/xml; charset=UTF-8">
<cfreturn verifyEmail />
I use cfsavecontent to create my variable, but I assume using cfxml would work as well. Anyway, the most important part of all of this is the final cfcontent tag–without this, Spry will break as before, so be sure to include it.
Once I have returned this data in a scrubbed, Spry-friendly format, I do something like this:
function emailResult(request) {
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var verifyEmail=xmldom.getElementsByTagName("email");
Basically, this simply walks Spry through the returned XML data from the CFC and gets the expected value from the appropriate node (here, "email").
Like I said at the beginning, I make no claims that this is a best practice. However, it is a fairly painless way to quickly and easily return data from a component to the asynchronous goodness of Spry.
Let me know how this works out for you, and if you have any links to the code you’re working on, I’d love to take a look at it.
about -1917 years ago
TB–
I noticed at least one issue that will cause problems for you: I think having "output=true" and "returntype=boolean" will not make Spry incredibly happy. While there might be a way to return a boolean value straight to Spry, I’m not sure what it is.
about -1917 years ago
Thanks for getting back to me, your code and comments were very helpful.
So clearly I suck at life cause I am still not getting it to work.
Either I am not quite getting this, or its just not working. It seems to make sense to me, so I am probably overlooking something really stupid.
I have ripped my function apart and put it back together and now have thrown it out and am trying your code. Here is my component page which is a complete plagarism of your code…
<cfcomponent name="species" hint="A security datastore object" output="no">
<cfsetting enablecfoutputonly="yes" />
<cffunction name="InsertNavItem" access="public" output="false" returntype="string">
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<email id="no" />
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="application/xml; charset=UTF-8">
<cfreturn verifyEmail />
</cffunction>
</cfcomponent>
Here is my JS
function InsertSuccess(request) {
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var verifyEmail = xmldom.getElementsByTagName("email");
var emailText = document.getElementById(’emailVerification’);
var emailNode = verifyEmail.item(1);
if (emailNode.getAttribute("id") == "yes")
{
alert("Yes");
}
else
{
alert("No");
}
}
When I try to run this, Firebug throws me a nice little message that reads "emailNode has no properties". This leads me to believe that the creation of the XML is not working properly.
Any ideas?
Thanks again for your help.
about -1917 years ago
Thanks for getting back to me, your code and comments were very helpful.
So clearly I suck at life cause I am still not getting it to work.
Either I am not quite getting this, or its just not working. It seems to make sense to me, so I am probably overlooking something really stupid.
I have ripped my function apart and put it back together and now have thrown it out and am trying your code. Here is my component page which is a complete plagarism of your code…
<cfcomponent name="species" hint="A security datastore object" output="no">
<cfsetting enablecfoutputonly="yes" />
<cffunction name="InsertNavItem" access="public" output="false" returntype="string">
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<email id="no" />
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="application/xml; charset=UTF-8">
<cfreturn verifyEmail />
</cffunction>
</cfcomponent>
Here is my JS
function InsertSuccess(request) {
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var verifyEmail = xmldom.getElementsByTagName("email");
var emailText = document.getElementById(’emailVerification’);
var emailNode = verifyEmail.item(1);
if (emailNode.getAttribute("id") == "yes")
{
alert("Yes");
}
else
{
alert("No");
}
}
When I try to run this, Firebug throws me a nice little message that reads "emailNode has no properties". This leads me to believe that the creation of the XML is not working properly.
Any ideas?
Thanks again for your help.
about -1917 years ago
Thanks for getting back to me, your code and comments were very helpful.
So clearly I suck at life cause I am still not getting it to work.
Either I am not quite getting this, or its just not working. It seems to make sense to me, so I am probably overlooking something really stupid.
I have ripped my function apart and put it back together and now have thrown it out and am trying your code. Here is my component page which is a complete plagarism of your code…
<cfcomponent name="species" hint="A security datastore object" output="no">
<cfsetting enablecfoutputonly="yes" />
<cffunction name="InsertNavItem" access="public" output="false" returntype="string">
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<email id="no" />
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="application/xml; charset=UTF-8">
<cfreturn verifyEmail />
</cffunction>
</cfcomponent>
Here is my JS
function InsertSuccess(request) {
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var verifyEmail = xmldom.getElementsByTagName("email");
var emailText = document.getElementById(’emailVerification’);
var emailNode = verifyEmail.item(1);
if (emailNode.getAttribute("id") == "yes")
{
alert("Yes");
}
else
{
alert("No");
}
}
When I try to run this, Firebug throws me a nice little message that reads "emailNode has no properties". This leads me to believe that the creation of the XML is not working properly.
Any ideas?
Thanks again for your help.
about -1917 years ago
TB–
The only problem that I see is the returnType on your function. When you try to return a string value from a component, it actually returns a wddx packet. ColdFusion apparently knows how to process this fine, but Spry doesn’t.
That is why, I would suspect, you are getting the javascript error. With the WDDX packet being returned, there is not an xml structure for Spry to traverse and so when it gets to actually manipulating the value assigned to "emailNode," there is no usable data for it.
The fix, as far as I can tell, would simply be to change your cffunction returnType from "string" to "xml".
Hope this helps!
about -1917 years ago
Hmm, I changed the returntype to xml and received the same error in the javascript.
about -1917 years ago
Ah, I see it. The "access" attribute of the function has to be set to "remote"–this exposes the function to asynchronous access, as well as any other type of remote access (e.g., webservices).
Fingers crossed, but that should do it.
about -1917 years ago
That didnt do it either. I’m at a loss.
about -1917 years ago
The only other thing I can think to check is:
1.) Ensure that "Enable Whitespace Management" is set in CF Administrator.
2.) Turn off all Debugging (i’ve found that sometimes this interferes).
Do you have this code published somewhere where I can browse to the error and see what is happening in the javascript?
about -1917 years ago
1.) Ensure that "Enable Whitespace Management" is set in CF Administrator. — This is enabled
2.) Turn off all Debugging (i’ve found that sometimes this interferes). – I’ve found the same thing and do not have it turned on.
I dont have this code anywhere you can get to it.
I just tried dumping the result
variable.var result = request.xhRequest.responseText;
into a div to see if the page was coming back correctly and it is throwing a CF error…
An error occured while Parsing an XML document.
Detail: Premature end of file.
I’m not familiar with this enough to know what that really means other than the XML is apparently not building correctly in my cffunction.
Here is my function again…
<cffunction name="InsertNavItem" access="remote" output="false" returntype="xml">
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<email id="no">
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="application/xml; charset=UTF-8">
<cfreturn verifyEmail />
</cffunction>
I read a few other sites with posts that are using the toXML function like this….
<cfset toXML = createObject("component", "toXML")>
<cfset usersXML = toXML.queryToXML(GetUserInfoQry, "dataset", "row")>
…but that doesnt seem relevant here because I am not trying to convert a query result to XML.
Thanks again for all your ideas.
about -1917 years ago
TB–
I’ve run across that darn "Premature end of file" error myself.
As a shot in the dark, try wrapping
<email id="no">
in a <cfoutput>.
about -1917 years ago
Ok, that gives me a function that looks like this…
<cfcomponent name="species" hint="" output="no">
<cfsetting enablecfoutputonly="yes" />
<cffunction name="InsertNavItem" access="remote" output="false" returntype="xml">
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<cfoutput><email id="no"></cfoutput>
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="application/xml; charset=UTF-8">
<cfreturn verifyEmail />
</cffunction>
</cfcomponent>
And throws a new error which is…
An error occured while Parsing an XML document.
Detail:
XML document structures must start and end within the same entity.
about -1917 years ago
Ok, scrap the <cfoutput>’s. I think I see one issue that will definitely kill the processing.
Your <email id="yes"> node is not closed. XML is EXTREMELY finicky about proper structure.
So I would close if off like so: <email id="yes" />.
See what that does.
about -1917 years ago
It seems to like this…
<cfcomponent name="species" hint="" output="no">
<cfsetting enablecfoutputonly="yes" />
<cffunction name="InsertNavItem" access="remote" output="false" returntype="xml">
<cfsavecontent variable="verifyEmail">
<?xml version=""1.0"" encoding=""UTF-8""?>
<cfoutput><email id="yes" /></cfoutput>
</cfsavecontent>
<cfset verifyEmail = xmlParse(verifyEmail) >
<cfcontent type="application/xml; charset=UTF-8">
<cfreturn verifyEmail />
</cffunction>
</cfcomponent>
With the <cfoutput><email id="yes" /></cfoutput> like that the CF error goes away.
However, it is still not returning a value to the javascript in the email node
When i set emailNode like this
var emailNode = verifyEmail.item(0); and then try to alert the value, it is null. and when it gets to the if statement Firebug throws the same "emailNode has no properties" error.
function InsertSuccess(request) {
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var verifyEmail = xmldom.getElementsByTagName("email");
var emailNode = verifyEmail.item(0);
alert(emailNode);
if (emailNode.getAttribute("id") == ‘yes’)
{
alert("Yes");
}
else
{
alert("No");
}
}
I need to turn my eyes away from this for the day. I will sing your praises and start tomorrow with a smile if you have this figured out then.
Thanks again,
TB
about -1917 years ago
TB–
Using your code, I was able to duplicate the error you’re getting. Unfortunately, I could only reproduce it when I turned ColdFusion debugging on–turning it back off removed the error. Further, it does not appear that "Enable Whitespace Management" in general settings affects it either way.
I even fiddled with removing the "enablecfoutputonly" and it still didn’t produce the error–the only thing that did was when I enabled the debugging.
about -1917 years ago
Ok back on this now.
I’ve set up my page to output the return value into a textarea and it is coming back with html from the cfadmin login page. So, when I call a cfc, instead of returning the value of the cfc, it is returning the login page. Do you have any other ideas of other settings in cfadmin I might need to change in order to get this working?
about -1917 years ago
It’s returning the CFAdmin page? It sounds like you’re browsing directly to the component (cfc) and not specifying the function you want to call.
Where in your code are you actually hitting the cfc with loadURL()?