Creating User Feedback With CFWindow

February 12, 2009

I hate javascript alert(‘boxes’). They’re boring, ugly and a lousy way to do user feedback. I recently put together the following demonstration of a more elegant system to present error message and feedback to user based on the coldfusion CFWindow tag. If you’ve never used CFwindow before, this tag automagically creates a div which can be populated with pretty much anything; dynamic data, images, even flash.

One thing to note about this code is that nowhere in the code will you actually see <cfwindow…> which is the tag built-in to Coldfusion. the CFWindow tag is very simple to use but in order to make your app really stand out, we need to get into the API which is built from the amazing EXT javascript library This allows us to dynamically set the window size, change the header style of the window and much more.

Have a look at the code. I’ve commented a lot (for me anyways) throughout the code so it should be fairly straightforward.

One note of warning. WordPress likes to mess with some of the text in and especially ” quotes even using the code tag so this may require a bit of cleaning up after you cut and paste.

<!---
Error & Messages 2.0

PreReqs: CF8, Basic Knowledge of CFWindow and java_script
Database not required
Files required: index.cfm (This file) and msgWindow.cfm

contents of msgWindow.cfm

<cfoutput>
<!--- displays the content of url.msg which is passed in the ColdFusion.Window.create call --->
<span style="font-size:.8em;font-family:tahoma;">
#url.msg#
</span>
</cfoutput>

Both files must be in the same directory.

