Tuesday, November 27, 2007

NAnt Create SFX Archive Task

I have been working on a installer for a .NET web application with a database back-end. The installer itself deserves a series of posts since it was fairly complicated to get everything to work. An integral part of the installer was using NAnt for various things. The final stage in creating the ready to deploy executable is packaging the files in a self-extracting archive that extracts and runs the setup. With the help of Winrar and NAnt, that task can be automated. Let's get started. Requirements:

WinRar SFX options file sample:

; The path to the setup executable Setup=Setup.msi ; Extract the files to a temporary directory TempMode ; Use semi-silent mode Silent=2 ; Overwrite any existing files Overwrite=1 ; The title of the SFX archive Title=Sample Setup ; The text to show initially when the user clicks on the SFX archive (will only matter if using Silent=0) Text { The installer will extract and run the setup }

NAnt build file sample:

<project name="SampleSFX" default="createSFX" basedir=".">
 <description>createSFX task for creating a sample self-extracting installer</description>

 <!-- The name of the archive to be created -->
 <property name="archiveName" value="SampleSFX" overwrite="false" />

 <!-- The target directory from where the build will be invoked -->
 <property name="targetDirectory" value="." overwrite="false" />

 <!-- The relative path (from the targetDirectory) to directory containing the files to be archived -->
 <property name="archiveDirectory" value="<RelativePathToTheDirectoryToArchive>" overwrite="false">

 <!-- The mask of the file extensions to be archived -->
 <property name="archiveFileMask" value="*.*" overwrite="false" />

 <!-- The path to the winrar executable -->
 <property name="winrarPath" value="c:\program files\winrar\winrar.exe" overwrite="false" />

 <!-- The winrar command line switches for creating the archive -->
 <property name="winrarSwitches" value="a -ep -ep1 -r -sfxdefault.sfx" overwrite="false" />

 <!-- The name of the sfx options file to use while creating the archive -->
 <property name="sfxOptionsFile" value="sfxoptions.txt" overwrite="false" />

 <!-- The relative path (from the targetDirectory) to icon file to be used for the created archive -->
 <property name="sfxIconFile" value="images\setupIcon.ico" overwrite="false" />

 <property name="archiveDirectoryFullPath" value="${path::combine(path::get-full-path(targetDirectory), archiveDirectory)}" overwrite="true" />
 <property name="sfxOptionsFileFullPath" value="${path::combine(path::get-full-path(targetDirectory), sfxOptionsFile)}" overwrite="true" />
 <property name="sfxIconFileFullPath" value="${path::combine(path::get-full-path(targetDirectory), sfxIconFile)}" overwrite="true" />

 <target name="createSFX" description="Creates the self-extracing installer archive">
  commandline="${winrarSwitches} -z&quot;${sfxOptionsFileFullPath}&quot; -iicon&quot;${sfxIconFileFullPath}&quot; &quot;${archiveName}&quot; &quot;${archiveDirectoryFullPath}\${archiveFileMask}&quot;"
  failonerror="true" />
To run this NAnt task:
  1. Open a command prompt in the targetDirectory you specified in the above build file.
  2. Run the build file with: nant -buildfile:<>

This will create a single executable file. When the user runs the executable, the setup files will be extracted and the "Setup.msi" (or whatever you specified in the sfx options file) will be executed.

Wednesday, November 21, 2007

Getting Enabled / Disabled Active Directory Accounts in ColdFusion and T-SQL

While working on recent telephone directory project based on Active Directory, it occurred to me that I am listing all users/accounts in Active Directory regardless of the fact that they might be disabled. Usually, my workplace disables a user's account once he/she is no longer working for the company. So here is how to get a list of only enabled or only disabled accounts from Active Directory. The examples cover doing this through ColdFusion's built in <cfldap> and using T-SQL syntax by leveraging Active Directory as a linked server. I have covered setting up Active Directory as a SQL Server linked server at Querying Active Directory Through SQL Server Here is how this is done with a simple cfldap query. Before you run this code below, please replace the values in the <> This query gets the "distinguishedName", "givenName" and "sn" attributes from Active Directory. The complicated part is the filter it uses. Here is the breakdown:
  • (objectCategory=Person) tells cfldap to get only objects that match the category of "person"
  • (objectClass=User) tells cfldap to get only objects that match the class of "user"
  • (!(userAccountControl:1.2.840.113556.1.4.803:=2)) tells cfldap to get only objects for witch when a bitwise AND is applied to the "userAccountControl" attribute and the number 2, the result does not equal to 0. This is a bit complicated but what it means is that if the bitwise operations does not return 0, the account is not disabled. To specify that only disabled accounts should be returned instead, we would use "(userAccountControl:1.2.840.113556.1.4.803:=2)" instead of "(!(userAccountControl:1.2.840.113556.1.4.803:=2))". I did not come up with this :-) Here is Microsoft's explanation on how to use bitwise filter.
 attributes="distinguishedName,givenName,sn" timeout="0">

<cfdump var="#enabledADUsers#" />
Here is the same thing but done through a SQL Server linked server. The filter is the same but written with the T-SQL syntax. The differencs is in how the filter on the "userAccountControl" attribute is applied through T-SQL. In the code below, the line "userAccountControl & 2 = 0" is equvalent to the cfldap filter "(!(userAccountControl:1.2.840.113556.1.4.803:=2))" which specifies that only accounts that are not disabled should be returned in the query. To specify that only disabled accounts should be returned instead, we would use "userAccountControl & 2 <> 0".
select userAccountControl,
 givenName as firstName, sn as lastName,
