Archive for April, 2010

Connecting to the MailChimp API with Coldfusion

Sunday, April 25th, 2010

I have just started playing with the MailChimp API with Coldfusion. As usual, there’s not a lot of Coldfusion documentation or examples. So I thought I’d start posting some of the how-to’s as I myself figure them out. Here will demonstrate how to make a simple connection with the API so you can start doing more productive things like adding emails to lists and getting stats.

Let’s begin at the beginning of using any API. We need an account and an API key. Go ahead and create an account as if you’re a client, or if you are doing this for an actual client go ahead and create an account for them. MailChimp makes this super simple. Once you have an account click on the ‘account’ button in the top left corner of the dashboard. This will take you to a set of links. Click the ‘API Keys & Info’ link. This will take you to a page where you can manage your API keys. Clicking ‘Add A Key’ will generate a new key for you to use.

Now that we have our key and account we’re ready to make our connection via Coldfusion. Before looking at any code, let’s make sure we know where our documentation is at. You can find the MailChimp docs here. Requesting data from MailChimp requires you to pass the method and parameters via the url: http://us1.api.mailchimp.com/1.2/. As parameters you need to indicate the type of output MailChimp should return data as. In our case we’ll use JSON. So we’ll attach ‘?output=json’. Next we need to specify the method. MailChimp has a ping() method meant for no other reason than to make sure the connection is working. So this is what we’ll use here. To do this we attach the method parameter ‘&method=ping’. If you look at the docs you’ll see that this requires only one parameter to work, which is the API key (this is required for all methods). So we’ll attach another parameter ‘&apikey=[yourapikey]‘.

So let’s look at how to write this using Coldfusion.

<!--- SET URL --->
<cfset VARIABLES.api_url = "http://us1.api.mailchimp.com/1.2/?output=json&method=ping&apikey=[your key]-us1">

<!--- SEND DATA VIA CFHTTP TAG --->
<cfhttp name="call" url="#VARIABLES.api_url#" result="VARIABLES.mycall">

<!--- BECAUSE RETURNED AS JSON WE NEED TO DESERIALIZE THE RESPONSE --->
<cfset VARIABLES.deserialized = deserializeJSON(VARIABLES.mycall.fileContent)>

<!--- DUMP RESPONSE --->
<cfdump var="#VARIABLES.deserialized#">

First we set our URL. In this case you’ll, of course, change the ‘[your key]‘ to whatever your actual key is that MailChimp generated for you in your account. Next we use the Coldfusion tag to send the url to MailChimp. MailChimp then processes it and sends back the result which we store as ‘VARIABLES.mycall’. Because we specified to MailChimp to return data back to us as JSON we need to make sure we deserialize it before displaying it. We do this using the ‘deserializeJSON()’ method. Also the response is passed back as a struct with the actual data from MailChimp stored in the ‘fileContent’ key. Lastly we just dump the data so we can see that ‘Everything’s Chimpy!’.

At this point you should be able to start playing more with the API to do more complex and practical things.

Using Coldfusion to specify which pages to run under an SSL

Sunday, April 25th, 2010

I recently had a site that ran under an SSL certificate. As this was the case each page of the site ran under the ‘https://’ protocol. A few weeks ago the client noticed that when run in Internet Explorer they were receiving a message stating that not all of the data was being passed via the SSL. It would ask if the user wished to load in the insecure data as well as the secure. If the user chose yes everything loaded as normal. If they chose no everything except AJAX data would load onto the page.

While this makes sense I’m not sure why it just started happening. Nonetheless, I noted that it was not necessary to run the entire site under the SSL. Since I, of course, do not use AJAX anywhere in the checkout process, I decided that the simple solution was to run the SSL only under the checkout. This would allow the rest of the site to run under the ‘http://’ protocol without any security prompts concerning AJAX from Internet Explorer. So let’s take a look at how to run the SSL under only select pages in your site.

<!--- ***************  MAKE SURE THE SSL IS IN PLACE  *************** --->
<cfset var storepath = "http://www.yourdomain.com/checkout/">
<cfset var url = "http://" & CGI.HTTP_HOST & CGI.SCRIPT_NAME>
<cfset var isinstore = findNoCase(storepath, url)>

