Some of the new, cool, hip sites out there have done something rather neat with their login system. If you take a look at Technorati, notice that when you click the Sign In link, a modal window pops up. This lets you login no matter where you in the site. No need to go to another page (thank goodness, since Technorati is slow) and be sent back (hopefully) when done. Can ColdFusion 8 do this? Of course! Let's look at an example.
First off - imagine we have an existing site. One that uses a custom tag for layout (details may be found here). All of our pages use the custom tag. For example:
<cf_layout>
<p>
Welcome to our Web 2.0 site.
</p>
</cf_layout>
(As just a quick warning, I'll be sharing the complete code for all of this at the end of the blog entry, and via a download link.) Our layout custom tag creates a simple navigation table on top:
<a href="index.cfm">Home</a> / <a href="about.cfm">About Us</a> / <a href="buy.cfm">Buy Us</a>
The first thing I want to do is add a Login link to the menu:
/ <a href="javaScript:doLogin()">Login</a>
The link above runs the JavaScript function, doLogin, so let's go there next.
function doLogin() {
ColdFusion.Window.create('loginwindow','Login','login.cfm',{center:true,modal:true});
}
This function simply uses the ColdFusion/Ajax API to create a window. I named it loginwindow, gave it a title, pointed it to a login.cfm page, and passed a few attributes. I could have styled it as well, made it a specific size, etc. But you get the idea. Now let's look at login.cfm:
<form action="login.cfm" onSubmit="handleLogin();return false;" id="loginform">
<table>
<tr>
<td>Username:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="login" value="Login"></td>
</tr>
</table>
</form>
This is a fairly simple form - but note that I specifically block the form submission. I call yet another JavaScript function, handleLogin. So let's take a look at that:
function handleLogin() {
ColdFusion.Ajax.submitForm('loginform','processlogin.cfm',handleResponse);
}
Once again we have a use of the ColdFusion/Ajax API. The submitForm function will do exactly as it sounds - send an entire form block to a page. My last argument, handleResponse, is the function that will be called with the result. Let's look at processlogin.cfm first:
<cfsetting enablecfoutputonly="true">
<cfparam name="form.username" default="">
<cfparam name="form.password" default="">
<cfif structKeyExists(form, "login")>
<cfif form.username is "paris" and form.password is "hilton">
<cfset session.loggedin = true>
<cfoutput>good</cfoutput>
<cfelse>
<cfoutput>bad</cfoutput>
</cfif>
</cfif>
Nothing too complex here. Normally you would call a CFC to handle the authentication. I've never used a hard coded username and password in production. Really. Now notice that I output two strings: good and bad. Remember how I submitForm named a function to run with the result? Let's look at that now:
function handleResponse(s) {
if(s == "good") {
//first hide the window
ColdFusion.Window.hide('loginwindow');
//rewrite out login
var loginspan = document.getElementById('loginstatus');
var newcontent = "<a href='index.cfm?logout=1'>Logout</a>";
loginspan.innerHTML = newcontent;
} else {
alert('Your login didn\'t work. Try paris/hilton');
}
}
The function was passed the response, so all I need to do is check the value. If it is good, I hide the window (again, using the ColdFusion/Ajax API. Now I do something fancy. It wouldn't make sense to keep the login link on the page. I wrapped my login link in a span:
<span id="loginstatus"><a href="javaScript:doLogin()">Login</a></span>
This lets me get the span and change the HTML inside. Pretty simple, right?
To see an online demo of this, go here: http://www.coldfusionjedi.com/demos/login/index.cfm
Obviously someone could do this a lot prettier then I did - but notice how nice it works. You can login from any page, and when done, you are still on the page you were looking at. (I do something like this for the contact form at CFBloggers.org.) Also note how little JavaScript was involved. The most complex bit was handling the result of the login, and even that was just about 10 lines of code.
Enjoy. I've attached the code to this blog entry.
Archived Comments
Nice post Ray. I started working on something like this awhile back but never finished. This will help a lot.
Thanks for the great info.
That's very sweet Ray. Thanks for this. I'm working on a project for myself at the moment, and this will be ideal.
Thanks for all the hard work.
To be honest, it wasn't hard work. I was telling Ben - I thought this entry would have more text - but it doesn't need it! That's the power of what we get in CF8! (Ok, so I'm easily excited. ;)
I know. I'm finding CF8 great to use, and fun too. Doing some great things and doing them quick!.
Anyway, I meant thanks for all the hard work generally. I find your blog an invaluable resource. Keep up the good work.
I love these examples! I need a log in just like this.
Very nice indeed. I am about to attempt the same thing using Spry (my customer is very large, so they will not be moving to CF8 until sometime next spring).
BTW...I think you have a Typo on your "Buy Us" page:
<quote>Our you incredibly rich?</quote>
<grin>
Troy
Thanks for the demo Ray. Been looking for something like this, it will fit the bill nicely.
A general question Ray - what's the browser compatibility like with this and with the CF8 Ajaxy goodness in general? And how does it degrade if a user has javascript disabled? Just wondering if it would be a good idea to do a quick check to see if javascript is enabled and browser compatibility before using this method.
Will, I wasn't able to find docs on this. Surprising. Maybe it's in the same place the CFFEED chapter is. ;)
From Ben: Official line is all current IE, FF, Opera and Safari. Safari has issues with FCK though.
The previous quote is in regard to supported browsers.
For JS turned off - there is no official way to handle stuff.
For my demo - you could easily make the login link go to a traditional form. Look at my blog entry on Spry and HTML Panels and how they handle graceful degradation. That style of link would work just fine here.
Thanks Ray. Pleased it's not just me who couldn't find much out about the supported browsers! I'll run some tests on different browser versions here and let you know what I find out.
Other than that, I suppose it's an issue of 'know your user' which determines how much trouble to go to.
@Will,
I have found that there are not too many people that disable JavaScript these days. Here are some statistics from the last 30 days on the most popular site I work on:
JavaScript enabled:
Yes - 309,475 visitors - 99.44%
No - 1,756 visitors - 0.56%
And a lot of those people that didn't have JavaScript enabled were people browsing on their blackberry's and such.
But then again, at our current conversion rate and average order value, those 1,756 people per month could represent about $12,000 in sales so that would make it well worth my effort to make sure they could browse my site easily without javascript, even though they should be paying attention to their driving and not shopping online with their phone =)
Not sure if it was mentioned, i was reading kind of fast, but Spry has a similar example that does the same thing with less lines of code. I understand this is an example to show the power of CF8, but I figured I'd mention it here for those who are not on CF8 at the moment.
Are you sure? Spry _can_ do this obviously. It has a way to send form data, although I don't believe it does a whole form scope like CF's does. Spry does not have a Window UI though. So I don't think you are right - but if you have a URL, I'd love to see it. As you know, I love Spry as well. :)
Nicely done. I've done something similar using cfwindow as the login prompt when experimenting with cfexchange. I've uploaded my sample to cfaussie google group.
Your samples and explanations have been an invaluable source for understanding and learning CF. What would people do without your site???
Thank you, Andrew. I do it all for the wishlist. In reality I'm a greedy SOB. ;)
@Ray
Maybe I misunderstood the post in that case. I was actually thinking of this example "Tooltips With Interactive Content" towards the bottom of this page:
http://labs.adobe.com/techn...
I imagine that your previous posts on posting data from spry using the "post" method can be combined with that sample and create a similar effect.
Hmmm. Maybe. Not sure I'd use a tooltip for logon. Just... feels wrong. Also, I don't believe Spry's tooltips allow for the modal effect that cfwindow does.
Is this working in coldfusion mx7?
It's a CF8 demo. :)
Just one thing i saw how to change the values of the login window, in adobe docs, but how can you costumize the login windows like the thecnorati alike?
Raymond,
Thank you, this is just what I was looking for!
I am not a CF developer, just a big fan. This was very helpful.
If I am going to check this against a database with hashed passwords, where do you propose I add that? To the processlogin.cfm file?
Also, I'd like this to be modal, so that if someone goes to a page they don't have access to, they won't see the content until they log in.
I have two other customization questions.
If the user enters an incorrect user name or password, it leaves the data in the fields. Can you suggest a way to clear the fields after the notice?
Also, can you propose a way to include a "forgot password" link in the login.cfm page that would close the cfwindow and redirect them to the forgot password page?
Many thanks, Cliff
Hi Ray...
This looks great and appears to be clear as mud until I try to run it in CF8. I have a js alert in begining of my handleResponse function which tells me the value of "s" (either good or bad passed from the processlogin.cfm). Trouble is, although I can see the value is being passed correctly (i.e s=good) it never logs me in as the js function does not seem to be treating "s" as a string...or something?
Any ideas please?
I'm afraid I don't quite get your comment. If the response is "good", it means your login worked, and the session variable stores the results. That is how I did the 'login' for this demo. Your security system may be different. If your security system uses some other method to mark a user logged in, then you would need to modify my demo code. Does that make sense?
Or are you saying the result of s "looks" to be good, but the stuff in the s=="good" portion isn't running? If so, I'd consider using Firebug and debugging the result. You can use console.log(s) to see the value.
Just getting my head round the whole Ajax thing, and this helped thanks.
I was tinkering with things and trying to learn more, so I have modified your script so that if the login fails, it runs a cfajaxproxy instance to a cfc to count the number of failed logins and returns the "x failed logins" to the login window and updates a div. This works beautifully, but the code only runs on a form submit event.
So what I was wanting to do was also run the code just after the coldfusion.window.create code as well, but this fails, I think because I would need to run it after some sort of ready event. Is there such a thing available when using the inbuilt coldfusion.window.create function?
You can run a function when the window shows. See the AJAX docs in the CFML Reference: ColdFusion.Window.onShow
I am running into a strange issue with the modal=true setting. It works just fine in most broswers except IE 7/8. There is no transparency/filter effect. It is just a solid grey background. Does anyone know of a fix for this?
Ray, Please point me in the right direction if this is too 'off-topic'. I want an additional link in my layout to appear if the user is logged in. Can I detect this with the session.loggedin or is there a better way?
Unless I'm misreading you, can't you just wrap a link with <cfif structKeyExists(session, "loggedin")> or some such?
Embarrassingly, no, you're not misreading me. That is indeed the solution. Sorry for the hassle.
No worries and glad to help.
Ray,
I am looking into the FW/1 Framework and was wondering how I might incorporate this into the framework? I can't seem to get the good to return to the function, so I can't log in. Any ideas?
I am really new to frameworks, and from what I have read (on your blog and others), this framework might be the best to use to start understanding frameworks.
thanks for the help
Dan
Well, remember, a framework, like a normal CFML file, returns data. Period. It can be HTML. It can be plain text. Or whatever. But (in general), its just text data. You should be able to build a FW/1 request that only returns "good". This is no different than a request that returns a crap load of HTML.
Does that make sense?
@Dan - I just double checked. In the examples folder there is an Ajax based application. I haven't run this myself, but check it out.
I am probably doing this all wrong but I am learning.
In my views folder I have a login folder with the login.cfm and processlogin.cfm pages. on the default.cfm page I run the cfwindow tag that will pop open the window with the source of the cfwindow tag being: index.cfm?action=login.login.
on the login.cfm page I have my fields for id and pass. I submit using your js call to the function that does the submitForm. I have this and all my javascript calls on my default.cfm page in my layouts folder.
This is where I have my issue. It runs the submitform (i think), and is suppose to call handleResponse js when it is done (this also on the default.cfm page in layout folder). It seems to call the function, however the variable of good is set on the processlogin.cfm page and I don't really know how it can be used in the response js.
I am having a hard time getting my head around controllers, services, layout, etc. and how data passes. Also, I have not done a lot of cfscripting, and alot of the code you all are writing now is in cfscript...it is taking me a while to convert that over in my head.
Anyway, thanks first for blogging about this framework, would not have known about it if not. Also thanks for any help you can give me...
The more example code from anyone using this framework would be great for not only me, but for anyone else that wants to check out FW/1.
Thanks again.
Dan
Dan, I would _strongly_ urge you to start more slower in FW/1. Before you start mixing Ajax into it, get a bit more comfortable with FW/1 in general. Know what I mean? I'm a big believer in baby steps. :)
You are probably right, I should just sumbit from page to page to page.
Thanks for all the information.
Isn't there a way to use the cflogin tag instead of the cfset. Then if you do how is the logout going to be handled?