Search

Wednesday, January 30, 2008

Using Generic Lists and SQL Server Views in Model-Glue

Model-Glue comes with some generic messages that you can use to manipulate data from your database. Generic messages are only available when you are using an ORM adapter with Model-Glue (such as Reactor or Transfer). In general, "messages" are how Model-Glue refers to requests to the server. As a very brief overview, messages are broadcast by the event handler and then handled by the defined Model-Glue controller. You can read more about this at The Model-Glue Event Lifecycle in Layman's Terms on Doug Boude's blog.

I find that I most often use the "ModelGlue.genericList" message which will return a ColdFusion query from the table specified. To use this feature in your event-handler, you simply add something like so under the "broadcasts":

<message name="ModelGlue.genericList">
  <!-- The name of your table -->
  <argument name="object" value="Users" />

  <!-- The name of query variable where the data will be stored -->
  <argument name="queryName" value="usersList" />
</message>

The "object" and "queryName" are the two required arguments you have to specify. However, you can also add an order by clause:
<message name="ModelGlue.genericList">
  <!-- The name of your table -->
  <argument name="object" value="Users" />

  <!-- The name of query variable where the data will be stored -->
  <argument name="queryName" value="usersList" />

  <!-- The column by which the data will be ordered -->
  <argument name="orderBy" value="userName" />
</message>

Or even a criteria to filter the data:
<message name="ModelGlue.genericList">
  <!-- The name of your table -->
  <argument name="object" value="Users" />

  <!-- The name of query variable where the data will be stored -->
  <argument name="queryName" value="usersList" />

  <!-- The column by which the data will be ordered -->
  <argument name="orderBy" value="userName" />

  <!-- The column by which the data will be filtered -->
  <argument name="criteria" value="userName" />
</message>
The "criteria" argument is the equivalent of using the "where" clause in your SQL query. It has to contain a name of a column already part of your table. It can also contain a hard coded value for the column value such as:
<!-- The column by which the data will be filtered -->
<argument name="criteria" value="userName=boyan" />


If a hard coded value is not specified for the "criteria" argument, Model-Glue looks for the data in the form or url scope. In case current example, it will look for variable with the name "userName" in the event scope and use it's value to set the value of the "criteria".
The last, somewhat advanced option in the "genericList" message is the "gatewayMethod" argument. In case your query is more complicated than a simple table lookup (such as many joins, various arguments and special processing), you can use the "gatewayMethod" argument to specify what cfc method Model-Glue should use to get the "genericList". The use this argument, the method has to reside in the model gateway created for the object specified under the "object" argument. In this example, the "gatewayMethod" has to exists in the "Users" gateway. Here is what a gateway generated by Reactor looks like:

<cfcomponent hint="I am the mssql custom Gateway object for the jobs object.  I am generated, but not overwritten if I exist.  You are safe to edit me."
	extends="usersGateway" >

</cfcomponent>

And here is a simple "gatewayMethod" to get a list of users based on a SQL Server stored procedure. The method takes a single numeric parameter called "showDeletedUsers" based on which it returns all users or only currently active users. Please note that this method is over simplified to show how the functionality works:
<cfcomponent hint="I am the mssql custom Gateway object for the jobs object.  I am generated, but not overwritten if I exist.  You are safe to edit me."
	extends="usersGateway" >

<cffunction name="getUsers" access="public" output="false" returntype="query">
	<cfargument name="showDeletedUsers" type="numeric" required="yes" />

	<cfset var usersQuery = queryNew("") />

	<cfstoredproc datasource="#_getConfig().getDsn()#" procedure="dbo.spGetUsers">
		<cfprocparam
		type="in" null="no" cfsqltype="cf_sql_int"
		dbvarname="showDeletedUsers" value="#arguments.showDeletedUsers#" />

		<cfprocresult name="usersQuery" resultset="1">
	</cfstoredproc>

	<cfreturn usersQuery />
</cffunction>

</cfcomponent>

To invoke this method through the "genericList", you simple use:
<message name="ModelGlue.genericList">
  <!-- The name of your table -->
  <argument name="object" value="Users" />

  <!-- The name of query variable where the data will be stored -->
  <argument name="queryName" value="usersList" />

  <!-- The column by which the data will be ordered -->
  <argument name="orderBy" value="userName" />

  <!-- The column by which the data will be filtered -->
  <argument name="criteria" value="showDeletedUsers=0" />

  <!-- The custom method to use when retrieving the data -->
  <argument name="gatewayMethod" value="getUsers" />