<!--- IF FOUND CHANGE TO SECURE PROTOCOL --->
<cfif isinstore neq 0 AND CGI.SERVER_PORT neq "443">
	<!--- SET TO SECURE PROTOCOL --->
	<cfset url = "https://" & CGI.HTTP_HOST & CGI.SCRIPT_NAME>

	<!--- SET QUERY STRING IF NECESSARY --->
	<cfif CGI.QUERY_STRING neq "">
		<cfset url = url & "?" & CGI.QUERY_STRING>
	</cfif>

	<!--- REDIRECT USER --->
	<cflocation url="#url#" addtoken="false">
</cfif>

This snippet of code is run under the onRequestStart() method in the Application.cfc. This allows the program to check the url with each request that is made. Some pre-planning is required to choose where the SSL is run without just doing a page by page check. In this case we have made sure that we have placed all of our secure pages under a single directory. In this case we are wanting to secure our ‘checkout’ directory. If we are under this directory we also want to make sure that we are not running on the secure port 443. If not we can take the current url, run the secure protocol at the beginning, attach the query string if necessary, and then redirect the user to the secure page.

With each request Coldfusion checks if the page falls under the ‘checkout’ directory and if so it redirects to itself using the ‘https://’ protocol. For this protocol to work, it of course assumes that you have set up the SSL in IIS or whatever you use. I also recommend going into the site and changing any absolute paths linking to any of the pages running under your secure directory to use the secure protocol by default.

Creating a Twitter style character counter with jQuery

Thursday, April 22nd, 2010

I’m not really sure why I decided to write the javascript to handle a text area with a limited character count as well as a character counter like that used on Twitter. Reason or not, it turned out to be fairly simple to pull off. By far the hardest part was finding a way to only run the onkeyup event when the appropriate text area field was focused. The idea being that every time the user presses a key typing in their name there isn’t a bunch of javascript running unnecessarily in the background. Somewhere on the web I found an activeElement javascript method that returns the focused form element. Just a fair warning that I’ve never used this outside of this example and have no idea if it works in IE or not. I did check though in Chrome, Firefox, and Safari however.

I came up with two scenarios for using a limited character text area with a character counter. First is a text area that allows x number of characters. The counter counts up to this number and then increases no more. The string itself is trimmed to the max character on each keypress beyond the max characters allowed. Second is a text area much truer to the Twitter ‘What’s Happening’ text area. This text area will allow the user to type as much as they wish, while the counter begins at the max and counts down to 0. At zero the counter begins counting down through negative numbers. I’ve also added a class at this point that changes the color of negative numbers to red.

Let’s take a look at some code.

The HTML

<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>Character Counter</title>

		<!-- JQUERY LINK -->
		<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"></script>

		<!-- CHARACTER COUNTER JS FILE -->
		<script src="js/character_counter.js" type="text/javascript"></script>
	</head>

	<body>
		<form name="text_input" method="post" action="#">
			<textarea name="my_text" id="my_text" cols="50" rows="10"></textarea>
		</form>

		Characters: <em><span id="counter"></span></em>
	</body>
</html>

Pretty simple stuff we have here. First is a link to the jQuery library hosted by Google. Next is a link to the character_counter.js file that I will get into next. As far as HTML is concerned we have a text area with an id of ‘my_text’ and a span with an id of ‘counter’. The span is empty because it will serve as a container for us to dynamically insert the character count as the user types into the text area.

I will list first the javascript that counts up to the max number of characters and then trims the string to the max character count so that the user can literally on enter the allowed number of characters. Afterwards, I will show the code that will allow the user to type as much as they wish and count into negative numbers.

character_counter.js (Counts up with character limit)

$(document).ready(function()
{
	var max_length = 10;

	//run listen key press
	whenkeydown(max_length);
});