from openquery(<replaceWithNameOfLinkedServer>, '
 select userAccountControl, distinguishedName, givenName, sn
 from ''LDAP://<replaceWithBaseDN>''
 where objectCategory = ''Person''
 objectClass = ''user''
 order by sn
where userAccountControl & 2 = 0
Here are some links on the subject:
How to query Active Directory by using a bitwise filter
How to use the UserAccountControl flags to manipulate user account properties
How Can I Get a List of All the Disabled User Accounts in Active Directory?
Retrieving Basic Active Directory Account Settings in C#

Wednesday, November 14, 2007

Useful Prototype.js String Functions

Often while doing web development with JavaScript there is a need to do some kind of string manipulation. Some of the more common tasks are checking if the string is empty and replacing some parts of the string but it doesn't have to stop there. Sometimes I want to capitalize the first letter in a string or check if the string ends with a certain character. In the past I would have to write functions most often involving regular expressions to do each of those trivial tasks. Luckily, in the present my favorite JavaScript library Prototype.js already comes with the mentioned above plus a bunch more string functions. Here are some that I use often:

  • empty() - checks if a string is empty
    var myEmptyString = "";
    if (myEmptyString.empty()) alert ('myEmptyString is empty');
  • blank() - checks if a string is either empty or contains white space
    var myBlankString = " ";
    if (myBlankString.blank()) alert ('myBlankString is blank');
  • capitalize() - capitalizes the first letter in a string
    var myString = "lower_case_string";
    // Will show an alert with "Lower_case_string"
  • gsub(pattern, replacement) - replaces a pattern in the string with the string specified in 'replacement'
    var myString = "some_string";
    // Will show an alert with "some string"
    alert(myString.gsub('_', ' '));
  • endsWith(substring) - checks if the specified string ends with the provided 'substring'
    var myString = "some string_";
    // Will show an alert with "myString ends with _"
    if (myString.endsWith('_')) alert('myString ends with _');
Many more can be found at the Prototype API reference

Thursday, November 08, 2007

Getting Started with NAnt - .NET Build Tool

As part of working on a installer for .NET web application, I decided to use NAnt (stands for Not Ant) which is a .NET equivalent of the popular Java based ANT build tool. If you don't know what a build tool is, check out the Build Tool article on Wikipedia. The beauty of NAnt is in the fact that you can automate many tasks and once you have a working build file, you do not have to worry about it again. Here is some of you can do with the help of NAnt:
  • Install and register ASP.NET with IIS without user intervention
  • Start/stop/pause or install windows services
  • Zip or unzip files
  • Changes values inside text or xml files
  • Create files and/or directories
  • Copy/move files and/or directories
  • Set directory and/or file security permissions
  • Much more
  • If that is not enough, you can always extend NAnt with your own custom tasks (written in VB.NET or C#)
That being said, here are the steps to get started with NAnt:
  1. Get NAnt from
  2. Get NAntContrib from
  3. Extract NAnt in "C:\Program Files\NAnt"
  4. Extract NAntContrib\bin in "C:\Program Files\NAnt\bin"
  5. Create a NAnt.bat file in C:\Windows with the following:
    @echo off
    "C:\Program Files\NAnt\bin\nant.exe" %*
  6. Create and run a sample build file. Build files can perform many functions, from compiling your .NET application to downloading files from the net. For a list of Tasks, see the NAnt Tasks list and the NAntContrib Tasks list.
Below is a sample build file that takes a currently installed web application, pre-compilies it and creates a zip file of the pre-compiled application ready to be deployed:
<?xml version="1.0"?>
<project name="Ensemble" default="deploy" basedir="." xmlns="">
 <description>Precompiles and zips the Ensemble project</description>

  <property name="debug" value="true" overwrite="true" />

 <!-- The target directory where the application will be deployed and the zip file created -->
  <property name="targetDirectory" value="." overwrite="false" />

 <!-- The temporary directory where the web application will be precompiled -->
  <property name="deployTarget" value="${path::combine(path::get-full-path(targetDirectory), 'deploy')}" overwrite="true" />

  <!-- The virtual directory that the web application resides in -->
  <property name="virtualDirectory" value="webApplication" overwrite="true" />

 <!-- The name of the zip file to create when zipping the deployed web application -->
  <property name="deployZipFilename" value="" overwrite="true" />

 <!-- The location where the zip file will be created -->
 <property name="deployZipFileLocation" value="${path::combine(path::get-full-path(targetDirectory), deployZipFilename)}" overwrite="true" />

 <!-- The location of the .NET Framework directory (for version 2) -->
  <property name="dotnetLocation" value="${framework::get-framework-directory('net-2.0')}" overwrite="true" />

  <!-- 'clean' target deletes the previously created zip file and deploy directory -->
  <target name="clean" description="Remove all generated files">
  <!-- Delete the existing zip file -->
    <delete file="${deployZipFileLocation}" if="${file::exists(deployZipFileLocation)}" />
  <!-- Delete the existing deploy directory -->
    <delete dir="${deployTarget}" if="${directory::exists(deployTarget)}" />

  <!-- 'build' target precompiles this ASP.Net application into the deployTarget directory -->
  <target name="deploy" description="Precompiles the web application and creates a zip file for it" depends="clean">
    <!-- Precompile the web application with the built-in .NET utility -->
     commandline="-nologo -fixednames -v ${virtualDirectory} "${deployTarget}""
     failonerror="true" />

    <!-- Clean up the "deployTarget" directory -->
    <!-- Delete any visual studio related files -->
    <include name="${deployTarget}/*.TempSolution" />
    <include name="${deployTarget}/**/*.scc" />
    <include name="${deployTarget}/**/*.resx" />
    <include name="${deployTarget}/**/*.txt" />
    <include name="${deployTarget}/**/*.db" />
    <include name="${deployTarget}/**/*.vssscc" />
    <!-- Delete all the files in the upload and files directory -->
    <include name="${deployTarget}/upload/**/*" />
    <include name="${deployTarget}/files/**/*" />

    <!-- Create a zip file from precompiled web application -->
    <zip zipfile="${deployZipFileLocation}" includeemptydirs="true">
      <fileset basedir="${deployTarget}">
        <include name="**/*" />

    <!-- delete the deployTarget directory -->
    <delete dir="${deployTarget}" />

Wednesday, October 24, 2007

I Knew I'm not a Nerd - My NerdTests Results

Whenever people would accuse me of being a nerd I always protested. I knew there was a line between nerds and geeks and here is my test scores to prove it: says I'm a Cool Technology Geek. What are you? Click here!

Thursday, October 18, 2007

SQL Server Script to Restore a Database from File

I frequently have to backup a database from the production machine and restore it in my testing environment. You can do this with SQL Server Enterprise Manager (or other similar tools) but it can get to be a pain if you have to it over and over again. So here is a script to the second part - restore a database from a file to sql server. For the first part, please see my related post called SQL Server Script to Backup a Database to File. To use this script you need to:
  1. Set the database name in the @databaseName variable to your database
  2. Set the @restoreDirectory variable to the absolute directory path where the database backup is found
  3. Run the script (with something like Query Analyzer)
The script will restore a backup of the specified database from the restore directory specified in the @restoreDirectory variable. It will look for a backup filename that consists of the database name, today's date and the '.bak' extension (example: myDatabase-10.18.2007.bak).

Download the script from
-- Restore database from file
use master

declare @backupFileName varchar(100), @restoreDirectory varchar(100),
 @databaseDataFilename varchar(100), @databaseLogFilename varchar(100),
 @databaseDataFile varchar(100), @databaseLogFile varchar(100),
 @databaseName varchar(100), @execSql nvarchar(1000)

-- Set the name of the database to restore
set @databaseName = 'myDatabase'
-- Set the path to the directory containing the database backup
set @restoreDirectory = 'aboslute_path_to_restore_directory' -- such as 'c:\temp\'

-- Create the backup file name based on the restore directory, the database name and today's date
set @backupFileName = @restoreDirectory + @databaseName + '-' + replace(convert(varchar, getdate(), 110), '-', '.') + '.bak'

-- Get the data file and its path
select @databaseDataFile = rtrim([Name]),
 @databaseDataFilename = rtrim([Filename])
from master.dbo.sysaltfiles as files
 inner join
 master.dbo.sysfilegroups as groups
 files.groupID = groups.groupID
where DBID = (
  select dbid
  from master.dbo.sysdatabases
  where [Name] = @databaseName

-- Get the log file and its path
select @databaseLogFile = rtrim([Name]),
 @databaseLogFilename = rtrim([Filename])
from master.dbo.sysaltfiles as files
where DBID = (
  select dbid
  from master.dbo.sysdatabases
  where [Name] = @databaseName
 groupID = 0

print 'Killing active connections to the "' + @databaseName + '" database'

-- Create the sql to kill the active database connections
set @execSql = ''
select @execSql = @execSql + 'kill ' + convert(char(10), spid) + ' '
from master.dbo.sysprocesses
where db_name(dbid) = @databaseName
 DBID <> 0
 spid <> @@spid
exec (@execSql)

print 'Restoring "' + @databaseName + '" database from "' + @backupFileName + '" with '
print '  data file "' + @databaseDataFile + '" located at "' + @databaseDataFilename + '"'
print '  log file "' + @databaseLogFile + '" located at "' + @databaseLogFilename + '"'

set @execSql = '
restore database [' + @databaseName + ']
from disk = ''' + @backupFileName + '''
  file = 1,
  move ''' + @databaseDataFile + ''' to ' + '''' + @databaseDataFilename + ''',
  move ''' + @databaseLogFile + ''' to ' + '''' + @databaseLogFilename + ''',

exec sp_executesql @execSql

exec('use ' + @databaseName)

-- If needed, restore the database user associated with the database
exec sp_revokedbaccess 'myDBUser'

exec sp_grantdbaccess 'myDBUser', 'myDBUser'

exec sp_addrolemember 'db_owner', 'myDBUser'

use master

Wednesday, October 17, 2007

SQL Server Script to Backup a Database to File

I frequently have to backup a database from the production machine and restore it in my testing environment. You can do this with SQL Server Enterprise Manager (or other similar tools) but it can get to be a pain if you have to it over and over again. So here is a script to do the first part - backup a database from a sql server to a file. For the second part, please see my related post called SQL Server Script to Restore a Database from File. To use this script you need to:
  1. Set the database name in the @databaseName variable to your database
  2. Set the absolute path for the backup directory in the @backupDirectory variable to the directory where the backup will be created
  3. Run the script (with something like Query Analyzer)
The script will create a backup of the specified database and put in the directory specified in the @backupDirectory variable. The actual backup name will be based on the database name, today's date and the '.bak' extension (example: myDatabase-10.17.2007.bak).

Download the script from
-- Backup database to file
declare @backupFileName varchar(100), @backupDirectory varchar(100),
 @databaseDataFilename varchar(100), @databaseLogFilename varchar(100),
 @databaseDataFile varchar(100), @databaseLogFile varchar(100),
 @databaseName varchar(100), @execSql varchar(1000)

-- Set the name of the database to backup
set @databaseName = 'myDatabase'
-- Set the path fo the backup directory on the sql server pc
set @backupDirectory = 'aboslute_path_to_backup_directory' -- such as 'c:\temp\'

-- Create the backup file name based on the backup directory, the database name and today's date
set @backupFileName = @backupDirectory + @databaseName + '-' + replace(convert(varchar, getdate(), 110), '-', '.') + '.bak'

-- Get the data file and its path
select @databaseDataFile = rtrim([Name]),
 @databaseDataFilename = rtrim([Filename])
from master.dbo.sysaltfiles as files
 inner join
 master.dbo.sysfilegroups as groups
 files.groupID = groups.groupID
where DBID = (
  select dbid
  from master.dbo.sysdatabases
  where [Name] = @databaseName

-- Get the log file and its path
select @databaseLogFile = rtrim([Name]),
 @databaseLogFilename = rtrim([Filename])
from master.dbo.sysaltfiles as files
where DBID = (
  select dbid
  from master.dbo.sysdatabases
  where [Name] = @databaseName
 groupID = 0

print 'Backing up "' + @databaseName + '" database to "' + @backupFileName + '" with '
print '  data file "' + @databaseDataFile + '" located at "' + @databaseDataFilename + '"'
print '  log file "' + @databaseLogFile + '" located at "' + @databaseLogFilename + '"'

set @execSql = '
backup database [' + @databaseName + ']
to disk = ''' + @backupFileName + '''
  name = ''' + @databaseName + ' backup'',


Tuesday, October 16, 2007

Unzip with UTF Support in ColdFusion - Function Updates for v0.2

Due to some shortcommings, here is an update of the "unzipWithUTFSupport" function. The change log is below. You can get the update version at
  • Support for absolute paths relative to the root of the web site
    Example: destination = "/"
  • Back slashes get converted to forward slashes
    Example: destination = "/test\blah" = "/test/blah"
  • Better support for relative paths:
    - relative to the "destination" if one is specified
    - relative to the name of the zip archive otherwise (which is used as the extract directory name)
  • Support for parent paths (relative to the executing script)
    Example: destination = "../test"
  • Support for absolute paths
    Example: destination = "d:\webroot\test"
  • All possible ways to define a path also apply to the "destination" key in the locationsByFileType structure
    Example: locationsByFileType.jpg.destination = "images\jpg"
  • No need for file type sub structure if you only care for the path where the files will be extracted
    Example: locationsByFileType.xml = "images\jpg"
    Instead of: locationsByFileType.jpg.destination = "images\jpg"
  • Corrected an omission in the build script that prevented all files from being extracted because of lower case or upper case file extensions
  • Renamed "overwriteDestination" to "deleteDestination" and set it to "false" by default

DriverMax - Reinstall your Windows Drivers within Minutes

Why didn't anybody think of this before?! Reinstalling your hardware drivers can always be a pain when reinstalling Windows. This program aims to backup your installed drivers so when you have to reinstall Windows, you can easily restore your drivers from the backup. DriverMax for Windows Vista and Windows XP - easily reinstall all your Windows drivers within a few minutes

Monday, October 15, 2007

Unzip with UTF Support in ColdFusion Curtosy of Ant via cfant

Update: Function was updated to v0.2 (see Unzip with UTF Support in ColdFusion - Function Updates for v0.2)

A fellow by the name of Ricardo Parente contacted me today about unzipping a zip file that contains files with special characters (to be more technical, the file names were using UTF8 encoding). Some filenames contained accents and/or other characters that would cause issues. He was trying to extract the zip file programmatically using the zip component from Alagad but that was throwing an exception. After looking around for a bit, turns out there is allready an existing user defined function on cflib called unzipFile. However, it seems that suffered from the same problem as the Alagad component when trying to extract files with UTF8 encoding. So after some searching, I found that the UTF encoding issue is an issue with Java and has not be fixed for 6 years!! Bah! So anyway, it turns out Ant, does support that with no problem and forutnatelly ColdFusion MX 7 comes with the undocumented function "cfant". So here is the custom function to unzip those pesky zip files contained UTF encoded file names. Createvely, I've named it "unzipWithUTFSupport". You can call it as simple as:
<cfset unzipResults = unzipWithUTFSupport("") />
or like so to specify the destination directory:
<cfset unzipResults = unzipWithUTFSupport(
	zipFile = "",
	destination = "yourRelativeDestination"
) />
This will return a structure with the following keys:
  • antMessage = string with the message Ant returned
  • argumentErrors = array of argument validation errors
  • fileList = query of files unzipped
  • success = boolean indicating the success or the failure of the unzip process
Now, for the more advanced functionality. You can have the function unzip the different file types to different sub directores. For example ".jpg" files can go in a "images" sub directory, while ".xml" files can go in a "configuration" sub directory. To take advantage of this, you need to pass the function a structure with the argument name "extractLocationsByFileType". That structure will define how what file extentions wil be extracted where. Here is a sample configuration that will extract only ".jpg" files to a "images" sub diretory:
<cfset locationsByFileType = structnew() />
<cfset locationsByFileType.jpg = structnew() />
<cfset locationsByFileType.jpg.destination = "images" />
Once you configure the "locationsByFileType" structure, you just pass it to the function like so:
<cfset results = unzipWithUTFSupport(
	zipFile = "",
	extractLocationsByFileType = locationsByFileType
) />
The important think to keep in mind is that, if you use this functionality, you will have to define the configurtion for each file type. This means that if you use the configuration above, only ".jpg" files will be extracted from the zip archive. This will return a structure with the following keys:
  • antMessage = string with the message Ant returned
  • argumentErrors = array of argument validation errors
  • fileList = a structure of queries with the unzipped files under each file extension
  • success = boolean indicating the success or the failure of the unzip process
There are a couple of optional parameters you can pass to the function. If you are not using the advanced feature defined above, you can also pass two more values to the function:
  • specialCharsMatchRegEx = string with the regular expression that will match the special characters in the file names (by default "[^A-Za-z0-9\._\-]")
  • replaceSpecialChars = boolean indicating if special characters in the file names should be replaced (by default true)
If you are using the structure that defines specific file types to be extracted thenn you can specify specific setting for the "replaceSpecialChars" and "specialCharsMatchRegEx" for each file type as so:
<cfset locationsByFileType = structnew() />
<cfset locationsByFileType.jpg = structnew() />
<cfset locationsByFileType.jpg.destination = "images" />
<cfset locationsByFileType.jpg.replaceSpecialChars = false />
<cfset locationsByFileType.jpg.specialCharsMatchRegEx = "[^A-Za-z0-9\._\-]" />
Finally, here is the function code
Function:		unzipWithUTFSupport
Created on:		10.15.2007
Author:			Boyan Kostadinov
Version:		0.1
Arguments:		zipFile (string) required
				The name of the zip file to extract

				destination (string)
				The relative destination directory to extract the zip to (by default ".")

				overwriteDestination (boolean)
				Should the destination directory to be overwritte (by default true)

				extractLocationsByFileType (struct)
				A structure containing the type of files to be extracted
				and their destinations (by default empty structure)

				<cfset locationsByFileType.jpg = structnew() />

				Specify the relative destination where to extract this type of files
				<cfset locationsByFileType.jpg.destination = "jpgFilesDir" />

				Should special characters be replaced for this type of files
				<cfset locationsByFileType.jpg.replaceSpecialChars = true />

				The regular expression to match special characters against
				<cfset locationsByFileType.jpg.specialCharsMatchRegEx = "[^A-Za-z0-9\._\-]" />

				If "replaceSpecialChars" and "specialCharsMatchRegEx" do not exist, the default
				values in the functions are used (true and "[^A-Za-z0-9\._\-]")

				replaceSpecialChars (boolean)
				Should special characters in the file names be replaced (by default true)

				specialCharsMatchRegEx (string)
				The regular expression that will match the special characters in the file names
				that need to replaced (by default "[^A-Za-z0-9\._\-]")

Return Value:	unzipResults (struct)
				A structure containing the results of the unzip process with the keys:
				success (boolean)
					the sucess of the unzip
				antMessage (string)
					the message the unzip task returns
				fileList (struct)
					the list of files/directories unzipped
Description:	I extract a zip file no matter of encoding with the help of Ant
<cffunction name="unzipWithUTFSupport" hint="I extract a zip file no matter of encoding with the help of Ant" returntype="struct">
	<cfargument name="zipFile" type="string" required="yes" />
	<cfargument name="destination" type="string" required="no" default="." />
	<cfargument name="overwriteDestination" type="boolean" required="no" default="true" />
	<cfargument name="extractLocationsByFileType" type="struct" required="no" default="#structnew()#" />
	<cfargument name="replaceSpecialChars" type="boolean" required="no" default="true" />
	<cfargument name="specialCharsMatchRegEx" type="string" required="no" default="[^A-Za-z0-9\._\-]" />

	<!--- Create local variables for the zip file name and the destination directory --->
	<cfset var zipFileName = "" />
	<cfset var unzipDestination = "" />
	<cfset var uniqueUnzipDestinationDirectory = "" />
	<cfset var unzipResults = structnew() />
	<cfset var buildMessage = "" />
	<cfset var currentDir = "" />
	<cfset var fileRenamed = false />

	<!--- Set the name of the temporary ant build file --->
	<cfset var buildFile = expandpath("unzip.xml") />

	<cfset unzipResults.success = false />
	<cfset unzipResults.antMessage = "" />
	<cfset unzipResults.fileList = 0 />
	<cfset unzipResults.argumentErrors = arraynew(1) />

	<!--- Set the name of the zip file to extract --->
	<cfif arguments.zipFile neq "">
		<cfset zipFileName = expandPath(arguments.zipFile) />

		<cfif not fileExists(zipFileName)>
			<cfset arrayappend(unzipResults.argumentErrors, "The zip file #arguments.zipFile# was not found") />
		<cfset arrayappend(unzipResults.argumentErrors, "The zip file was not specified") />

	<!--- Set the extract destination --->
	<cfif arguments.destination neq "">
		<cfset unzipDestination = expandPath(arguments.destination) />
		<cfset arrayappend(unzipResults.argumentErrors, "Destination was empty") />

	<!--- If there were not argument validation errors --->
	<cfif arrayisempty(unzipResults.argumentErrors)>
		<!--- Create a directory for the zip file based on the name of the zip archive --->
		<cfset uniqueUnzipDestinationDirectory =
			unzipDestination & "\" &
			rereplacenocase(arguments.zipFile, "\.zip$", "") />

		<cfdump var="#uniqueUnzipDestinationDirectory#" />
		<!--- Create the xml string for the ant build file --->
		<!--- If the "extractLocationsByFileType" structure is not empty, loop over it
		to extract the different files types to the specified sub directory --->
		<cfsavecontent variable="unzipXml">
			<target name="unzip">
				<cfif not structisempty(extractLocationsByFileType)>
					<cfloop list="#structKeyList(extractLocationsByFileType)#" index="key">
						<cfif isstruct(extractLocationsByFileType[key])>
				<unzip src="#zipFileName#" dest="#uniqueUnzipDestinationDirectory#\#lcase(extractLocationsByFileType[key].destination)#">
						<include name="**/*.#lcase(key)#"/>
				<unzip src="#zipFileName#" dest="#uniqueUnzipDestinationDirectory#\#lcase(extractLocationsByFileType[key])#">
						<include name="**/*.#lcase(key)#"/>
				<unzip src="#zipFileName#" dest="#uniqueUnzipDestinationDirectory#" />

			<!--- If the destination directory already exists, delete it --->
			<cfif directoryExists(uniqueUnzipDestinationDirectory) and arguments.overwriteDestination>
				<cfdirectory action="delete" directory="#uniqueUnzipDestinationDirectory#" recurse="yes" />

			<!--- Write the temporary ant build file on the file system --->
			<cffile action="write" nameconflict="overwrite" file="#buildFile#" output="#unzipXml#" />
			<!--- Execute the ant task with the created ant build file --->
			<!--- "messages" holds the coldfusion variable to write the ant output to --->
			<!--- "target" is the name of the "defaultTarget" (ant speak) to execute when runing the ant task --->
			<cfant buildFile="#buildFile#"

			<cfif refindnocase(".*unable to expand to file.*", buildMessage)>
				<cfset unzipResults.success = false />
			<cfelseif refindnocase(".*build successful.*", buildMessage)>
				<cfset unzipResults.success = true />

			<cfset unzipResults.antMessage = buildMessage />

			<!--- Delete the temporary ant build file --->
			<cffile action="delete" file="#buildFile#" />

			<cfif unzipResults.success>
				<cfif not structisempty(extractLocationsByFileType)>
					<cfset unzipResults.fileList = structnew() />
					<cfloop list="#structKeyList(extractLocationsByFileType)#" index="key">
						<cfif isstruct(extractLocationsByFileType[key])>
							<cfset currentDir =
							uniqueUnzipDestinationDirectory & "\" &
							extractLocationsByFileType[key].destination />
							<cfif structkeyexists(extractLocationsByFileType[key], "replaceSpecialChars")>
								<cfif isboolean(extractLocationsByFileType[key].replaceSpecialChars)>
									<cfset replaceSpecialChars = extractLocationsByFileType[key].replaceSpecialChars />
							<cfif replaceSpecialChars>
								<cfif structkeyexists(extractLocationsByFileType[key], "specialCharsMatchRegEx")>
									<cfset specialCharsMatchRegEx = extractLocationsByFileType[key].specialCharsMatchRegEx />
							<cfset currentDir = uniqueUnzipDestinationDirectory & "\" &
							extractLocationsByFileType[key] />
						<!--- Get a list of the files in the directory --->
							directory="#currentDir#" name="currentFileList" />
						<cfset fileRenamed = false />
						<cfif replaceSpecialChars>
							<!--- Loop over all the files --->
							<cfloop query="currentFileList">
								<!--- If the filename has special characters --->
								<cfif refind(specialCharsMatchRegEx, name)>
									<!--- Create a new name for the file by replacing all special characters with "_" --->
									<cfset newName = rereplace(name, specialCharsMatchRegEx, "_", "all") />
									<!--- Rename the file to the new name --->
										destination="#directory#\#newName#" />
									<cfset fileRenamed = true />
						<cfif fileRenamed>
							<!--- Get a list of the files in the directory
						(again since some files might have been renamed) --->
								directory="#currentDir#" name="currentFileList" />
						<cfset unzipResults.fileList[key] = currentFileList />
					<!--- Get a list of the files in the directory --->
						directory="#uniqueUnzipDestinationDirectory#" name="currentFileList" />
					<cfset fileRenamed = false />
					<cfif arguments.replaceSpecialChars and specialCharsMatchRegEx neq "">
						<!--- Loop over all the files --->
						<cfloop query="currentFileList">
							<!--- If the filename has special characters --->
							<cfif refind(specialCharsMatchRegEx, name)>
								<!--- Create a new name for the file by replacing all special characters with "_" --->
								<cfset newName = rereplace(name, specialCharsMatchRegEx, "_", "all") />
								<!--- Rename the file to the new name --->
									destination="#directory#\#newName#" />
								<cfset fileRenamed = true />
					<cfif fileRenamed>
						<!--- Get a list of the files in the directory
						(again since some files might have been renamed) --->
							directory="#uniqueUnzipDestinationDirectory#" name="currentFileList" />
					<cfset unzipResults.fileList = currentFileList />
		<cfcatch type="any">
			<cfdump var="#cfcatch#" />

	<cfreturn unzipResults />

Friday, October 12, 2007

Get a User's Primary Group Membership from Active Directory with ColdFusion

Recently, I've had to do much work with Active Directory/LDAP. I needed a way to grab the user's primary group so I can set some permissions in my application based on the primary group the user is a member of. This turned out to be a bit tricky since the primary group for each user is not part of the list of groups the user is a member of. Instead, the primary group token (just an ID) is stored in each user's record. Seems pretty simple now that I got it working but there was a big lack of documentation on how to do this in ColdFusion. So here is the psudo code:
  1. We need to query Active Directory and get the value of the "primaryGroupID" for the selected user
  2. We also need a full list of groups in Active Directory (this is acheived with using the filter "(&(objectcategory=group))")
  3. Next, we need to get the group name from the full list of groups based on the primary group token we got from the user's record
  4. Bingo!
Here is the code:
<!--- Get the primary group id for an example user --->
	maxrows="1" />

<!--- Query the ldap server for the full list of groups --->
	timeout="0" />

<!--- Beacause the list of groups the user belongs to,
does not contain the user's primary group, query the
ldap groups to get only the group name for witch
the value of the primaryGroupTokenAttribute
matches the value of the primaryGroupIDAttribute --->
<cfquery name="primaryGroupQuery" dbtype="query">
select	lower(name) as name
from	groupsQuery
where	primaryGroupToken = '#userLdapQuery.primaryGroupID#'

<!--- Get the primary group name from the query resutls --->
<cfset primaryGroup = primaryGroupQuery["name"][1] />

Thursday, October 11, 2007

Must Have Tools for Windows Power Users

This is a list of must have tools with inspiration from blog post at

Launchy: - An open source keystroke launcher for Windows
XYPlorer: - A windows file manager and explorer replacement
Hamachi: - Software based VPN
FolderShare: - Sync files/directories between multiple computers
RoboForm: - An automated form filer and password manager
Altiris Software Virtualization: - A virtual engine for applications
Paint.NET: - Free image and photo editing software
Quest Comparison Suite for SQL Server: - Free Microsoft SQL server comparison suite

Tuesday, October 09, 2007

Neat Prototype Trick - Activate First Form Element Without a Certain Class Name

Let's say you have form with a bunch of fields that you have disabled with the "read only" attribute so the user can't change the data. Also, you have applied a css class called "readOnlyInput" so you can style the input field as read only field (different color or whatever else you like). I had such form this evening but I wanted the focus to automatically go to the first non read only element when the page is loaded. Usually, you can pass focus on a form element by doing this:
// 'myFormField' is the id of form field you want to focus on
However, if the input field already contains data, you might want to focus the field and highlight it in the same time. In that case you will do:
// 'myFormField' is the id of form field you want to focus on
That is all fine and great but what when I have a bunch of input elements and some of them might be marked "read only". To make things more complicated (as I love to do), I would hate to hard code the id of the first element I want to focus/activate. So here is the solution:
// For each input element on the form
$A($('myForm').getInputs()).each(function(formElement) {
 // If the form element does not have the class "readOnlyInput"
 if (!$(formElement).hasClassName('readOnlyInput')) {
  // Activate that element
  // Break out of the loop (prototype specific syntax here)
  throw $break;
Just so Doug's kids would understand this, here it is in psudo code:
  1. Get a list of form input elements by using Prototype's built in "getInputs" function $('myForm').getInputs()
  2. Convert the list of form inputs to a Prototype array $A($('myForm').getInputs())
  3. Now for each of the elements in the array, run a function pass in the current element $A($('myForm').getInputs()).each(function(formElement)
  4. If the current form element has a css class not matching "readOnlyInput" if (!$(formElement).hasClassName('readOnlyInput'))
  5. Activate that form element $(formElement).activate();
  6. We found the first element so break out of the loop throw $break;

Monday, October 08, 2007

ColdFusion CFC Scope Exploration

Doug Boude's post at, prompted me to create this simple test that should show the difference between using the "this" scope, the "variables" scope and the "var" scope. Here is what you need to get things setup. Declare a test component as follows:
<cfcomponent displayname="thisScopeToUseOrNotToUse">
 <cffunction name="testMethod">
  <!--- Create a variable in the local scope of the function --->
  <cfset var iAmAccessableOnlyWithinThisMethod = true />

  <!--- Create a variable in the "this" scope --->
  <cfset this.iAmAccessableFromOutside = true />

  <!--- Create variable in the private component scope --->
  <cfset variables.iAmAccessableOnlyWithinThisCFC = true />

 <cffunction name="getIAmAccessableOnlyWithinThisCFCButCanBeViewedFromTheOutsideWithAPublicMethod">
  <cfreturn variables.iAmAccessableOnlyWithinThisCFC />
Then declare a test file as follows:
<cfset testCFC = createObject("component", "thisScopeToUseOrNotToUse") />
<cfset testCFC.testMethod() />
<p />
Try to read a variable declare in the "this" scope of the component
<p />
<cfdump var="#testCFC.iAmAccessableFromOutside#" />
<p />
<p />
Try to read a variable declared in the private component scope (the "variables" scope)
<p />
 <cfdump var="#testCFC.iAmAccessableOnlyWithinThisCFC#" />
<cfcatch type="any">
 <cfdump var="#cfcatch.message#" />
<p />
<p />
Using a public method to read a variable in the private component scope (the "variables" scope):
<p />
<p />
<p />
<strong>Trying to read a variable declared only in the method scope:
<p />
 <cfdump var="#testCFC.iAmAccessableOnlyWithinThisMethod#" />
<cfcatch type="any">
 <cfdump var="#cfcatch.message#" />
Here are the results: ColdFusion "this" Scope Test Now, for the explanation:
  1. First we create a instance of the compontent with "createObject"
  2. Call the "testMethod" of the component so we can set the variables
  3. Then we try to access the "iAmAccessableFromOutside" variable and get "true" since that was the value set to the variable in the "testMethod" function
  4. Next, we try to read a variable declared in the private component scope "iAmAccessableOnlyWithinThisCFC". The code block is wrapped with "cfcatch" so we get a nice error message when ColdFusion cannot access the privately declare variable
  5. However, we can use the method "getIAmAccessableOnlyWithinThisCFCButCanBeViewedFromTheOutsideWithAPublicMethod" which just returns the private scope variable "iAmAccessableOnlyWithinThisCFC"
  6. Lastly, we try to access the variable "iAmAccessableOnlyWithinThisMethod" which was declared in the private "var" scope of the "testMethod" function. The code block is wrapped with "cfcatch" so we get a nice error message when ColdFusion cannot access the privately declare variable

Friday, October 05, 2007

Web Page Syntax Highlighting for ColdFusion - Brush for SyntaxHighlighter

I have been working on a implementing a solution to highlight ColdFusion code snippets when they are being posted on your web site/blog. Highlighting the code according to the appropriate language can make it much easier to read and comprehend (and make it pretty of course). To do this, I have created a ColdFusion syntax "brush" for the JavaScript highlighter SyntaxHighlighter (formally dp.SyntaxHighlighter). In this context, a "brush" is a set of language specific settings for highlighting/prettifying code. SyntaxHighlighter is a pretty cool piece of software and I had some fun and faced some challenges while trying to create ColdFusion specific highlighting brush. Here is a demo of the highlighter at work when given ColdFusion code snippet:
<cfcomponent displayname="applicationController" extends="ModelGlue.unity.controller.Controller" output="false">

// Set component variables
variables.appConfig = 0;
variables.modelGlueConfig = 0;
variables.rolePermissions = structnew();
variables.userIDWithAdminRights = "";

Function: name="changeUserSiteRole"
Created on: 09.25.2007
Updated on: 09.25.2007
Author: Boyan Kostadinov
Arguments: event(ModelGlue.Core.Event)
Return Value: none
<cffunction name="changeUserSiteRole" access="public" returntype="void" output="false">
 <cfargument name="event" type="ModelGlue.Core.Event">
 <cfset var userID = arguments.event.getValue("userID", 0) />
 <cfset var siteID = arguments.event.getValue("siteID", 0) />
 <cfset var roleID = arguments.event.getValue("roleID", 0) />
 <cfset var showAllUsers = arguments.event.getValue("showAllUsers", false) />

<cfif userID neq 0 and siteID neq 0 and roleID neq 0> <!--- Call the changeUserSiteRole method in the UsersGateway to change the user's role in the specified siteID ---> <cfset variables.usersGateway.changeUserSiteRole(userID, siteID, roleID) /> </cfif>
<cfif showAllUsers> <cfset arguments.event.addResult("allUsers") /> <cfelse> <cfset arguments.event.addResult("singleUser") /> </cfif> </cffunction>
<cffunction name="onQueueComplete" access="public" returntype="void" output="false"> <cfargument name="event" type="any">
<cfset arguments.event.setValue("defaultEvent", getModelGlue().getBean("modelGlueConfiguration").getdefaultEvent()) /> <cfset arguments.event.setValue("appConfig", variables.appConfig) /> <cfset arguments.event.setValue("ApplicationVersion", variables.appConfig.GetConfigSetting("ApplicationVersion")) /> <cfset arguments.event.setValue("ApplicationLastRevisionDate", variables.appConfig.GetConfigSetting("ApplicationLastRevisionDate")) /> <cfset arguments.event.setValue("urlPrefix", variables.defaultTemplate & "?" & arguments.event.getValue("eventValue") & "=") /> <cfset arguments.event.setValue("CurrentPageUrl", getfilefrompath(cgi.script_name) & "?" & cgi.query_string) /> </cffunction>
<cffunction name="onRequestEnd" access="public" returntype="void" output="false"> <cfargument name="event" type="any"> </cffunction>
To install the SyntaxHighlighter on your page:
  1. Get it
  2. Extract it somewhere accessable from the web
  3. Add the following code
    <!-- Include the SyntaxHighlighter stylesheet -->
    <style type="text/css" media="screen">@import url("pathToSyntaxHighlighterStyle/syntaxHighlighter.css");</style>
    <!-- Include the core SyntaxHighlighter library -->
    <script language="javascript" src="pathToSyntaxHighlighter/scripts/shCore.js"></script>
    <!-- Include the ColdFusion brush -->
    <script language="javascript" src="pathToSyntaxHighlighter/scripts/shBrushColdFusion.js"></script>
    <script language="javascript">
    window.onload = function () {
     // Set the path to the flash component to enable 'copy to clipboard' in firefox
     dp.SyntaxHighlighter.ClipboardSwf = 'pathToSyntaxHighlighter/scripts/clipboard.swf';
     // Enable blogger mode (if using Blogger)
     // Highlight page elements with the name "code"
     // For configuration options see
     dp.SyntaxHighlighter.HighlightAll('code', false, true, false, 1, false);
  4. Modify the css for your preferences (Firebug is really handy here to figure out what classes you want to modify). Here are my changes:
    <style type="text/css">
    .dp-highlighter ol li, .dp-highlighter .columns div { padding: 0px 3px 0px 0px  !important; font-size: 12px !important; font-family: 'Lucida Console', 'Bitstream Vera Sans Mono', 'Courier New', Monaco, Courier, monospace; }
    .dp-highlighter .tools { padding-left: 0px; }
    .dp-highlighter .tools a { color: #000; text-decoration: underline; }
    .dp-highlighter .tools a:hover { margin-right: 10px; }
  5. Add some more brushes. For supported languages are C++, C#, CSS, Delphi, Java, JavaScript, PHP, Pythod, Ruby, SQL, VB, XML, HTML, XLST and now ColdFusion :-). More information on the languages at are the brushes I am using:
    <script language="javascript" src="scripts/shBrushCSharp.js"></script>
    <script language="javascript" src="scripts/shBrushCss.js"></script>
    <script language="javascript" src="scripts/shBrushJScript.js"></script>
    <script language="javascript" src="scripts/shBrushSql.js"></script>
    <script language="javascript" src="scripts/shBrushVb.js"></script>
    <script language="javascript" src="scripts/shBrushXml.js"></script>
  6. Post your code enclosed in "<pre>" tags as follows:
    <!-- For xml/html/xslt code -->
    <pre class="xml" name="code">
    <!-- For css code -->
    <pre class="css" name="code">
    <!-- For coldfusion code -->
    <pre class="cf" name="code">
    <!-- For sql code -->
    <pre class="sql" name="code">
What the ColdFusion brush does:
  1. Highlights ColdFusion functions, tags, attributes, strings, numbers, comments, cfscript comments
  2. Highlights a very limited set of Model-Glue specific keywords (I got adventures)
  3. Uses the Dreamweaver color shema to apply the syntax highlighting
How to use it and/or modify it:
  1. To just use it (without modifications), just include the compressed version from the svn location at
  2. To use it but modify the css styles, get the uncompressed version from and modify the css definitions in "this.Style"
  3. Include it as a brush in your page:
    <script language="javascript" src="pathTo_shBrushColdFusion.js"></script>
Here is actual brush definiton file (highlighted with the JavaScript SyntaxHighlighter brush): = function()
 this.CssClass = 'dp-coldfusion';
 this.Style = '.dp-coldfusion { font: 13px "Courier New", Courier, monospace; }' +
 '.dp-coldfusion .tag, .dp-coldfusion .tag-name { color: #990033; }' +
 '.dp-coldfusion .attribute { color: #990033; }' +
 '.dp-coldfusion .attribute-value { color: #0000FF; }' +
 '.dp-coldfusion .cfcomments { background-color: #FFFF99; color: #000000; }' +
 '.dp-coldfusion .cfscriptcomments { color: #999999; }' +
 '.dp-coldfusion .keywords { color: #0000FF; }' +
 '.dp-coldfusion .mgkeywords { color: #CC9900; }' +
 '.dp-coldfusion .numbers { color: #ff0000; }' +
 '.dp-coldfusion .strings { color: green; }';

 this.mgKeywords = 'setvalue getvalue addresult viewcollection viewstate';

 this.keywords = 'var eq neq gt gte lt lte not and or true false ' +
 'abs acos addsoaprequestheader addsoapresponseheader ' +
 'arrayappend arrayavg arrayclear arraydeleteat arrayinsertat ' +
 'arrayisempty arraylen arraymax arraymin arraynew ' +
 'arrayprepend arrayresize arrayset arraysort arraysum ' +
 'arrayswap arraytolist asc asin atn binarydecode binaryencode ' +
 'bitand bitmaskclear bitmaskread bitmaskset bitnot bitor bitshln ' +
 'bitshrn bitxor ceiling charsetdecode charsetencode chr cjustify ' +
 'compare comparenocase cos createdate createdatetime createobject ' +
 'createobject createobject createobject createobject createodbcdate ' +
 'createodbcdatetime createodbctime createtime createtimespan ' +
 'createuuid dateadd datecompare dateconvert datediff dateformat ' +
 'datepart day dayofweek dayofweekasstring dayofyear daysinmonth ' +
 'daysinyear de decimalformat decrementvalue decrypt decryptbinary ' +
 'deleteclientvariable directoryexists dollarformat duplicate encrypt ' +
 'encryptbinary evaluate exp expandpath fileexists find findnocase ' +
 'findoneof firstdayofmonth fix formatbasen generatesecretkey ' +
 'getauthuser getbasetagdata getbasetaglist getbasetemplatepath ' +
 'getclientvariableslist getcontextroot getcurrenttemplatepath ' +
 'getdirectoryfrompath getencoding getexception getfilefrompath ' +
 'getfunctionlist getgatewayhelper gethttprequestdata gethttptimestring ' +
 'getk2serverdoccount getk2serverdoccountlimit getlocale ' +
 'getlocaledisplayname getlocalhostip getmetadata getmetricdata ' +
 'getpagecontext getprofilesections getprofilestring getsoaprequest ' +
 'getsoaprequestheader getsoapresponse getsoapresponseheader ' +
 'gettempdirectory gettempfile gettemplatepath gettickcount ' +
 'gettimezoneinfo gettoken hash hour htmlcodeformat htmleditformat ' +
 'iif incrementvalue inputbasen insert int isarray isbinary isboolean ' +
 'iscustomfunction isdate isdebugmode isdefined isk2serverabroker ' +
 'isk2serverdoccountexceeded isk2serveronline isleapyear islocalhost ' +
 'isnumeric isnumericdate isobject isquery issimplevalue issoaprequest ' +
 'isstruct isuserinrole isvalid isvalid isvalid iswddx isxml ' +
 'isxmlattribute isxmldoc isxmlelem isxmlnode isxmlroot javacast ' +
 'jsstringformat lcase left len listappend listchangedelims listcontains ' +
 'listcontainsnocase listdeleteat listfind listfindnocase listfirst ' +
 'listgetat listinsertat listlast listlen listprepend listqualify ' +
 'listrest listsetat listsort listtoarray listvaluecount ' +
 'listvaluecountnocase ljustify log log10 lscurrencyformat lsdateformat ' +
 'lseurocurrencyformat lsiscurrency lsisdate lsisnumeric lsnumberformat ' +
 'lsparsecurrency lsparsedatetime lsparseeurocurrency lsparsenumber ' +
 'lstimeformat ltrim max mid min minute month monthasstring now ' +
 'numberformat paragraphformat parameterexists parsedatetime pi ' +
 'preservesinglequotes quarter queryaddcolumn queryaddrow querynew ' +
 'querysetcell quotedvaluelist rand randomize randrange refind ' +
 'refindnocase releasecomobject removechars repeatstring replace ' +
 'replacelist replacenocase rereplace rereplacenocase reverse right ' +
 'rjustify round rtrim second sendgatewaymessage setencoding ' +
 'setlocale setprofilestring setvariable sgn sin spanexcluding ' +
 'spanincluding sqr stripcr structappend structclear structcopy ' +
 'structcount structdelete structfind structfindkey structfindvalue ' +
 'structget structinsert structisempty structkeyarray structkeyexists ' +
 'structkeylist structnew structsort structupdate tan timeformat ' +
 'tobase64 tobinary toscript tostring trim ucase urldecode urlencodedformat ' +
 'urlsessionformat val valuelist week wrap writeoutput xmlchildpos ' +
 'xmlelemnew xmlformat xmlgetnodetype xmlnew xmlparse xmlsearch xmltransform ' +
 'xmlvalidate year yesnoformat';

 // Array to hold the possible string matches
 this.stringMatches = new Array();
 this.attributeMatches = new Array();
} = new; = ['coldfusion', 'cf']; = function()
 function push(array, value)
  array[array.length] = value;

 function find(array, element)
  for(var i = 0; i < array.length; i++){
   if(array[i] == element){
    return i;

  return -1;

 var match = null;
 var regex = null;

 // Match numbers
 // (\\d+)
 this.GetMatches(new RegExp('\\b(\\d+)', 'gm'), 'numbers');

 // Match mg keywords
 this.GetMatches(new RegExp(this.GetKeywords(this.mgKeywords), 'igm'), 'mgkeywords');

 // Match single line comments via the built in single line regex (for cfscript)
 this.GetMatches(, 'cfscriptcomments');

 // Match multi line comments via the built in multi line regex (for cfscript)
 this.GetMatches(, 'cfscriptcomments');

 // Match tag based comments (including multiline comments)
 // (\<|<)!---[\\s\\S]*?---(\>|>)
 this.GetMatches(new RegExp('(\<|<)!---[\\s\\S]*?---(\>|>)', 'gm'), 'cfcomments');

 // Match attributes and their values excluding cfset tags
 // (cfset\\s*)?([:\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\')*
 regex = new RegExp('(cfset\\s*)?([:\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\')*', 'gm');
 while((match = regex.exec(this.code)) != null)
  // If there is match in element 1 (the tag is cfset), continute to the next match
  if (match[1] != undefined && match[1] != '')

  // Add the atribute to the matches only if it has a matching value (dbtype="query")
  // and the match is not an empty string
  if (match[3] != undefined && match[3] != '' && match[3] != '""' && match[3] != "''")
   push(this.matches, new[2], match.index, 'attribute'));
   push(this.matches, new[3], match.index + match[0].indexOf(match[3]), 'attribute-value'));
   // Add the attribute value to the array of string matches
   push(this.stringMatches, match[3]);

   // Add the attribute to the array of attribute matches
   push(this.attributeMatches, match[2]);

 // Match opening and closing tag brackets
 // (\<|<)/*\?*(?!\!)|/*\?*(\>|>)
 this.GetMatches(new RegExp('(\<|<)/*\\?*(?!\\!)|/*\\?*(\>|>)', 'gm'), 'tag');

 // Match tag names
 // (\<|<)/*\?*\s*(\w+)
 regex = new RegExp('(?:\<|<)/*\\?*\\s*([:\\w-\.]+)', 'gm');
 while((match = regex.exec(this.code)) != null)
  push(this.matches, new[1], match.index + match[0].indexOf(match[1]), 'tag-name'));

 // Match keywords
 regex = new RegExp(this.GetKeywords(this.keywords), 'igm');
 while((match = regex.exec(this.code)) != null)
  // if a match exists (there is a value for the attribute)
  if (find(this.attributeMatches, match[0]) == -1)
   push(this.matches, new[0], match.index, 'keywords'));

 // Match cfset tags and quoated attributes
 regex = new RegExp('cfset\\s*.*(".*?"|\'.*?\')', 'gm');
 while((match = regex.exec(this.code)) != null)
  // if a match exists (there is a value for the attribute)
  if(match[1] != undefined && match[1] != '')
   push(this.matches, new[1], match.index + match[0].indexOf(match[1]), 'strings'));

   // Add the attribute to the array of string matches
   push(this.stringMatches, match[1]);

 // Match string enclosed in double quoats
 while((match = != null)
  //if (this.stringMatches.indexOf(match[0]) == -1)
  if (find(this.stringMatches, match[0]) == -1)
   push(this.matches, new[0], match.index, 'strings'));

 // Match string enclosed in single quoats
 while((match = != null)
  //if (this.stringMatches.indexOf(match[0]) == -1)
  if (find(this.stringMatches, match[0]) == -1)
   push(this.matches, new[0], match.index, 'strings'));
Questions/comments? You can leave a comment here or use my contact form to send me your thoughts.

Full List of ColdFusion Functions (according to DW)

Yesterday I needed a full list of ColdFusion functions for my syntax highlighting. What easier way to get that list than using your IDE?! I found the functions under the dreamweaver directory, in the file "configuration\CodeHints\CodeHints.hml". After some regex matching with the following pattern:
<function name="code" html? pattern="(\w+).*>
and replacing with the following pattern:
Here is the list of ColdFusion functions that I got:
Abs ACos AddSOAPRequestHeader AddSOAPResponseHeader ArrayAppend ArrayAvg ArrayClear
ArrayDeleteAt ArrayInsertAt ArrayIsEmpty ArrayLen ArrayMax ArrayMin ArrayNew
ArrayPrepend ArrayResize ArraySet ArraySort ArraySum ArraySwap ArrayToList
Asc ASin Atn BinaryDecode BinaryEncode BitAnd BitMaskClear BitMaskRead BitMaskSet
BitNot BitOr BitSHLN BitSHRN BitXor Ceiling CharsetDecode CharsetEncode Chr
Cjustify Compare CompareNoCase Cos CreateDate CreateDateTime CreateObject
CreateObject CreateObject CreateObject CreateObject CreateODBCDate
CreateODBCDateTime CreateODBCTime CreateTime CreateTimeSpan CreateUUID
DateAdd DateCompare DateConvert DateDiff DateFormat DatePart Day DayOfWeek
DayOfWeekAsString DayOfYear DaysInMonth DaysInYear DE DecimalFormat DecrementValue
Decrypt DecryptBinary DeleteClientVariable DirectoryExists DollarFormat Duplicate
Encrypt EncryptBinary Evaluate Exp ExpandPath FileExists Find FindNoCase FindOneOf
FirstDayOfMonth Fix FormatBaseN GenerateSecretKey GetAuthUser GetBaseTagData
GetBaseTagList GetBaseTemplatePath GetClientVariablesList GetContextRoot
GetCurrentTemplatePath GetDirectoryFromPath GetEncoding GetException GetFileFromPath
GetFunctionList GetGatewayHelper GetHTTPRequestData GetHTTPTimeString
GetK2ServerDocCount GetK2ServerDocCountLimit GetLocale GetLocaleDisplayName
GetLocalHostIP GetMetaData GetMetricData GetPageContext GetProfileSections
GetProfileString GetSOAPRequest GetSOAPRequestHeader GetSOAPResponse
GetSOAPResponseHeader GetTempDirectory GetTempFile GetTemplatePath GetTickCount
GetTimeZoneInfo GetToken Hash Hour HTMLCodeFormat HTMLEditFormat IIf IncrementValue
InputBaseN Insert Int IsArray IsBinary IsBoolean IsCustomFunction IsDate IsDebugMode
IsDefined IsK2ServerABroker IsK2ServerDocCountExceeded IsK2ServerOnline IsLeapYear
IsLocalHost IsNumeric IsNumericDate IsObject IsQuery IsSimpleValue IsSOAPRequest
IsStruct IsUserInRole IsValid IsValid IsValid IsWDDX IsXML IsXMLAttribute IsXMLDoc
IsXMLElem IsXMLNode IsXMLRoot JavaCast JSStringFormat LCase Left Len ListAppend
ListChangeDelims ListContains ListContainsNoCase ListDeleteAt ListFind ListFindNoCase
ListFirst ListGetAt ListInsertAt ListLast ListLen ListPrepend ListQualify ListRest
ListSetAt ListSort ListToArray ListValueCount ListValueCountNoCase LJustify Log
Log10 LSCurrencyFormat LSDateFormat LSEuroCurrencyFormat LSIsCurrency LSIsDate
LSIsNumeric LSNumberFormat LSParseCurrency LSParseDateTime LSParseEuroCurrency
LSParseNumber LSTimeFormat LTrim Max Mid Min Minute Month MonthAsString Now
NumberFormat ParagraphFormat ParameterExists ParseDateTime Pi PreserveSingleQuotes
Quarter QueryAddColumn QueryAddRow QueryNew QuerySetCell QuotedValueList Rand
Randomize RandRange REFind REFindNoCase ReleaseCOMObject RemoveChars RepeatString
Replace ReplaceList ReplaceNoCase REReplace REReplaceNoCase Reverse Right RJustify
Round RTrim Second SendGatewayMessage SetEncoding SetLocale SetProfileString
SetVariable Sgn Sin SpanExcluding SpanIncluding Sqr StripCR StructAppend StructClear
StructCopy StructCount StructDelete StructFind StructFindKey StructFindValue StructGet
StructInsert StructIsEmpty StructKeyArray StructKeyExists StructKeyList StructNew
StructSort StructUpdate Tan TimeFormat ToBase64 ToBinary ToScript ToString Trim UCase
URLDecode URLEncodedFormat URLSessionFormat Val ValueList Week Wrap WriteOutput
XMLChildPos XMLElemNew XMLFormat XMLGetNodeType XMLNew XMLParse XMLSearch XMLTransform
XMLValidate Year YesNoFormat

Wednesday, October 03, 2007

Readable ModelGlue Debugging Output while using CSS Layout

If you have your application layout done with CSS, most likely ModelGlue debugging output looks something like the picture below. ModelGlue Debugging with CSS Layouts Overlaying the debug output with your application styles can make it a bit unreadable. So here is a tip. Get and install the Web Developer extension if you don't have it already. Once installed (and you restarted Firefox), go to the "CSS" menu and under "Disable Styles", click on "All Styles" (or use the shortcut key "Ctrl+Shift+S"). Here is a screen shot of doing so: disable_css

If you use Internet Explorer instead of Firefox, you can use the same thing with the Internet Explored Developer Toolbar by going to the "Disable" menu and click on "All CSS". Here is a screen shot of doing so: ie

Now your ModelGlue debugging output should look much cleaner and readable: Model-Glue Debbuging without CSS

Free Ruby on Rails E-Book is offering a free Ruby on Rails book called "Build Your Own Ruby on Rails Web Applications" by Patrick Lenz.

You can download the book in PDF format from the address

Tuesday, October 02, 2007

Prototype.js Object Initialization and Bind While Enumerating Arrays

If the title sounds confusing, that is because it is. I couldn't figure out a better way to describe the issue I was running into. Let see if I can better demonstrate. I have a "initialize" function defined in my JavaScript object as such:

initialize: function() {
// siteID contains a list of ids such as "1,2,3,4"
// Convert the id list in siteIDs to a prototype array $A(siteIDs) and
// for each element in the array, run a function passing it the single siteID
$A(siteIDs).each(function(siteID) {
// If an element exists on the page with the id of setCurrentSiteElementPrefix + current siteID
if ($(this.setCurrentSiteElementPrefix + siteID)) {
 // Add an observer to the 'click' event of that element and bind it
 // to the 'getContent' function
 $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this));
where "siteIDs" is a comma delimited list of ids ("1,2,3,4") and "setCurrentSiteElementPrefix" is a part of the name of link element with a dynamically generated id (such as setCurrentSiteElementPrefix_1, setCurrentSiteElementPrefix_2). Here is the above loop in pseudo code (just for your kids Doug): Convert the list of siteIDs to a prototype array and for each found array element, run a function passing the individual site id. $A(siteIDs) converts the list of site ids to an array, while .each() executes the inline function for each array element. // $A(siteIDs).each(function(siteID) If an element exists on the page with the id starting with setCurrentSiteElementPrefix (definition not shown here) + current siteID (passed by the inline function above) // if ($(this.setCurrentSiteElementPrefix + siteID)) Add an observer to the "click" event of that element and bind it to the "getContent" function. The "getContent" function has to bound to the "this" scope since the "getContent" function will not otherwise know what "this" means when trying to refer to elements defined in the object. // $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this)); Never mind why I am doing this, I will follow up on that with another post. So I was trying to tie an event observer to the 'click' event of each link element by looping through the list of dynamic ids ($A(siteIDs).each(function(siteID)). However, that was giving me some issues with the message "this.getContent is not defined". Why the heck not, the code is inside the "initialize" method of my object?! It turns out while looping over an array as above the "this" scope is not preserved. So the function inside the loop has no idea what "this" means. The simple fix is to surround the function with parenthesis and append ".bind(this)":
initialize: function() {
 // Note the extra parenthesis before 'function(siteID)'
 $A(siteIDs).each((function(siteID) {
  if ($(this.setCurrentSiteElementPrefix + siteID)) {
   $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this));
 // Note the ').bind(this)' before the semicolon
If you like to learn more on bind, check out Understanding bind and bindAsEventListener in Javascript and Understanding bind and bindAsEventListener in Javascript - Part II

Modifying Canvas ColdFusion Wiki

Canvas is an excellent wiki written by Ray Camden. It has a vast number of features for creating/maintaining documentation of any sort. I have been playing with it for a bit but I still haven't taken advantage of all its features. However, it lacks some basic features I thought it needed. I have been working on a side project and as a side effect I have added a few essentials (in my opinion). In subsequent posts, I will attempt to explain how to modify Canvas and add the following features:

  1. Database user authentication
  2. Persist user sessions between visits - "Remember Me" functionality
  3. LDAP user authentication
  4. Integration with Google Analytics
  5. Contact form
  6. Better exception handling / notifications

Loop Over ColdFusion Structure

Just a quick reference on how to loop over a structure without knowing its keys.
<cfset testStruct = structnew() />
<cfset testStruct.key1 = "test1" />
<cfset testStruct.key2 = "test2" />
<cfset testStruct.key3 = "test3" />

<cfdump var="#testStruct#" />

<cfloop list="#structKeyList(testStruct)#" index="key">
Key: #key#, Value: #testStruct[key]#

Wednesday, September 26, 2007

SVN4DW - Hard to Find SVN Extension for Dreamweaver

I've been using this extension for a while and it's pretty handy for updating/committing code from/to your SVN repository. I tried to find it on the net tonight so I can install it at home but it seems to have vanished. So I looked through my work PC and there it was. Awesome! For those folks that like to use it, I've put it up on my personal site. The link is

Querying Active Directory Through SQL Server

Integrating your application with Active Directory (or another LDAP based directory) is a common requirement in many business applications. Almost every language has a way to query LDAP but little known is the approach of integrating SQL Server with your LDAP controller. This quick article will guide you through setting up and using LDAP queries through SQL Server 2000 with Active Directory as the LDAP controller.

First, you need to create a SQL Server linked server. Set it up through SQL Server Enterprise Manager:
  1. Open SQL Server Enterprise Manager
  2. Go to the database server to which you will be adding the linked server
  3. Expend "Security"
  4. Right-click on "Linked Servers" and click on "New Linked Server..."
  5. Fill in the following:
    1. Under the General tab:
      1. Linked Server: adsi (or whatever you want to call it)
      2. Server type: select Other Data Source
      3. Provider name:select OLE DB Provider for Microsoft Directory Services
      4. Under Provider Options: check that Allow InProcess is checked
      5. Leave the rest of the fields blank
    2. Under the Security tab:
      1. Local Login: sqlServerUser
      2. Remote User: (such as or\bkostadinov)
      3. Remote Password: userPassword for the above domain account
Through Query Analyzer:
  1. Change the "AllowInProcess" registry key under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Providers\ADSDSOObject to "dword:00000001"
    1. Edit the registry manually or put the following in a .reg file and execute it:
      Windows Registry Editor Version 5.00
  2. Open Query Analyzer (or your choice of query tool)
  3. Connect to the server to which you will be adding the linked server
  4. Change the provided values and execute the following code:
-- Change 'adsi' to the desired name of the linked server
exec sp_addlinkedserver

-- Change 'adsi' to the desired name of the linked server
-- Change 'sqlUser' to the username of local sql server user
-- Change 'domainName\userName' to a domain account
-- (the format can be 'domainName\userName' or 'userName@domainName')
-- Change 'domainUserPassword' to the password of the domain account
exec sp_addlinkedsrvlogin

Or you can just fill in the values on top of the following script and run that:

declare @linkedServerSql nvarchar(4000),
  @linkedServerName varchar(100),
  @localSqlUsername varchar(100),
  @domainUsername varchar(100),
  @domainUserPassword varchar(100)

-- Set the local sql server user
set @localSqlUsername = 'sqlUser'
-- format can be 'domainName\userName' or 'userName@domainName'
set @domainUsername = 'domainName\userName'
set @domainUserPassword = 'domainUserPassword'

set @linkedServerName = 'adsi'

set @linkedServerSql = '
  exec sp_addlinkedserver
  ''' + @linkedServerName + ''',

exec sp_addlinkedsrvlogin
  ''' + @linkedServerName + ''',
  ''' + @localSqlUsername + ''',
  ''' + @domainUsername + ''',
  ''' + @domainUserPassword + ''''

exec sp_executesql @linkedServerSql
Run a query to verify that the linked server works. The query below will give you all the users in the dc=ica,dc=com (change that to match your own domain):
select  *
from  openquery(adsi, '
select  givenName,
from    ''LDAP://dc=ica,dc=com''
where   objectCategory = ''Person''
        objectClass = ''user''
Below, is an alternate syntax which you can use to apply ldap filters with almost universal syntax. The following will get all the users in LDAP but limit the result set to those users who's "given" & "sn" names are not empty. It will also apply a filter to the "division" attribute and exclude any records that match "system" and "generic".
declare @ldapFilter nvarchar(1000), @ldapSQL nvarchar(4000)

-- Set the filter to exlude objects that have a division of "System" and "Generic"
set @ldapFilter = '(!division=System*)(!division=Generic)'

-- Create an ldap query to get all users under dc=ica,dc=com
set @ldapSQL = '
select  givenName as firstName,
        sn as lastName,
        lower(sAMAccountName) as accountName,
        telephoneNumber as phoneNumber,
        mobile as cellPhoneNumber,
        mail as emailAddress,
        physicalDeliveryOfficeName as siteName
from    openquery(adsi, ''<LDAP://dc=ica,dc=com>
    (&(objectCategory=Person)(objectClass=user)'+ @ldapFilter + ');
where   givenName is not null
    sn is not null'

exec sp_executesql @ldapSQL

Some things to note:

"LDAP" is case sensitive, if you try using "ldap", the query will throw an error.

Querying the Active Directory server will work fine from Query Analyzer even if you do not run sp_addlinkedserverlogin. However, if you try to execute the query from the web (with something like ColdFusion, you will get an error similar to:

[Macromedia][SQLServer JDBC Driver][SQLServer]OLE DB provider 'ADSDSOObject' reported an error. The provider indicates that the user did not have the permission to perform the operation.

Tuesday, September 25, 2007

Web Application Deadly Sin

I have ran across this situation more than a couple of times recently. First it was my hosting provider, than the new web application I tried today (called Fuser). When signing up, they let you pick your password and I use a totally random password each time somewhere around 16-20 characters. So it happens that the applications above, cut my password to their allowed length but do not let me know they are doing so. Nowhere does it say that the password should be 10 or 15 characters. So end up with a newly created account that I cannot log into only to find out (after I go through the "forgot password" procedure) that my password was chopped. That is annoying and enough to turn me off using the application. Common sense, where are you?

Reactor Oops - Case Sensitive Dictionary XML

Here is the message I got today while trying to do some work with reactor and model-glue.

Message An error occurred while Transforming an XML document.

Detail Empty expression!

Extended Info

Tag Context D:\_Webroot\reactor\core\objectTranslator.cfc (149) D:\_Webroot\reactor\core\objectTranslator.cfc (164) D:\_Webroot\reactor\core\objectTranslator.cfc (164) D:\_Webroot\reactor\core\objectTranslator.cfc (101) D:\_Webroot\reactor\core\objectFactory.cfc (223) D:\_Webroot\reactor\reactorFactory.cfc (80) D:\_Webroot\reactor\base\abstractRecord.cfc (393) D:\_Webroot\reactor\project\myproject\Validator\usersValidator.cfc (10)

It turned out that the dictionary file that reactor generated is case sensitive. I need to change "<Users>" to "<users>". The table is defined as "users" but I had to change it to "<Users>" on my home machine. It seems that reactor can't make up it's mind on how it wont's the table to be called. Why is it "Users" at home and "users" at work? I'm using Apache on Windows with the same version of MySQL and reactor on both machines.

Update: So I just learned that:

<cfset usersRecord = variables.reactor.createRecord("users") />
is not the same as:
<cfset usersRecord = variables.reactor.createRecord("Users") />
The dictionary file was created with "Users" as the table name so reactor was looking for the same case. I think this is only an issue with MySQL.

Updating Web Applications in Production

Disclaimer: This is just an idea so far, but it is worth documenting so I remember to do it. It also assumes that you are using some kind of source control system such as Subversion.

Wouldn't it be cool to know when your production web application is out of date? By that I mean there is a newer version of it available in your source code repository. So instead of the usual steps:

  1. Check-in latest changes to the application
  2. Go to the production sever and update the code from the repository
  3. Reinitialize the application (I use Model-Glue)

You can simply:

  1. Check-in latest changes to the application
  2. Go to the web application in production
  3. Click a button to updated to the latest revision

Same number of steps I guess but doesn't the second solution seem so much nicer. I will have to implement this across all the web applications I maintain/develop.

Thursday, September 20, 2007

CSSVista - Edit your CSS code live on Internet Explorer and Firefox

I came across this pretty nice tool called CSSVista. It lets you edit css on the fly. Sure, the web developer toolbar and Firebug already does this for Firefox but it's still cool.

JSValidation Kicks Butt - Validation Library for Prototype.js

to get it to work, simply include the library like so:
<script language="javascript" type="text/javascript" src="js/jsvalidate/jsvalidate.js"></script>
Add some css styles:
.jsvalidation { color: #ff0000; font-size: 14px; }
add "jsrequired" (for a required field) and the jsvalidate type (such as "jsvalidate_number") to the "class" attribute of your text element.
<input type="text" name="year" id="year" size="4" class="jsrequired jsvalidate_number" />
and finally set the message to be displayed when the validation fails in the "alt" attribute of the element:
<input type="text" name="year" id="year" size="4" class="jsrequired jsvalidate_number" alt="Please enter a Year" />
More on the jsvalidation types at the docs

Monday, September 17, 2007

Quake Style Command Prompt - Cool Stuff

This is based on the guide provided on Instructables. I'm posting this here because I finally got around trying this out. I have to say it's pretty nice. The idea is simple. You press the Windows key + '~' (tilda) and get a semi-transparent console window in the top left corner of your screen. You press the same combination key again and the window disappears. Very handy for those of us that use the command prompt often. Here is the end result. Click on the thumbnail to see a larger picture: To get started: 1. Get AutoHotkey 2. Download the last stable version of Console (as of 09.17.2007 that is 1.5 so get "" 3. Get the customized "console.xml" file from the bottom of the Instructable or from here directly 4. Get the AutoHotkey script from the bottom of the Instructable or from here directly 5. Install AutoHotkey 6. Extract to "c:\program files\console" or wherever you desire 7. Create a shortcut to "console.exe" in your "c:\windows" directory 8. Copy the customized "console.xml" to "c:\program files\console" (or wherever you installed Console) and overwrite the file that's there 9. Start the AutoHotkey script you got in step 4. You can also put a shortcut to the script in the "Startup" folder on your Start Menu.

Using Regular Expression in SQL Server

Who doesn’t love regular expressions?! If you don’t know what they are, you should take some time to learn. Regular expressions or from here on RegEx are a very powerful way to search/replace string within strings. That being said, almost any language supports RegEx and for a while I’ve been using them with SQL Server thanks to xp_pcre - Regular Expressions in T-SQL Here is an example use of two of the functions that come with the above extended sql server library:
declare @out varchar(8000)
declare @str varchar(500)

-- Create a string containing an ldap common name

set @str = lower(’LDAP://CN=APO Conference Room,OU=Locations,OU=APO,OU=Sites,DC=ica,DC=com’)

-- Strip everything from the string and replace with the ’ou’ that comes after ’ou=locations’

exec master.dbo.xp_pcre_replace @str, ’.*ou=locations,ou=(’w+).*’, ’$1’, @out out

-- Print the results
print @out

-- Create a string containing an ldap common name
set @str = lower(’LDAP://OU=SMD,OU=Sites,DC=ica,DC=com’)

-- Return 1 if there is a match of ’ou=’ followed by ’ou=sites’
select master.dbo.fn_pcre_match(@str, ’^ldap://ou=’w+,ou=sites’)
More information here:

Thursday, September 13, 2007

Draw Flow Charts Online

Download Squad just had a post about this nifty online tool. Anybody that draws diagrams (that means you Doug), will benefit from this. Draw Anywhere - easy online diagramming, flow chart

Disabling Forms with Prototype - Gotcha

I love using Prototype for all my JavaScript needs. Ajax calls are real easy with or without parameters. To get some form parameters to pass with your request, you would simply do:
var params = $('myForm').serialize();
and then you would disable the form like so:
However, here is a little gotcha I always seem to forget:
  • DO NOT disable the form before you read your form values
If you do, code such as:
will no longer work. By not working, I mean will just return empty and you will be beating your head against the desk.

Chad Vader - Funny Stuff

Whoever hasn't seen the Chad Vader creation of Matt Sloan and Aeron Yonda should. It's pretty amusing. You can find it at
Chad Vader: Day Shift Manager - Blame Society Productions - Matt Sloan and Aaron Yonda. The tag line is "Life is hard for Chad Vader, the younger, less charismatic brother of Darth Vader, who is the day shift manager of a grocery store."

Tuesday, September 11, 2007

Cats love to sleep, eat and sneak out

This is one passed out cat. She had to eat first however. This is the same cat that managed to get out and spend the night outdoors when we took our overnight trip to Old Forge. I am glad she is ok. To our surprise, we came home to a dead mole by the back door and it was a mystery until we found the cat outside of the house.DSCF4856

Monday, September 10, 2007

Common Model-Glue "Newbie" Mistake

In short: forgetting to "wire" the message broadcasted by your event to the function in your controller. I still do this on occasion which prompted me to post it. The longer version: You have a an event defined as follows:
<event-handler name="GetUsersForm">
  <message name="getRolesList" />
  <message name="getSytelineUsersBySiteID" />
  <message name="getUsersBySiteID" />

  <view name="body" template="frmUsers.cfm" />
You have "getRolesList", "getSytelineUsersBySiteID" and "getUsersBySiteID" defined in your controller. However, when you call the event, you don't get any of the data you expect to see there. The gotcha is "wiring" the broadcasted message to the function in the controller as follows:
<controller name="ApplicationController" type="LabelGeneration.controller.ApplicationController">
 <message-listener message="OnRequestStart" function="OnRequestStart" />
 <message-listener message="OnQueueComplete" function="OnQueueComplete" />
 <message-listener message="OnRequestEnd" function="OnRequestEnd" />

 <message-listener message="getSitesList" function="getSitesList" />
 <message-listener message="getRolesList" function="getRolesList" />

 <message-listener message="getSytelineUsersBySiteID" function="getSytelineUsersBySiteID" />
 <message-listener message="getUsersBySiteID" function="getUsersBySiteID" />
 <message-listener message="addUser" function="addUser" />
 <message-listener message="deleteUser" function="deleteUser" />
// //]]>