</message>

The last thing to remember is that you can use the "genericList" with SQL Server views and not just tables. Views are like a custom table that you have defined on the server. They can contain custom fields and data from multiple tables. So if you have a created a view called "vGetUsers" (instead of using the custom "gatewayMethod"), you can use in the "genericList" in the same manner as you would a table:
<message name="ModelGlue.genericList">
  <!-- The name of your view -->
  <argument name="object" value="vGetUsers" />

  <!-- The name of query variable where the data will be stored -->
  <argument name="queryName" value="usersList" />

  <!-- The column by which the data will be ordered -->
  <argument name="orderBy" value="userName" />
</message>

Tuesday, January 29, 2008

Build Your SubSonic DAL with NAnt

SubSonic is a .NET ORM (object relational mapping) tool with plenty of extras. In the ORM market, it can be compared to other tool such as Linq and NHibernate. I chose SubSonic over NHibernate, because of the ease of configuration or Linq, because I am still programming for .NET 2.0. That being said, there are three different ways to generate your data access layer using SubSonic:

  1. Using the command line by calling "sonic.exe"
  2. Using the SubSonic Tools for Visual Studio
  3. Using a build provider. However, this method is only available to web projects.

I was using the second method by installing the tools for Visual Studio. However, that got to be a pain since every time I want to regenerate the data access layer I have to start Visual Studio, wait for it to load, invoke the SubSonic tools, wait for the generation and finally rebuild the project.

So a few days ago I switched to using the first method - the command line. Once I did that, it was a matter of time before I wrote the NAnt build file to do it for me without Visual Studio - generate the data access layer and rebuild the data access layer dll. So here is the build file that can be used stand alone or even invoked through a pre-build event in Visual Studio:

<project name="Ensemble" default="build" basedir=".">
  <!-- The full path to the SubSonic directory where SubSonic.dll is found -->
  <property name="subSonicFullPath" value="c:\Program Files\SubSonic\SubSonic 2.0.3" />

  <!-- The full path to the SubSonic commander -->
  <property name="sonicCommanderFullPath" value="C:\Program Files\SubSonic\SubSonic 2.0.3\SubCommander\sonic.exe" />

  <!-- The command line arguments for the SubSonic commander -->
  <property name="sonicCommanderArguments" value="generate /lang vb" />

  <!-- The root namespace for the project -->
  <property name="build.rootNamespace" value="ensembleVideo" />

  <!-- The build type (release or debug) for the project -->
  <property name="build.config" value="release" />

  <!-- The relative path to the project directory (from the location of the build file) -->
	<property name="targetDirectoryRelativePath" value="../dataAccessLayer" overwrite="false" />

	<!-- The relative path to directory where SubSonic will generated the files (from the project target directory) -->
	<property name="subsonicGeneratedFilesRelativePath" value="generated" />

  <property name="targetDirectoryFullPath" value="${path::get-full-path(targetDirectoryRelativePath)}" overwrite="false" />
  <property name="subsonicGeneratedFilesFullPath" value="${path::combine(targetDirectoryFullPath, 'generated')}" />
	<property name="binDirectory" value="${path::combine(targetDirectoryFullPath, 'bin')}" overwrite="false" />

  <property name="buildDirectoryFullPath" value="${path::combine(binDirectory, build.config)}" overwrite="false" />
  <property name="buildOutput" value="ensembleVideo.dataAccessLayer.dll" />

  <!-- Default build target -->
	<target name="build">
	  <!-- Execute the SubSonic commander to generated the files in the defined "sonicCommanderFullPath" directory -->
	  <exec
    	basedir="${targetDirectoryFullPath}"
    	program="${sonicCommanderFullPath}"
    	commandline="${sonicCommanderArguments} /out "${subsonicGeneratedFilesFullPath}""
    	workingdir="${targetDirectoryFullPath}"
    	failonerror="true" />

    <!-- Execute the vb compiler to compile the SubSonic generated files -->
    <vbc target="library" output="${path::combine(buildDirectoryFullPath, buildOutput)}" rootnamespace="${build.rootNamespace}">
        <imports>
            <import namespace="System" />
            <import namespace="System.Data" />
        </imports>

        <sources>
          <!-- Include all the SubSonic generated files -->
          <include name="${subsonicGeneratedFilesFullPath}\*.vb" />
        </sources>

        <!-- Include a reference to the SubSonic dll -->
        <references basedir="${subSonicFullPath}">
  			  <include name="SubSonic.dll" />
  		  </references>
     </vbc>
	</target>