whenkeydown = function(max_length)
{
	$("#my_text").unbind().keyup(function()
	{
		//check if the appropriate text area is being typed into
		if(document.activeElement.id === "my_text")
		{
			//get the data in the field
			var text = $(this).val();

			//set number of characters
			var numofchars = text.length;

			if(numofchars <= max_length)
			{
				//set the length of the text into the counter span
				$("#counter").html("").html(text.length);
			}
			else
			{
				//make sure string gets trimmed to max character length
				$(this).val(text.substring(0, max_length));
			}
		}
	});
}

character_counter.js (Counts down without a character limit)

$(document).ready(function()
{
	//set max length
	var max_length = 10;

	//load in max characters when page loads
	$("#counter").html(max_length);

	//run listen key press
	whenkeydown(max_length);
});

whenkeydown = function(max_length)
{
	$("#my_text").unbind().keyup(function()
	{
		//check if the appropriate text area is being typed into
		if(document.activeElement.id === "my_text")
		{
			//get the data in the field
			var text = $(this).val();

			//set number of characters
			var numofchars = text.length;

			//set the chars left
			var chars_left = max_length - numofchars;

			//check if we are still within our maximum number of characters or not
			if(numofchars <= max_length)
			{
				//set the length of the text into the counter span
				$("#counter").html("").html(chars_left).css("color", "#000000");
			}
			else
			{
				//style numbers in red
				$("#counter").html("").html(chars_left).css("color", "#FF0000");
			}
		}
	});
}

Gist of both is that we need to set a max character count for the text area and then listen for the keyup event to fire. When this happens we then check if the text area with the id of ‘my_text’ is focused. If not there is no need to continue running code. If so we get the value of the text area and get the length which is the number of characters. If we are increasing our counter by one we just need to use jQuery to write to the span tag the current length of the value of the text area. If counting down we do the opposite. We subtract the length of the value of the text area from the max number of characters allowed. Next, if we are trimming the string so that it can never actually get longer than the number of allowed characters we check if the length has exceeded the max characters. If so we use the ‘subString()’ method to make sure that the value is trimmed off each time the user tries to add a character. If running the counter into negative numbers we don’t really need to run any conditionals unless changing the color so that negative numbers show up different than positive numbers. In this case I just check if the length of the value exceeds the max length. If so I use the jQuery css() method to change the color of the text to red.

That’s really it. Not pretty, but functional and simple.

Looping over lists

Wednesday, April 21st, 2010

I realize that the title implies a super basic topic, and it certainly does. But this is something that tripped me up a little earlier today so I thought that simple as it may be I would share for anyone else encountering a similar problem. My issue was that I created a list, looped over the list, for each loop checked the value, and depending on the value output a new value. What I found was that my switch statements would pick up the first value in the list but not the rest. The problem ended up being that I was putting a space between my comma and the next value. Let’s look at a quick example of this.

<!--- SET A LIST --->
<cfset VARIABLES.list = "RI, MA, CT">

<!--- LOOP OVER LIST --->
<cfloop list="#VARIABLES.list#" delimiters="," index="VARIABLES.i">
	<!--- CHECK ITEM IN LIST AND SHOW FULL STATE NAME --->
	<cfswitch expression="#VARIABLES.i#">
		<cfcase value="RI">
			<cfset VARIABLES.state = "Rhode Island">
		</cfcase>

		<cfcase value="MA">
			<cfset VARIABLES.state = "Massachusetts">
		</cfcase>

		<cfcase value="CT">
			<cfset VARIABLES.state = "Connecticut">
		</cfcase>
	</cfswitch>

	<!--- DISPLAY FULL STATE NAME --->
	<cfoutput>
		#VARIABLES.state#<br />
	</cfoutput>
</cfloop>

This will display “Rhode Island” three times. What causes this? Again it’s that space after the commas in my initial list. As it stands the the case value is looking for ” MA” not “MA”.

If you convert the items in the list into a byte array you will see that both MA and CT have an extra character which is a space. Simple and obvious solution though. Just make sure that your lists are nice and tight. While I have a tendency to use arrays as opposed to lists in these cases I really find it hard to believe that I’m just now noticing this.

VARIABLES scope in CFCs in Coldfusion 9

Sunday, April 11th, 2010