Description
java_script alert() messages are so turn of the century. This tutorial uses CFwindow to create custom error and informational messages. The main function is showMsg() which creates the window and takes several parameters to set up how it looks and what messages are displayed. Unlike basic usage of the cfwindow tag, this uses the Coldfusion AJAX API (via cfajaximport) to create windows at runtime (so you don't have to know how they're going to look or  what they'll contain in advance)

We also take advantage of changing styles of the cf window (didn't know you could do that?) to provide appropriate visual cues for the user.

Since we are creating the window at runtime, it gives us the opportunity to automagically resize the window to fit our message text. This is something you can't do with the simple cfwindow tag as you have to hard code the window size in advance and it can look a bit clunky as a one size fits all message box.

The simplest usage of this function is

function sayHello(){
var d = new Date();
var msg = 'Hello World';
var wid = d.getMinutes()+d.getSeconds();
showMsg(wid, 'Message',msg, 75,400);
}

You can use this on any page to call a window. You can put pretty much anything in the message including html
--->
<!--- index.cfm --->
<!--- import the cfwindow tag so we can call via ColdFusion.Window.create at runtime --->
<CFAJAXIMPORT TAGS="cfwindow">
<html>
<head>
<style ="text/css">
/*change the default cfwindow style and make the header text red*/
.errHdr { background: url(/CFIDE/scripts/ajax/resources/ext/images/default/layout/panel-title-greylight-bg.gif) repeat-x ;
color:#ff0000;
font:normal 11px tahoma, verdana, helvetica;
text-align: center;
padding:5px;
font-weight:bold;}
.x-dlg .x-dlg-close {
background-image: url(/CFIDE/scripts/ajax/resources/ext/images/default/basic-dialog/close.gif);
}
/*change the default cfwindow style*/
.msgHdr { background: url(/CFIDE/scripts/ajax/resources/ext/images/default/layout/panel-title-greylight-bg.gif) repeat-x ;
color:#000;
font:normal 11px tahoma, verdana, helvetica;
text-align: center;
padding:5px;
font-weight:bold;}
.x-dlg .x-dlg-close {
background-image: url(/CFIDE/scripts/ajax/resources/ext/images/default/basic-dialog/close.gif);
}
</style>

<script type="text/javascript">
function showMsg(){
//create the window
//param 1 = showMsg.arguments[0] (wid)
//param 2 = showMsg.arguments[1]  (header text)
//param 3 = windowurl and showMsg.arguments[2](msg variable)
//cfwindowparams {height:showMsg.arguments[3],width:showMsg.arguments[4]
// the other params can be hardcoded since we want error messages to be modal and closeable.
//(you could expand this technique to have even more control if you want)
ColdFusion.Window.create("myWindow"+showMsg.arguments[0], showMsg.arguments[1], "msgWindow.cfm?msg=" + showMsg.arguments[2], {height:showMsg.arguments[3],width:showMsg.arguments[4],modal:true,closable:true, draggable:false,resizable:false,center:true,initshow:true,refreshOnShow:true});
//apply styles based on header message
///you could have as many styles as you want and use a switch block instead of if else
if(showMsg.arguments[1]=='Error Message')
{
document.getElementById(ColdFusion.Window.getWindowObject("myWindow"+showMsg.arguments[0]).header.id).className = "errHdr";
}
else{
document.getElementById(ColdFusion.Window.getWindowObject("myWindow"+showMsg.arguments[0]).header.id).className = "msgHdr";
}
// clear window objectCache
ColdFusion.objectCache["myWindow"+showMsg.arguments[0]] = null;
}

function showHelp()
//this is the basic info box. A slighlty more advanced version of hello world
{
var d = new Date()
var msg = 'This displays an informational message. You can populate this statically, from a db on page load or via an ajax call.'
msg = msg + ' Notice how the header style of the CFWindow changes based on param 2 of the showMsgjava_script call'
msg = msg + ' You can even include html, links and images <div> <a href=\"http://fusebox.org\">Get Fusebox</a><br><br><img src=\"http://fusebox.org/css/images/fuseboxlogo.gif\" style=\"width:300px;\"></div>'
msg=msg + ' <br> just rememberjava_script wants you to escape the double quotes with a front slash \\"'
var wid = d.getMinutes()+d.getSeconds()
showMsg(wid, 'Help Window',msg, 275,450);

}

function checkField()
//this function checks for a value in a form field and pops up an error if none exists
//<standard rant> Remember! Client side validation is a convenience and not a security decision.
//Always use sever side validation and cfqueryparam </standard rant>
{
if (document.getElementById('testField').value=='')
{
var d = new Date()
var msg = 'Please Enter A Value.'
msg = msg + ' You can use this for client side error checking, ajax return errors etc. '
msg = msg + 'Notice the change in header based on the style errHdr. You can populate this statically like we are doing here, from a db on page load or via an ajax call.'

var wid = d.getMinutes()+d.getSeconds()
showMsg(wid, 'Error Message',msg, 130,400);
return false;}
else
{
return true;
}
}

function showWindowResize(theVal)
{
//This function show how we can dynamically fit the window to the message
//for simplicity we'll just set up a single long msg string. In real life you'd probably be pulling various length'ed
//strings from a db
var msg = 'This displays an informational message. You can populate this statically, from a db on page load or via an ajax call.'
msg = msg + ' Thejava_script looks at the <span style=\"font-size:1.3em;\">size</span> of the string passed and resizes the cfWindow to -fit- (mostly)'
msg = msg + ' Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim'
msg = msg + ' veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. '
msg = msg + ' Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. '
msg = msg + ' Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
msg = msg + ' <strong>Neque porro quisquam est</strong>, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam'
msg = msg + ' eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis '
msg = msg + ' nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur Quis autem '
msg = msg + ' vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum'
msg = msg + ' fugiat quo voluptas nulla pariatur'

//we'll trim the message to various lengths to show how the code resizes the window.
//we're taking the values passed by button onclick event and trimming the string using substr
// you wouldn't usually need this in a standard application.

switch  (theVal){
case 150:
msg = msg.substring(0,120)
break;
case 300:
msg = msg.substring(0,250)
break;
case 500:
msg = msg.substring(0,450)
break;
case 600:
msg = msg.substring(0,550)
break;
case 700:
msg = msg.substring(0,650)
break;
case 800:
msg = msg.substring(0,750)
break;
case 1000:
msg
break;
}
//for the demo we'll get the message length and assign it to a variable to show in the header of the cfwindow
// wouldn't usually be used in an application although it does show how you can add messages to a header
hdr = 'Message Length is ' + msg.length

//determine the length of the msg variable and set the window ehight accordlingly. vHeight will need
//to be adjusted based on your font size, whether you want scroll bars etc.
if(msg.length < 150) {
vHeight = 85;
} else if(msg.length > 149 && msg.length < 300) {
vHeight = 125;
} else if(msg.length > 299 && msg.length < 500) {
vHeight = 175;
} else if(msg.length > 499 && msg.length < 600) {
vHeight = 200;
} else if(msg.length > 599 && msg.length < 700) {
vHeight = 225;
} else if(msg.length > 699 && msg.length < 800) {
vHeight = 285;
} else {
vHeight = 400;
}
//variable wid is a timestamp that will help ensure that each window instance is unique to avoid caching issues
var d = new Date()
var wid = d.getMinutes()+d.getSeconds()
//a non-demo would not use the hdr length variable
//showMsg(wid, 'Help Window', vHeight,400);

//this is the main call
//the attributes passed are
//wid = unique timestamp
//header title
//msg = message body
//vHeight = cfwindow height variable
//cfwindow width = 400 could be a variable here as well but is static for this demo
showMsg(wid, 'Help Window ' + hdr, msg, vHeight, 400);
}
</script>
</head>
<script  type="text/java_script">
function sayHello(){
var d = new Date();
var msg = 'Hello World';
var wid = d.getMinutes()+d.getSeconds();
showMsg(wid, 'Message',msg, 75,400);
}

</script>

<body>

<button onClick="showHelp();">Help with Image and Link</button>    <br>
<button onClick="showWindowResize(150);">Show Window Resize 150 Chars</button>    <br>
<button onClick="showWindowResize(300);">Show Window Resize 300 Chars</button>    <br>
<button onClick="showWindowResize(500);">Show Window Resize 500 Chars</button>    <br>
<button onClick="showWindowResize(600);">Show Window Resize 600 Chars</button>    <br>
<button onClick="showWindowResize(700);">Show Window Resize 700 Chars</button>    <br>
<button onClick="showWindowResize(800);">Show Window Resize 800 Chars</button>    <br>
<button onClick="showWindowResize(1000);">Show Window Resize +800 Chars</button>    <br>
<form method="post" action="index.cfm" onSubmit="return checkField();">
<input type="text" name="testField" id="testField">
<input type="submit" value="TestMe Without a Value">

</form>
</body>

</html>


Modifying IIS 6 to allow subdomains using a wildcard ssl certificate to use port 443

January 23, 2009

History: By default, IIS websites cannot use the same SSL port (443) for a site using the same IP address. Websites with different IPs can all use port 443. This situation is typical of a standard SSL certificate which is issued to a single host header (ie: secure.website.com). Any other attempt to connect to a site via https through a different host header (ie: www.website.com) causes a certificate error (this is by design).

This becomes an issue when trying to install a wildcard SSL certificate. Wildcard certificates provide the ability to secure all subdomains on the root domain using a single certificate. (*.website.com) examples:

root domain – website.com

subdomains -admin.website.com, shop.website.com,etc

However, after you’ve installed a wildcard certificate and try to set up the subdomain to use SSL port 443, you will receive a Port In Use error. You will not be able to start the subdomain websites with the port set to 443. The IIS MMC (inetmgr.mmc) does not provide a method of resolving this so you must use the command line. (Start -> Run -> cmd)

You need to determine the site identifier of the subdomain you wish to modify. To determine <site identifier> Open IIS MMC, click on Web Sites and view the Identifier number next to the subdomain website. In the following script, replace <site identifier> with the number shown in the IIS MMC and change subdomain.website.com to the subdomain you wish to modify. Run the script with the modifications and you should now be able to browse to https://subdomain.website.com with no error.

cscript.exe c:\inetpub\adminscripts\adsutil.vbs set /w3svc/<site identifer>/SecureBindings “:443:subdomain.website.com”

if you have multiple subdomains, you could create a batch file to run the scripts.


A Coldfusion Who’s Online Widget

January 9, 2009

Last week I set up Ray Camden’s Lighthouse Pro , a open source issue/bug tracking Coldfusion application. We are going to use this internally for project and bug tracking. One bit of functionality I wanted that wasn’t included was a Who’s Online function so everyone could see who was using the site at any given time. This gives me to opportunity to “gently” prod folks who are resistant to using the new program that “it’s the way we now do things”.  Change is hard you know.

This code is not specifically tied to any app and can be dropped in any system using application.cfc.  It would be good for forums, intranets or any other “social web” app where knowing who’s online may be a benefit.

The first bit is to define the struct we are going to use to store the information and to populate it with the newly logged in user.

On thing to note here is that I’m using a combination of CFID and Username to give me my user_cfid which is the key we are looking for. The reason for this is that we want to be able to allow multiple users with the same name to use the application. If we just keyed on username, the system couldn’t tell there were more than one and we could see name overlap problems. Because CFID is set with the session, it’s “reasonably” unique, especially when combined with username. For greater overlap prevention or for systems which only use jsessionid’s, you could use createuuid() intead of CFID.

Application.cfc onRequest in your successful login section
—————————————-

<!— begin Who’s Online section —>
<cfparam name=”session.whosOnFirst” default=”">
<!— Test for existence of UserInfo and create if necessary  —>
<cflock timeout=”15″ scope=”APPLICATION” type=”EXCLUSIVE”>
<cfif NOT isDefined(“Application.UsersInfo”)>
<cfset Application.UsersInfo = StructNew()>
</cfif>
</cflock>
<cfif isDefined(‘form.userName’)>
<!— create new user info for the struct —>
<cflock name=”#CreateUUID()#” timeout=”15″ type=”EXCLUSIVE”>
<cfset user_cfid = Evaluate(CFID) & “,” & form.userName>
<cfset user_time = Now()>
</cflock>
<!— set a session id so we can use it to verify that this session is still active in application.cfm —>
<cfset session.whosOnFirst = user_cfid>
<cflock scope=”APPLICATION” type=”EXCLUSIVE” timeout=”15″>
<!— If the user does not exist in the struct, insert it —>
<cfif NOT StructKeyExists(Application.UsersInfo, user_cfid)>
<cfset temp = StructInsert(Application.UsersInfo, user_cfid, user_time)>
</cfif>
</cflock>
</cfif>
<!— end Who’s Online section —>
the second bit does the updating and checking of user activity and either updates or deletes the user from the tracking struct

Application.cfc onRequest
—————————————-
<!— Begin Who’s Online section —>
<!— determines timeout to heck if user still is active.
set to 0 will check on each page load and will cause list to change a lot as people “timeout”
while viewing or working on pages
set to 10 minutes will give a more general idea of activity.
remember that activity timeout and session timeout may be different.
—>

<!— handle application timeout —>
<cflock timeout=”15″ scope=”APPLICATION” type=”EXCLUSIVE”>
<cfif NOT isDefined(“Application.UsersInfo”)>
<cfset Application.UsersInfo = StructNew()>
</cfif>
</cflock>

<cfset ActivityTimeout = 0>
<!— if we still have a session id but have been cleared from the struct reset the struct
this would be due to having an activity online timeout which is shorter than
your session timeout. —>

<!– check if session exists –>
<cfif structKeyExists(session,”whosOnFirst”)>
<!– check if key in who’s online exists –>
<cfif NOT StructKeyExists(Application.UsersInfo, session.whosOnFirst)>
<cfset temp = StructInsert(Application.UsersInfo, session.whosOnFirst, now())>
</cfif>
</cfif>

<cfloop collection=”#Application.UsersInfo#” item=”uName”>
<!— if the struct matches the session id then it’s us so update our activity time —>
<cfif structKeyExists(session,”whosOnFirst”)>
<cfif uName eq session.whosOnFirst>
<cfset user_cfid = uName>
<cfset user_time = Now()>
<cfset temp = StructUpdate(Application.UsersInfo, user_cfid, user_time)>
</cfif>
</cfif>
<!— look for values in the struct which are larger than ActivityTimeout
and delete them as their timeout period will have expired.   —>
<cfif Evaluate(DateDiff(“n”, StructFind(Application.UsersInfo, uName), Now())) GT ActivityTimeout>
<cfset StructDelete(Application.UsersInfo, uName)>
</cfif>
</cfloop>
<!— end Who’s Online section —>

This last bit is the display section which you can place on any page you want the widget to appear.

whosOnFirst.cfm
———————————————-
<div style=”padding:5px;”>
<cflock scope=”APPLICATION” type=”EXCLUSIVE” timeout=”10″>
<cfoutput>
Users Online : #StructCount(Application.UsersInfo)#<br>
<cfloop collection=”#Application.UsersInfo#” item=”uName”>
<cfif Uname eq session.whosOnFirst>
<div style=”font-weight:bold;border-top:1px dashed;border-bottom:1px dashed;”>#listlast(UCASE(Uname))#</div><!— this is me —>
<cfelse>
<div>#listlast(UCASE(Uname))# <br>
<span style=”font-size:.7em;”>
Last Activity : #timeformat(structfind(Application.UsersInfo,uname), “hh:mm:ss”)#
</span></div>
</cfif>
</cfloop>
</cfoutput>
</cflock>
</div>

For apps that allow both registered users and guests, you could add an additional logic section to onRequestStart which determines if a user is browser as a guest and then set user_cfid as

<cfset user_cfid = Evaluate(CFID) & “,” & “Guest”>

Since CFID is “unique-ish” we can combine it with multiple Guests and not have overlap. For larger volume sites which could have many guests, I’d recommend using the createUUID() as recommend above.

Word of warning: I’m not sure what performance implications there would be on a heavily visited forum site.  You would also want to either limit the number of users displayed by the collection cfloop, or add some css to allow for scrolling of the div so you didn’t have a list that had 300-400 visible users.

Have Fun.