</project>

Friday, January 25, 2008

All Your Dates are Belong to Us - Custom Date Formatting in SQL Server

Most people that use SQL Server are familiar with formatting dates inside SQL Server. The common approach is:
convert(varchar, getdate(), 106) -- Displays a date in the format 25 Jan 2008
That is all fine and good if your requirements are satisfied with the formats available inside SQL Server. SQL Server does provide a decent number of formats. Here is the list right from the SQL Server help:
Without century (yy) With century (yyyy) Standard Input/Output**
-0 or 100 (*) Defaultmon dd yyyy hh:miAM (or PM)
1101USAmm/dd/yy
2102ANSIyy.mm.dd
3103British/Frenchdd/mm/yy
4104Germandd.mm.yy
5105Italiandd-mm-yy
6106-dd mon yy
7107-Mon dd, yy
8108-hh:mm:ss
-9 or 109 (*) Default + millisecondsmon dd yyyy hh:mi:ss:mmmAM (or PM)
10110USAmm-dd-yy
11111JAPANyy/mm/dd
12112ISOyymmdd
-13 or 113 (*) Europe default + millisecondsdd mon yyyy hh:mm:ss:mmm(24h)
14114-hh:mi:ss:mmm(24h)
-20 or 120 (*) ODBC canonicalyyyy-mm-dd hh:mi:ss(24h)
-21 or 121 (*) ODBC canonical (with milliseconds)yyyy-mm-dd hh:mi:ss.mmm(24h)
-126(***)ISO8601yyyy-mm-dd Thh:mm:ss:mmm(no spaces)
-130*Kuwaitidd mon yyyy hh:mi:ss:mmmAM
-131*Kuwaitidd/mm/yy hh:mi:ss:mmmAM
Wouldn't you know it, my requirements were not :-) I tried working with the built in formats and had to roll my own after all anyway. So here is the function I came up with: Please note: The function below relies on using regular expressions inside SQL Server. SQL Server does not have built in regular expression support so the function below relies on the method described in my other blog post about Using Regular Expression in SQL Server.
/*
Type:		Function
Name:		dbo.fnFormatDate
Author:		Boyan Kostadinov
Created:	01.25.2008
Dependencies:	master.dbo.fn_pcre_replace
Parameters:	@inputDate(datetime)
		- The date to format

		@formatString(varchar)
		- the format string to use (Examples "dd mm yyyy", "mmm.dd.yy")
Description:

Formats a given date based on the format specified in @formatString
d 	- one digit day (when applicable)
dd	- two digit day
ddd	- short day name
dddd	- long day name
m	- one digit month (when applicable)
mm	- two digit month
mmm	- short month name
mmmm	- long month name
yy	- two digit year
yyyy	- four digit year
*/
create function dbo.fnFormatDate
(
	@inputDate datetime,
	@formatString varchar(25)
)
returns varchar(20) as
begin
	declare @returnValue varchar(25)

	-- Declare local vairables
	declare @formattedDate varchar(25),
		@day varchar(20), @month varchar(20), @year varchar(20),
		@dayFormat varchar(5), @monthFormat varchar(5), @yearFormat varchar(5)

	set @dayFormat = ''
	set @monthFormat = ''
	set @yearFormat = ''

	-- Convert the supplied date to day mon year (25 Jan 2008)
	set @formattedDate = convert(varchar, @inputDate, 106)

	-- If the format string contains a format for the day
	if charindex('d', @formatString) > 0
		-- Get the day format string
		set @dayFormat = master.dbo.fn_pcre_replace(@formatString, '.*?(d{1,4}).*', '$1')

	-- If the format string contains a format for the month
	if charindex('m', @formatString) > 0
		-- Get the month format string
		set @monthFormat = master.dbo.fn_pcre_replace(@formatString, '.*?(m{1,4}|M{1,4}).*', '$1')

	-- If the format string contains a format for the year
	if charindex('y', @formatString) > 0
		-- Get the year format string
		set @yearFormat = master.dbo.fn_pcre_replace(@formatString, '.*?(y{2,4}).*', '$1')

	-- Format the day value based on the format string for the day
	select	@day =
		case @dayFormat
		when 'dd' then master.dbo.fn_pcre_replace(@formattedDate, '^(\d+).*', '$1')
		when 'ddd' then substring(datename(dw, @formattedDate), 1, 3)
		when 'dddd' then datename(dw, @formattedDate)
		else convert(varchar, day(@formattedDate))
	end

	-- Format the month value based on the format string for the month
	select	@month =
		case @monthFormat
		when 'mm' then master.dbo.fn_pcre_replace(convert(varchar, @inputDate, 101), '^(\d+)/.*', '$1')
		when 'mmm' then master.dbo.fn_pcre_replace(@formattedDate, '\d+\s(\w+)\s\d+', '$1')
		when 'mmmm' then datename(m, @formattedDate)
		else convert(varchar, month(@formattedDate))
	end

	-- Format the year value based on the format string for the year
	select	@year =
		case @yearFormat
		when 'yy' then substring(convert(varchar, year(@formattedDate)), 3, 2)
		else convert(varchar, year(@formattedDate))
	end

	set @returnValue = @formatString

	-- If the day format was specified
	if @dayFormat <> ''
		-- Replace the day format string with the actual day value
		set @returnValue = master.dbo.fn_pcre_replace(@returnValue, @dayFormat, @day)

	-- If the month format was specified
	if @monthFormat <> ''
		-- Replace the month format string with the actual month
		set @returnValue = master.dbo.fn_pcre_replace(@returnValue, @monthFormat, @month)
	
	-- If the year format was specified
	if @yearFormat <> ''
		-- Replace the year format string with the actual year
		set @returnValue = master.dbo.fn_pcre_replace(@returnValue, @yearFormat, @year)

	-- Return the formated value
	return @returnValue