The point is to hopefully add a little clarification to the VARIABLES scope used inside of CFCs in Coldfusion 9.  I’ll also be using a few other things like an init() method to further illustrate the use of the VARIABLES scope.  I’ll be using simple examples of these things for demonstration purposes only.  That being said, we’ll look at the first example which will demonstrate the ‘VARIABLES’ scope in a cfc as well as using an ‘init()’ method to set up a CFC.  Let’s take a look at the component.

<cfcomponent displayname="Manage Person" hint="Manages the persons created." output="false">

	<!--- **************************  INITIALIZE  ************************** --->
	<cffunction name="init" displayname="Initialize" description="Sets the variables that will need to be used throughout this component." output="false" access="public" returntype="any">

		<cfset VARIABLES.name = "Matthew">
		<cfset VARIABLES.haircolor = "Brown">
		<cfset VARIABLES.eyecolor = "Hazel">
		<cfset VARIABLES.height = "5 feet, 6 inches">
		<cfset VARIABLES.weight = "130 lbs">
		<cfset VARIABLES.gender = "Male">

		<cfreturn THIS>

	</cffunction>

	<!--- **************************  ADD PERSON  ************************** --->
	<cffunction name="addPerson" displayname="Add Person" description="Creates a person object." access="public" output="false" returntype="struct">

		<!--- BUILD STRUCT --->
		<cfset var data = structNew()>
		<cfset data.success = true>
		<cfset data.message = "">
		<cfset data.person = structNew()>
		<cfset data.person.name = VARIABLES.name>
		<cfset data.person.haircolor = VARIABLES.haircolor>
		<cfset data.person.eyecolor = VARIABLES.eyecolor>
		<cfset data.person.height = VARIABLES.height>
		<cfset data.person.weight = VARIABLES.weight>
		<cfset data.person.gender = VARIABLES.gender>

		<!--- RETURN STRUCT --->
		<cfreturn data />

	</cffunction>

	<!--- *****************************  SET NAME  ***************************** --->
	<cffunction name="setName" displayname="Set Name" description="Sets a persons name into the component." access="public" output="false" returntype="void">

		<!--- ARGUMENTS --->
		<cfargument name="name" displayname="Person Name" hint="The name of a person." type="string" required="true" />

		<!--- SET NAME INTO THIS SCOPE --->
		<cfset VARIABLES.name = ARGUMENTS.name>

	</cffunction>

	<!--- **************************  SET EYE COLOR  ************************** --->
	<cffunction name="setEyeColor" displayname="Set Eye Color" description="Sets the eye color of a person." access="public" output="false" returntype="void">

		<!--- ARGUMENTS --->
		<cfargument name="eyecolor" displayname="Person Eye Color" hint="The eye color of a person." type="string" required="true" />

		<!--- SET EYE COLOR INTO THIS SCOPE --->
		<cfset VARIABLES.eyecolor = ARGUMENTS.eyecolor>

	</cffunction>

	<!--- **************************  SET HAIR COLOR  ************************** --->
	<cffunction name="setHairColor" displayname="Set Hair Color" description="Sets the hair color of a person." access="public" output="false" returntype="void">

		<!--- ARGUMENTS --->
		<cfargument name="haircolor" displayname="Person Hair Color" hint="The hair color of a person." type="string" required="true" />

		<!--- SET HAIR COLOR INTO THIS SCOPE --->
		<cfset VARIABLES.haircolor = ARGUMENTS.haircolor>

	</cffunction>

	<!--- **************************  SET WEIGHT  ************************** --->
	<cffunction name="setWeight" displayname="Set Weight" description="Sets the weight of a person" access="public" output="false" returntype="void">

		<!--- ARGUMENTS --->
		<cfargument name="weight" displayname="Person Weight" hint="The weight of a person." type="string" required="true" />

		<!--- SET WEIGHT INTO THIS SCOPE --->
		<cfset VARIABLES.weight = ARGUMENTS.weight>

	</cffunction>

	<!--- **************************  SET GENDER  ************************** --->
	<cffunction name="setGender" displayname="Set Gender" description="Sets the gender of a person" access="public" output="false" returntype="void">

		<!--- ARGUMENTS --->
		<cfargument name="gender" displayname="Person Gender" hint="The gender of a person." type="string" required="true" />

		<!--- SET GENDER INTO THIS SCOPE --->
		<cfset VARIABLES.gender = ARGUMENTS.gender>

	</cffunction>

	<!--- **************************  SET HEIGHT  ************************** --->
	<cffunction name="setHeight" displayname="Set Height" description="Sets the height of a person" access="public" output="false" returntype="void">

		<!--- ARGUMENTS --->
		<cfargument name="height" displayname="Person Height" hint="The height of a person." type="string" required="true" />

		<!--- SET HEIGHT INTO THIS SCOPE --->
		<cfset VARIABLES.height = ARGUMENTS.height>

	</cffunction>