end
To test this function, I created a table that hold the following date format string:
formatString             
-------------------------
dd MMM yy
dd MMM yyyy
dd-MM-yy
dd-MM-yyyy
dd.MM.yy
dd.MM.yyyy
dd/MM/yy
dd/MM/yyyy
ddMMMyy
ddMMMyyyy
MM-dd-yy
MM-dd-yyyy
MM/dd/yy
MM/dd/yyyy
MMM dd yyyy
MMM dd, yy
MMM dd, yyyy
MMMdd,yyyy
MMMddyyyy
yy.MM.dd
yy/MM/dd
yyMMdd
yyyy-MM-dd
yyyy.MM.dd
yyyy/MM/dd
yyyyMMdd
MMMyyyy
I tested the function with the simple SQL query:
select df.formatString,
dbo.fnFormatDate(getdate(), df.formatString)
as formattedDate
from dateFormats as df
And here are the results:
formatString              formattedDate       
------------------------- --------------------
dd MMM yy 25 Jan 08
dd MMM yyyy 25 Jan 2008
dd-MM-yy 25-01-08
dd-MM-yyyy 25-01-2008
dd.MM.yy 25.01.08
dd.MM.yyyy 25.01.2008
dd/MM/yy 25/01/08
dd/MM/yyyy 25/01/2008
ddMMMyy 25Jan08
ddMMMyyyy 25Jan2008
MM-dd-yy 01-25-08
MM-dd-yyyy 01-25-2008
MM/dd/yy 01/25/08
MM/dd/yyyy 01/25/2008
MMM dd yyyy Jan 25 2008
MMM dd, yy Jan 25, 08
MMM dd, yyyy Jan 25, 2008
MMMdd,yyyy Jan25,2008
MMMddyyyy Jan252008
yy.MM.dd 08.01.25
yy/MM/dd 08/01/25
yyMMdd 080125
yyyy-MM-dd 2008-01-25
yyyy.MM.dd 2008.01.25
yyyy/MM/dd 2008/01/25
yyyyMMdd 20080125
MMMyyyy Jan2008

Saturday, January 19, 2008

Kick Butt Project Management with Assembla

A few days ago I blogged about Tools for Better Project Management and Organization and this post offers an alternative application for project management. As I mentioned in my pervious post, I highly recommend RedMine as your web based project manager. RedMine is great if you want something that you have complete control over. With complete control however, come the pain of having to install it, configure it and worry about backups. So today, as a part of a project I am involved in, I set on a quest to find something that would be as good as RedMine but without the installation and configuration involved. After evaluating a few different web based project management application (Unfuddle, CodeSpaces, and a few other not worth mentioning), I tried Assembla. I had put in on my list to evaluate a while ago so it was not a new find but I am sorry I did not try it earlier. I am not going to get into much detail on the features but I have to say, the number of features is impressive. Best of all you get all this including 500mb of space for free! Here is a quick overview:

The start page:

Boyan Kostadinov- start -Assembla_1200795708018

The dashboard

Ensemble Video- index -Assembla_1200795731143 

Time entry

Ensemble Video- tasks -Assembla_1200795740736

Milestones

Ensemble Video- Milestones -Assembla_1200795878080

Online chat

Ensemble Video- chat -Assembla_1200795889986

Files repository

Ensemble Video- files -Assembla_1200795897143

Wiki

Ensemble Video- Space Home -Assembla_1200795904674

Trac/SVN integration

Ensemble Video- trac_tool -Assembla_1200795912877

Images repository

Ensemble Video- Images -Assembla_1200795923080

Blog integration with Mephisto

Ensemble Video- mephisto_tool -Assembla_1200795932439

Alerts

Ensemble Video- alerts -Assembla_1200795964174

Additional add-ons

Ensemble Video- edit -Assembla_1200795987596

Friday, January 18, 2008

Kill All Database Connections to a SQL Server Database

Today I needed to kill the active connections to a particular database so I can delete it. Usually that can be a painful process of opening enterprise manager and killing each active process one by one. While that approach might be fine for one or two active connections, when you have over 10, it gets to be annoying. So here is a script that will get all the active connections for a given database and kill each one. This is already part of my other post titled SQL Server Script to Restore a Database from File.
-- Create the sql to kill the active database connections
declare @execSql varchar(1000), @databaseName varchar(100)
-- Set the database name for which to kill the connections
set @databaseName = 'myDatabase'

set @execSql = '' 
select  @execSql = @execSql + 'kill ' + convert(char(10), spid) + ' '
from    master.dbo.sysprocesses
where   db_name(dbid) = @databaseName
     and
     DBID <> 0
     and
     spid <> @@spid
exec(@execSql)
Update
As Noel pointed out, it turns out there is much simpler way:
alter database dbName set single_user with rollback immediate
and to revert that
alter database dbName set multi_user with rollback immediate

Thursday, January 17, 2008

Prototype.js Extensions by Nick Stakenburg

I was working on a project today with the excellent Prototip extension for Prototype.js. Prototip makes it easy to add tooltips to any element. Here is what a custom styled tooltip would look like:

toolitpExample

While browsing the support forum that Nick has put together for Prototip, I realized that he has created more than one Prototype extension. First is Starbox which allows you to add a star rating system to your page:

 Starbox - Rating stars for prototype_1200573851295

Next is Lightview which is very similar to Lightbox and it creates an overlay window to display your images:

Lightview_1200574518998

Monday, January 14, 2008

Tools for Better Project Management and Organization

Organization and project management have turned into very important skills even for developers. With growing demand for automation and the spread of information, companies are consistently working on new ways to organize, restructure and make data accessible.

If you are a developer, at some point in your career, it is very likely that you have run into trouble with a project running over the perceived time frame and/or original specifications. Everyone in the programming word has heard the horror stories of never ending features and lack of project requirements. I would not be honest if I said this has not happened to me. C'est la vie, as the French would say but I am trying to learn something from it.

Over the past few months, I have started to use several tools to keep track of project requirements/bugs/features, log tasks I have worked on/accomplished, and billing related data (tasks, time, invoices, etc).

Project Management

After evaluating about 20 open source projects (thank you SourceForge), and some commercial products, I settled on using a open source project called RedMine. Here is the overview of RedMine's features directly from the web site:

  • Multiple projects support
  • Flexible role based access control.
  • Flexible issue tracking system
  • Gantt chart and calendar
  • News, documents & files management
  • Feeds & email notifications.
  • Per project wiki
  • Per project forums
  • Simple time tracking functionality
  • Custom fields for issues, projects and users
  • SCM integration (SVN, CVS, Mercurial, Bazaar and Darcs)
  • Multiple LDAP authentication support
  • User self-registration support
  • Multilingual support
  • Multiple databases support

You can see a full list of features plus screen shots at the Features page.

Task Logging

The nice folks at Lifehacker have put together an excellent little script for logging your daily tasks. The basics of it are:

  • You setup the script by editing the .vbs file, create a shortcut to it and setup a shortcut key
  • You press the shortcut key and an input box pops up
  • You enter the task/item you want to log and the script writes it with a date plus time to your log file