</cfcomponent>

We have a simple cfc here with an init() method, an addPerson() method, and several setter methods for setting different attributes of a person. The scope being used here is the VARIABLES scope. This scope persists with the instance of the component. In other words, until the a new instance is created any data stored in the VARIABLES scope will be available to all functions inside the component.

The init() method is called when we create an instance of the component. Upon creation we simply set a few pieces of data into the VARIABLES scope preparing the component to use any of this data in any of the following methods. In this case I go ahead and simply create a default person object, but could easily leave this data blank and rely on the set methods to fill in the person object attributes.

The next method is the addPerson() method which just compiles all of the attributes that make a person object and store it into a struct called ‘data’. Notice though that none of the values of a person are set in this method. It’s only job is to lump it all into a struct. We can do this because our data is stored in the VARIABLES scope.

The remaining functions just set data into the VARIABLES scope. Notice that they simply accept a value as an ARGUMENT, and then take that value and set it into the VARIABLES scope. This essentially overrides the value set initially in the init() method. Let’s look now at how this would be called from the .cfm page.

(more…)

Getting the full file path of a Coldfusion Component in dot-notation

Sunday, April 11th, 2010

When you are creating an instance of a cfc using the <cfobject> tag or createObject() method you need to specify the path to the desired component via a dot-notation file path.  All-in-all this is little more than a file path where the back-slashes are replaced with periods.  However, these paths are not full local paths like when using an absolute path for an image for example.  So the file path will not begin with ‘/Applications’ (if on a mac) or ‘C:/’ (if on windows).  Instead the path begins in the server root, which for Coldfusion is the ‘wwwroot’ directory.

If you are running a package of cfcs or are creating instances from different parts of your application I would recommend creating a mapping to the package or directory containing the cfc(s).  This will save you some work in the future if, say, the application directory structure changes.  You can get the full dot-notation path by introspecting the cfc you are creating an instance of.  I’ve created a short video showing a few ways of getting this file path pretty quickly.  Watch the video.

Stripping out HTML tags from text with Coldfusion 9

Wednesday, April 7th, 2010

I’ve built a fairly simple UDF for stripping out HTML from a chunk of text. I’ll quickly explain my logic and what this UDF can do.

First, there are two types of tags in HTML. One is a wrapping tag. An example of this is the bold tag.

<b>Some content</b>

Next is a self-contained tag.

<img src="my/path/image.jpg" />

If the tag is a wrapping tag we need to make the decision of whether or not to delete the content the tag is wrapping. For example the content inside a bold tag we will most likely want to keep. While the content inside a script tag we will want to delete.

When it comes right down to it, we need to make three decisions about the HTML being deleted.

  1. What tag needs to be deleted.
  2. Is this a wrapping tag.
  3. If a wrapping tag, should the content be deleted.

So let’s take a look at the UDF.