You can find the full details about it and how to set it up at Geek to Live: Quick-log your work day

Keeping Track of Billing Data

There is more than one tool for the job so choices highly depend on features, preference and price. For my need, I have found that a tool will need a least a minimum set of features:

  • Keep track of clients and projects
  • Log time and description per time spend on project
  • Generate invoices for selected time frame

Considering the above, I can recommend a small windows application called Billing Tracker. The pro version costs $89. I am sure there are plenty of free web based applications that fall in this category too.

Bonus - Keeping Track of your Daily Tasks

Even with all the above, keeping on track with your projects and life can be hard. Not everything falls into a project and sometimes you do not want to create the smallest task as part of your project tracking. On the other hand, if you don't write it down, you will forget it, I do.

So here is where a To-Do list comes in. There are many, many free, web based to-do applications. I use Remember The Milk. Some nice features are:

  • Offline access (with Google Gears)
  • Task categories, lists and locations
  • Reminders
  • Integration with GMail (only for Firefox with an extension) and Google Calendar
  • RSS of your tasks


Conclusion

No matter how you decide to go about it, most important than everything else is your discipline to track and log your work.

Using Optional Parameters in SQL Server Stored Procedures

Stored procedures are a nice way to encapsulate some business logic inside your database. This post is not about learning how to write stored procedures but instead deals with the specifics of implementing optional parameters in your SQL Server stored procedures. You can learn more about stored procedures at Wikipedia or Google.

The most common use of stored procedures is to return some data based on some passed in parameters. More often then not they need to take multiple parameters. A common struggle is how to pass optional parameters to a stored procedure.

Let's talk about a simple example. You have an employees table that has the employee id, first name and last name. The table is defined as follows:

create table [dbo].[employees] (
  [id] int identity(1, 1) not null,
  [firstName] varchar(255) null,
  [lastName] varchar(255) null,
  primary key clustered ([id])
)
on [primary]
go

and has the been populated with the following sample data:

insert into [dbo].[employees] ([firstName], [lastName])
values (N'Boyan', N'Vassilvev')
go

insert into [dbo].[employees] ([firstName], [lastName])
values (N'Boyan', N'Kostadinov')
go

insert into [dbo].[employees] ([firstName], [lastName])
values (N'Doug', N'Boude')
go

You would like to write a stored procedure to return this data but if you don't want to write separate procedure for each type of employee search you want to execute. Ideally, you want to write one stored procedure that will return all employees or only an employee with a certain id or an employee has a certain first name (or last name for that matter). So how is this done? Simple, you need to use optional parameters in your stored procedure. You define your procedures as follows:

create	proc	dbo.spGetEmployees
	@employeeID int = null,
	@firstName varchar(255) = null,
	@lastName varchar(255) = null
as

select	*
from	dbo.employees
where	(id = @employeeID or @employeeID is null)
	and
	(firstName = @firstName or @firstName is null)
	and
	(lastName = @lastName or @lastName is null)

Now you can call the same stored procedure 4 different ways:

-- Without parameters to get all the employees
exec dbo.spGetEmployees
-- With id parameter to get an employee with a specific id
exec dbo.spGetEmployees 1
-- With first name parameter to get an employee with a specific first name
exec dbo.spGetEmployees null, 'boyan'
-- With last name parameter to get an employee with a specific last name
exec dbo.spGetEmployees null, null, 'kostadinov'

And you will get results which look like:

image

Note: Something to keep in mind is that the parameter order is important. If you are specifying only the first optional parameter, you do not need anything else. However, if you want to use any but the first parameter, you need to set any preceding parameters to "null".

Friday, January 11, 2008

Unobtrusive JavaScript - Tie Events to Links with Prototype

Often, it is a good practice to separate your presentation layer from the the logic of your application. In web application, the presentation layer is your html pages, while the logic usually resides on the server or in JavaScript on the client (such as validation of user submitted values). Separating presentation and logic usually has to do with as little as possible logic inside your presentation layer.
With this in mind, it would be nice to have your application do something with JavaScript when you click on a link. In previous years that would be written something like this:
<a href="#" onClick="alert('You Clicked on Link 1');">Link 1</a>
<a href="#" onClick="alert('You Clicked on Link 1');">">Link 1</a>
While that works for this simple example, it is much nicer not to duplicate your code and not to mix your html with JavaScript. There is more than one reason the above approach is no longer the desired on, but here are few reasons: you need to write the "alert" call in each link you create, if your functionality changes, you will have to track each one of those calls and change them. The newer, desired, approach is to leave the JavaScript in a separate file and instead use the element's CSS class name to apply some logic to the element. So instead of the way above, you will simply define your links as follows:
<a href="#" class="clickMe">Link 1</a>
<a href="#" class="clickMe">Link 2</a>