<cfcomponent displayname="Strip HTML" hint="Strips out of a set of given text any specified html tags." output="false">

	<!--- ************************  STRIP HTML  ************************ --->
	<cffunction name="stripHtml" displayname="Strip HTML" description="Strips out specified HTML tags." access="public" output="false" returntype="struct">

		<!--- ARGUMENTS --->
		<cfargument name="text" displayName="Text" type="string" hint="Text to strip out html tags from." required="true" />
		<cfargument name="tags" displayName="Tags" type="string" hint="Tags to be striped from the text.  Ex. '[string:tag name],[what to remove - {string:tag | string:content}],[is it a wrapping tag? {boolean}]'. Tags are delimited with semi-colons." required="true" />

		<!--- SET SOME LOCAL VARS --->
		<cfset var textbytes = "">
		<cfset var counter = 1>
		<cfset var delete = false>
		<cfset var temp = "">
		<cfset var tagtoberemoved = "">
		<cfset var whatgetsremoved = "">
		<cfset var wrappingtag = "">

		<!--- BUILD STRUCT --->
		<cfset var data = structNew()>
		<cfset data.success = true>
		<cfset data.message = "">
		<cfset data.orginaltext = ARGUMENTS.text>
		<cfset data.strippedtext = ARGUMENTS.text>

		<!--- CHECK IF ALL CONTENT SHOULD BE REMOVED --->
		<cfif ARGUMENTS.tags eq "all">
			<!--- REMOVE HTML TAGS --->
			<cfset data.strippedtext = rereplaceNoCase(ARGUMENTS.text, "<[^>]*>", "", "all")>
		<cfelse>
			<!--- LOOP OVER THE LIST OF TAGS TO BE REMOVED --->
			<cfloop list="#ARGUMENTS.tags#" index="VARIABLES.i" delimiters=";">
				<!--- SET ATTRIBUTES OF TAG TO BE DELETED --->
				<cfset tagtoberemoved = listFirst(VARIABLES.i, ",")>
				<cfset whatgetsremoved = listGetAt(VARIABLES.i, 2, ",")>
				<cfset wrappingtag = listLast(VARIABLES.i, ",")>

				<!--- IF REMOVING JUST THE TAG --->
				<cfif whatgetsremoved eq "tag">
					<!--- CHECK IF IT IS A WRAPPING TAG --->
					<cfif wrappingtag eq true>
						<!--- REMOVE WRAPPING TAG, BUT NOT THE CONTENT --->
						<cfset data.strippedtext = rereplaceNoCase(data.strippedtext, "<#tagtoberemoved#>", "", "all")>
						<cfset data.strippedtext = rereplaceNoCase(data.strippedtext, "</#tagtoberemoved#>", "", "all")>
					<cfelse>
						<!--- REMOVE CONTAINED TAG --->
						<cfset data.strippedtext = rereplaceNoCase(data.strippedtext, "<#tagtoberemoved# />", "", "all")>
					</cfif>

				<!--- IF REMOVING TAG AND CONTENT --->
				<cfelseif whatgetsremoved eq "content">
					<!--- CHECK IF IT IS A WRAPPING TAG --->
					<cfif wrappingtag eq true>
						<!--- REMOVE THE TAG AND CONTENT --->
						<cfset data.strippedtext = rereplaceNoCase(data.strippedtext, "<#tagtoberemoved#>.*</#tagtoberemoved#>", "", "all")>
					<cfelse>
						<!--- REMOVE CONTAINED TAG --->
						<cfset data.strippedtext = rereplaceNoCase(data.strippedtext, "<#tagtoberemoved# />", "", "all")>
					</cfif>
				</cfif>
			</cfloop>
		</cfif>

		<!--- RETURN STRUCT --->
		<cfreturn data>

	</cffunction>

</cfcomponent>

The method takes two arguments. First is the chunk of text to have HTML stripped out of. Second is information about the HTML to strip out of the text. The latter argument accepts a string. Multiple tags can be specified by separating them with semi-colons. For each tag that needs deleted three pieces of information needs to be provided. Each of the three pieces are delimited with a comma. These are the three arguments listed above. First is the name of the tag. So if we are wanting to remove an img tag we would specify “img”, for bold “b”. Second, we specify if we want just the tag removed or the tag and the content it wraps. There are two acceptable strings here – “tag” (for just removing the tag) and “content” (for removing the tag and the content). Lastly, specify whether this is a wrapping or contained tag. For an image tag we would specify “false” because it is not a wrapping tag. For bold we would specify “true” because it is a wrapping tag.