Now you just need to write the JavaScript which will look for all "a" elements with the CSS class name of "clickMe" and for each one found, it will invoke the "whatDidIClick" function.

<script type="text/javascript" language="javascript">
// For each link element with CSS class name "clickMe"
$$('a.clickMe').invoke('observe', 'click', whatDidIClick);

function whatDidIClick(event) {
// Event.element(event) returns the element that caused the click event
alert('You clicked on ' + Event.element(event).innerHTML));
}
</script>

Wednesday, January 09, 2008

Speaking at CFUnited 2008 about JavaScript Frameworks

125x125_cfunited08_speakerJust a quick note about this year's CFUnited in Washington DC. If you don't know what CFUnited is, you can learn more at the CFUnited web site. Basically, it is one of the few major ColdFusion conferences.

I will be speaking on the topic of "Using Popular JavaScript Frameworks" and it will cover Prototype, jQuery, mooTools, Yahoo UI and Ext. I haven't worked out the complete table of contents but I will have one shortly.

Tuesday, January 08, 2008

Introduction to Model-Glue

The presentation below is a short introduction to Model-Glue for the Central New York ColdFusion Users Group.

Supporting Documentation http://docs.google.com/Doc?id=dcz9szc5_11gff8ddd5

Friday, January 04, 2008

NAnt, What Operating System is This?

I needed a quick way to tell what operating system the NAnt build file was running under. Why, one might wonder? Let's say you are installing a web application under IIS and you need the ASP.NET extension to be enabled. If you are wondering what that means, it's simple, with the introduction of IIS 6 (under Windows 2003), individual extensions (such as ASP or ASP.NET) can be enabled or disabled by the system administrator. So back to the why, you need to install a ASP.NET web application under IIS but to make things more complicated (as I often like to do), the ASP.NET extension might be disabled. So, you create a NAnt build file to do all the copy/setup steps for your web application installation (if you don't know what NAnt is, check out my other post titled Getting Started with NAnt - .NET Build Tool) but now you need to check if the ASP.NET extension is enabled. Before you can do that, you have to remember that only IIS 6 under Windows 2003 supports enabling/disabling extensions. If you try that under Windows XP (IIS 5) or Windows 2000 (IIS 5), your build file with barf. So to make a long story short, I need a test that will tell me if the NAnt build file is running under Windows 2003 or another operating system. So let's start with the NAnt build file that will output the current operating system:
<project name="testOS" default="runTest">
  <property name="operatingSystem" value="${operating-system::to-string(environment::get-operating-system())}" />

  <target name="runTest">
    <echo message="${operatingSystem}" />
  </target>
</project>

On Windows 2000 with Service Pack 4, that will output "Microsoft Windows NT 5.0.2195 Service Pack 4"
On Windows XP with Service Pack 2, that will output "Microsoft Windows NT 5.1.2600 Service Pack 2"
On Windows 2003 with Service Pack 2, that will output "Microsoft Windows NT 5.2.3790 Service Pack 2"
So now, to test if the NAnt build file is running under Windows 2003, we simply use "string::contains":
<if test="${string::contains(operatingSystem, 'Microsoft Windows NT 5.2')}">
  <echo message="This is Windows 2003" />
</if>

The complet NAnt build file to test the operating system should look like:
<project name="testOS" default="runTest" basedir=".">
  <property name="operatingSystem" value="${operating-system::to-string(environment::get-operating-system())}" />

  <target name="runTest">
    <if test="${string::contains(operatingSystem, 'Microsoft Windows NT 5.0')}">
      <echo message="This is Windows 2000" />
    </if>

    <if test="${string::contains(operatingSystem, 'Microsoft Windows NT 5.1')}">
      <echo message="This is Windows XP" />
    </if>

    <if test="${string::contains(operatingSystem, 'Microsoft Windows NT 5.2')}">
      <echo message="This is Windows 2003" />
    </if>
  </target>
</project>
// //]]>