One last note, if you want to strip out all HTML tags you can just pass in the string “all” to the method. But note that this will just strip out the HTML and any content being wrapped by the tags will be left alone.

So let’s look at an example of how to call this tag.

<!--- TEXT TO STRIP HTML FROM --->
<cfset VARIABLES.text = "Lorem <img />ipsum <b>dolor</b> sit amet, <em>consectetur</em> adipiscing elit.>

<!--- STRIP OUT ALL HTML --->
<cfset stripHtml(VARIABLES.text, "all")>

<!--- STRIP OUT IMG, B, AND EM TAGS --->
<cfset stripHtml(VARIABLES.text, "img,tag,false;b,tag,true;em,content,true")>

Feel free to copy this and use it as you please.

Directory Watcher Gateway for file management logging

Sunday, April 4th, 2010

I wanted to play with gateways a little bit in Coldfusion 9 this weekend, but wanted to build something at least slightly practical.  What I ended up with is  a gateway that keeps up with file additions, changes, and deletions for a given project and logs those changes to a file.  This was surprisingly easy to accomplish with the directory-watcher gateway that ships with Coldfusion.  All in all this is a tool I may implement for development purposes just to have a running log of file changes that occur within a given project over the given amount of time.

So let’s get this ball rolling by creating a couple projects under the ‘wwwroot’ folder in our Coldfusion9 directory.  If you are still using Coldfusion 8 I don’t see any reason why this won’t work for you as well.  I haven’t tested it though.  For me, I named the first project ‘my_site’ and the second ‘my_othersite’.  Now set up two projects in Coldfusion Builder that respectively points to these two directories.

Now we’ll create a set of directories in each of these projects.  Under the project you should create a directory called ‘gateways’.  Under that directory create another directory called ‘cfcs’ and another one called ‘config’.  Note that this is how that I’ve chosen to do this.  You can set up your file structure however you want.  The main thing to note is that you will need the configuration file for the directory-watcher gateway in your project.  This way when you create your instance of the gateway in the Coldfusion Administrator you can point it to this specific project.  You will then be able to set a new configuration file in another project (which we’ll do) allowing you to watch multiple directories at once.  Inside of the ‘cfcs’ directory you can create a component called ‘directory_watcher.cfc’, and under the ‘config’ directory create a file called ‘directory-watcher.cfg’.  So your file structure should look like the following.

File Structure

File Structure

Now we’ll set up the directory-watcher.cfg file.  Under your ‘ColdFusion9′ directory navigate to ‘gateway/config’ and open the ‘directory-watcher.cfg’ file.  Copy the contents to your file in the my_site project.  Note: you could also just copy the entire file to your ‘config’ directory if you like.  The contents will look like the following.

directory-watcher.cfg

#
# DirectoryWatcherGateway configuration file
#

# The directory you want to watch.  If you are entering a Windows path
# either use forward slashes (C:/mydir) or escape the back slashes (C:\\mydir).
directory=

# Should we watch the directory and all subdirectories too
# Default is no.  Set to 'yes' to do the recursion.
recurse=no

# The interval between checks, in miliseconds
# Default is 60 seconds
interval=60000

# The comma separated list of extensions to match.
# Default is * - all files
extensions=*

# CFC Function for file Change events
# Default is onChange, set to nothing if you don't want to see these events
changeFunction=onChange

# CFC Function for file Add events
# Default is onAdd, set to nothing if you don't want to see these events
addFunction=onAdd

# CFC Function for file Delete events
# Default is onDelete, set to nothing if you don't want to see these events
deleteFunction=onDelete

We’re going to make just a couple changes to this file.  First and foremost we need to add the directory for the gateway to watch.  This is the my_site directory we created under the ‘wwwroot’ directory.  I’m on a mac so I would write ‘/Applications/ColdFusion9/wwwroot/my_site’ after the ‘directory=’ at the beginning of the file.  Next we need to set ‘recurse=no’ to ‘recurse=yes’.  This will allow our gateway to watch this directory and all directories beneath it.  Lastly, just so we can see our changes more quickly we can set the interval to ’10000′ milliseconds.  By default it runs every minute.  So our new file will look like:

(more…)