Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Florida web site design



Czech your Page Rank!
Check Page Rank of any web site pages instantly:
This free page rank checking tool is powered by Page Rank Checker service
Surf's Up!
Visit Egosurf.org and massage YOUR web ego!
My Score: 9,001
Doug's Books

Read (and recommend)

  • Men are from Mars, Women are from Venus
  • The Wisdom of Crowds: Why the Many Are Smarter Than the Few and How Collective Wisdom Shapes Business, Economies, Societies and Nations
  • Blink: The Power of Thinking Without Thinking
  • Head First Design Patterns
  • Transact-SQL Programming
  • What's So Amazing About Grace?
  • Just So Stories (Rudyard Kipling collection)

Reading

  • Prayer: Does it Make Any Difference?
  • Data Mining (Practical Machine Learning Tools and Techniques)
<< September, 2006 >>
SMTWTFS
12
3456789
10111213141516
17181920212223
24252627282930
Search Blog

Recent Comments
Categories
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

25 September 2006
Faux Audio Streaming
If you ever find yourself with the need or desire to build a streaming audio library but don't want to mess with incorporating REAL Server or some other formal product to do it, I came across a cool little workaround that I thought I'd toss out there just "FYI".

In my experiment, the test audio files are in MP3 format, but I assume that any format that can be natively played by Windows Media Player will work just as well.

The basic steps:
  • Upload MP3s to their new home;
  • Create a template containing a link to a "pointer" file;
  • Create the pointer file, which itself contains links directly to the MP3(s);

Okay, let's say I have two songs I want people to be able to play, but I don't want them to have to wait until the entire file downloads before it starts playing. I create my main template with a link to a pointer file, like so:

<a href="mytest.m3u">Click here to try out "faux audio streaming"</a>
.....

I then create the actual pointer file, which itself contains complete URLs to the audio files:

http://www.dougboude.com/documents/02 Help Is On It's Way.mp3
http://www.dougboude.com/documents/03 Love's Theme.mp3
Important: This pointer file is saved with a "M3U" extension

Now, when I click the link in the main template, I'm prompted to open Windows Media Player. When I do, the first song immediately begins to play even before it has completely downloaded. In addition, I see in my media player's playlist all of the mp3 files listed in my pointer file, and have the ability to skip around.



You can see it in action by clicking here.
Posted by dougboude at 7:27 PM | PRINT THIS POST! | Link | 1 comment



19 September 2006
Migrating Diagrams in SQL 2000 or older
If you've ever tried to migrate or export database diagrams from one database to another in SQL Server 2000 or older, you may have noticed that no provision is made for doing so within the export wizard. Why the engineers would overlook THAT little detail I have no idea. But I came across a solution and so thought I'd put it out there just in case anybody else ever has the same issue.

Diagrams live in a system table called dtProperties, which is a system table. Every database has this table present, so no need to look in the Master database for that one.

What we're going to do is a basic insert into the dtproperties table of our destination database from our source database, using the following sql:

SET IDENTITY_INSERT DestinationDB..dtproperties ON

INSERT DestinationDB..dtproperties (id, objectid, property, value, uvalue, lvalue, version)
SELECT T1.id, T1.objectid, T1.property, T1.value, T1.uvalue, T1.lvalue, T1.version
FROM SourceDB..dtproperties T1
 
SET IDENTITY_Insert DestinationDB..dtproperties OFF
(Note: If the version of SQL server being used is older than version 2000, omit the field named 'uvalue' as it does not exist in previous versions)

Bear in mind that the above sql assumes that no other diagrams currently exist in your destination database. Executing the above sql against a dtproperties table that has existing diagrams is a really bad idea since you might be inserting rows with identical IDs.

If you can't see your database's system tables and wish to peruse it, right click on the sql server name in Enterprise Manager, click Edit Server Registration Properties, and make sure the item labeled "show system databases and system objects" is checked, as in the following illustrations:



That's it!
Posted by dougboude at 1:07 PM | PRINT THIS POST! | Link | 0 comments
18 September 2006
Client-Side Interactivity without Ajax
Keeping response times down and interactivity high has and always will be two important priorities with web interfaces of any kind. For standard html interfaces, Ajax is all the buzz and is great when it’s necessary to maintain interaction with live data. But when a static version of the data will do just fine, there’s at least one other alternative that you may want to consider….

The <CFWDDX> tag provides a way for us to bridge the gap between dynamic data processed on the server side (specifically, queries returned by CFQUERY), and Javascript on the client side. What this does is give us the ability to transform query results into a form that we can manipulate via javascript, opening up some very useful potential for interactive Dynamic HTML.

In a nutshell, the process goes like this:
  • A CFQUERY tag is run;
  • The result is fed to CFWDDX with the ACTION attribute set to CFML2JS, which produces a Javascript-ready recordset;
  • The JS function that works with the recordset is created
That’s it. Now, for some of the nitty gritty details

In my example, I’m displaying a list of employees for the user to select from. What I want though is that whenever an employee is selected from the list, their detailed information is displayed to the side so that the user can determine if this is the right person or not. Like so:

(go 'head! click it!)

 

Ajax would work for this! Everytime an employee’s name is clicked on, we could make a call back to the server and grab that employee’s detailed information, then, using Javascript, output it to the designated portions of the template without ever reloading the page (yet still executing an HTTP call over the web). Or, we could just go ahead and retrieve the employees’ details during our initial query, since we had to retrieve their names and IDs anyway in order to produce the select list.
<!--- Using QuerySim to create the cfquery... --->
<cf_querysim>
    UserInfo
    userid,fname,lname,ext,title,dept,email
    200|Joe|Blow|2235|Supervisor|Reporting|jblow@nowhere.com
    300|Doug|Boude|2112|WaterBoy|Recreation|dboude@nowhere.com
    100|Stan|Cox|1225|Developer|IT|scox@nowhere.com
    400|Jim|Pickering|1513|Designer|IT|jpickering@nowhere.com
    500|John|Smith|1112|CEO|Management|JASmith@nowhere.com
    600|John|Smith|1123|Janitor|Building Maintenance|JLSmith@nowhere.com
</cf_querysim>

We could then take that query and create a Javascript-friendly version of it like so:
<script>
    <cfwddx action="cfml2js" input="#UserInfo#" toplevelvariable="users">
....
</script>
(Note that the attribute “TopLevelVariable” contains the value that will be used to name our recordset; that is the name it will be referenced as within our JS function.)

Within the body of our page, we’ll go ahead and create the select list just like we always do, only let’s add a function call to the onClick event that we can use to dynamically populate the targeted portions of our template:
<select onClick="getUserInfo(this.value);" size="8">
    <option value="" selected></option>
    <cfoutput query="userInfo">
        <option value="#userid#">#lname#, #fname#</option>
    </cfoutput>
</select> 

Additionally, let’s create the placeholders for our employee details:
<table>
    <tr>
        <td><STRONG>First Name:</STRONG></td><td><span id="fname"></span></td>
    </tr>
    <tr>
        <td><STRONG>Last Name:</STRONG></td><td><span id="lname"></span></td>
    </tr>
    <tr>
        <td><STRONG>Title:</STRONG></td><td><span id="title"></span></td>
    </tr>
    <tr>
        <td><STRONG>Department:</STRONG></td><td><span id="dept"></span></td>
    </tr>
    <tr>
        <td><STRONG>Extension:</STRONG></td><td><span id="ext"></span></td>
    </tr>
    <tr>
        <td><STRONG>Email:</STRONG></td><td><a id="email" href=""></a></td>
    </tr>
</table>
(Note: In my example, I use SPAN tags as placeholders as well as an Anchor tag, but any valid document object (form fields, div tags, etc.) can be targeted as placeholders, as long as they have a unique ID value)

At this point, our query has been converted into a ‘WDDXRecordset’ object. Behind the scenes, it’s a complex Javascript array, but the nice folks at Adobe wrapped it up for us with a set of functions that make it easy to get at the data using JS.  For example, to retrieve the first name from the first row, the call would look something like
Var thisName = getField(0,’fname’);
....
(Note that our first row is referenced with zero instead of one.)

Now all we need is a function to retrieve the employee details. It’s as simple as a Javascript FOR loop, leveraging the functions provided by the WDDXRecordset object:
function getUserInfo(userid){
    var i = 0;
    for (i=0; i < users.getRowCount(); i++){// loop through our wddx recordset...
        if(users.getField(i,'userid') == userid){//if the current record matches the ID passed in...
            document.getElementById('fname').innerHTML = users.getField(i,'fname');
            document.getElementById('lname').innerHTML = users.getField(i,'lname');
            document.getElementById('title').innerHTML = users.getField(i,'TITLE');
            document.getElementById('dept').innerHTML = users.getField(i,'dept');
            document.getElementById('ext').innerHTML = users.getField(i,'ext');
            document.getElementById('email').href = 'mailto:' + users.getField(i,'email');
            document.getElementById('email').innerHTML = users.getField(i,'email');
            return false; //ends this function call
        }
    }
    //if we made it here, then we found no match in our loop above; just blank everything out.
    document.getElementById('fname').innerHTML = "";
    document.getElementById('lname').innerHTML = "";
    document.getElementById('title').innerHTML = "";
    document.getElementById('dept').innerHTML = "";
    document.getElementById('ext').innerHTML = "";
    document.getElementById('email').href = "";
    document.getElementById('email').innerHTML = "";
}  
(Note: Although our function only utilized the getField() and getRecordCount() methods,  several more are available to us. You can take a gander at what else the WDDXRecordset provides at this LiveDocs link)

You can grab the entire template here if you'd like to see it all together.

Gotchas/things to remember:
  • some web hosts don't provide access to the CFIDE/Scripts directory. If this is the case, then you'll need to grab a copy of wddx.js and put it in your webroot, then reference it within a script tag prior to creating any wddx-powered functions, like so:
<script src="wddx.js"></script>//< - - - making sure wddx.js is loaded...
<script>
    <cfwddx action="cfml2js" input="#UserInfo#" toplevelvariable="users">
    function getUserInfo(userid){
        ...(remainder of js)
   
  • When referencing query fields, the js is not case sensitive (a getField(1,'title') will work as well as getField(1,'TITLE') ); However, javascript itself IS case sensitive, so referencing the target span tag you must use the exact case of the ID (getElementById('title') is NOT the same as getElementById('TITLE') )
  • The WDDXRecordset object contains a zero-based array, meaning that the index number of the first row will actually be referenced as zero rather than one (getField(0,’fname’) retrieves the value for first name from the first record)
That's it!

Doug out 
Posted by dougboude at 7:49 PM | PRINT THIS POST! | Link | 3 comments
14 September 2006
Responding Appropriately to ColdFusion Questions
Reading Between the Lines
"Daddy, how do birds fly?" my five year old asks. "Well son, it has to do with Newton's third law of physics, which states
'For every action there is an equal and opposite reaction'. You see, the stream of air passing over the curved wing of the bird creates a stream on the underside that is traveling faster than the stream above, resulting in a lower pressure below which lifts the wing and thus, the bird." "Oh, I get it now daddy!"....not. Would we ever answer this way to our five year old? No way. Why not? Because we want to give them an answer that is actually useful to them, that they can add to their understanding and build upon later.

What troubles me, though, are those times that I see a definate lack in the way that questions are addressed by those in the ColdFusion community who actually do know the answers. It's as though the Knowledgable are so deep into their understanding of the topic that they have completely forgotten how to transfer that knowledge to those who are just starting out. In fact, sometimes the responses received to newbie-type questions are so laced with terms and phrases that have no place in the asker's understanding that the responses THEMSELVES generate questions on top of the original question. It can already be a rather long and painful trek up the J-Curve, depending on where one is starting from; but to receive answers that are so far above one's head can be a source of discouragement.

My point then: we should take the time and care to respond to questions in the context and in the perspective of the person asking it. If a 5 year old asks us a question about how birds fly, do we respond with vocabulary and explanations appropriate for a 4th year college engineering student? No, we don't, because we want the 5 year old to get the understanding they're seeking. Just because nobody in our community is 5 years old, though, doesn't mean they can automatically digest information to which they cannot relate.

Consider the following question and response (the one that made me feel the need to share my thoughts on this):

Question
"I am a newbie to reactor, and also to flex2.  I'm curious to know if others
are using FUSEBOX with reactor AND flex2, or if the use of flex2 makes
fusebox kinda unnecessary.  What is the best use/layering of these
frameworks?  Does flex become the controller and the view, thereby
eliminating the need for the MVC architecture of fusebox?  Is reactor simply
the model behind the entire flex application?"

Response
"Well, Reactor is the persistence mechanism for the model, so it is part of the model, but not the whole thing. You’ll probably want to wrap it with a service layer for any business rules and to provide an API, and you may or may not want to create a façade to that API that is Flex specific . . ."


Whiskey Tango Foxtrot??????


I'm sure the responder meant nothing but the best, and it is to their credit that they took the time out of their busy schedule to even draft a response; but I gotta say, that answer is completely useless to the person who initially asked the question. It's filled with acronyms and terms the user likely is unfamiliar with and it wrongly assumes that the asker has a level of comprehension of these concepts that is far greater than that which can obviously be seen in their question. I don't cast blame at those whose understanding is just so deep that they're miles and miles ahead of "The Rest of Us", but I would encourage them to be a little more thoughtful in their efforts to assist by remembering their audience; their audience being the person who asked the question.

So, how can those who possess the knowledge sought be even more helpful to the self-acknowledged newbie person? By taking a few moments to read between the lines. A lot can be derived, and easily so, about the asker's current level of understanding just by the way they phrase their question. Don't be oblivious to that, home in on it and let THAT be the context in which you respond. Let's take the preceding question as an example.

"I am a newbie to reactor, and also to flex2...." The individual is a self-proclaimed newbie, admitting quite humbly that they have just barely scratched the surface on these subjects and at best probably have only a rudimentary understanding of the concepts behind the terms. Duly noted.

"...I'm curious to know if others are using FUSEBOX with reactor AND flex2, or if the use of flex2 makes fusebox kinda unnecessary...." It's obvious that their understanding isn't all that deep, reconfirming what we already believed. They've probably read a few articles, looked through the code of a few tutorials...taken the time to try and get a grasp of what each of these items are and do and how they relate contextually to one another. But by the way the writer blatantly overlaps methodology with technology in an almost interchangable way further reinforces that the place they're coming from is one of elementary knowledge, at best, of the subjects they're asking about.

The remainder of the question does contain some buzzwords (MVC, model) used in an appropriate context. But describing Fusebox as an MVC architecture when it really is not (even though it could be used in that way) tells me that either the person doesn't understand what MVC really is, they don't understand what Fusebox really is, or they don't understand either one all that well. It's not really relevant which they do or do not understand; the point is that the question this person is posing is beyond their current understanding of the subjects they're asking about. That is a fact that should be recognized by the person who feels the desire to help this individual. Therefore the most useful response would first address the things this person is lacking in their foundational knowledge, either by taking the time to draft that information, point them to recommended reading material, and/or suggest some exercises or tutorials this person could do to ramp up their understanding. Only after the hidden questions are addressed should their actual question be addressed, if at all.

It should be said that some of which is "perceived" by reading between the lines may be incorrect; but better to err on the side of assuming too little than too much.

So what use is a response to a question for which the asker obviously doesn't even understand what they're asking? Not much use at all. My whole point with this post then is to do nothing more than encourage those who have the knowledge to answer a question to make sure they take the time to read between the lines just a little so that the answers given might truly be of more use to the person asking.

Doug out.
Posted by dougboude at 12:13 PM | PRINT THIS POST! | Link | 22 comments
08 September 2006
Database-Oriented Document Management
Nearly every time the question of storing documents or images in a database comes up, the answer is almost always "don't do it", and for very good reasons. There are those times, however, when it's very much the appropriate thing to do. In my case at my day job, we have multiple production servers that are maintained by another (very protective) team, and the method of load balancing in place is NOT conducive to keeping server content in sync. Therefore, when we want to post a new document or image on the web, it may very well be hours before said file is actually replicated to all production servers. In order to make an image or document immediately available, we decided that storing them within the database would resolve our challenge. Since the final solution did involve some research and a few fiery hoops, I figured I'd share it with the rest of the Ether  in case it might come in handy for someone else. Following is the general process and code snippets involved with making this happen.

Basic Process:
  • File is selected via a form within a document admin utility;
  • Selected file is read into binary and stored in a "Documents" table
  • Links to stored documents/images contain the UUID of the target document. Table is queried for that UUID, binary is retrieved from database.
  • File's binary is provided to a CFCONTENT tag, along with the mime type, and result is delivered to requestor.
Before we go any farther, please pay heed to these three items:

 1. All of the following code and snapshots are in regard to CFMX 6.1 and higher. The process described below is similar for CF 5, but there are some additional caveats and steps involved (Email me for particulars if you're still using CF5).

 2. You MUST allow the retrieval of BLOBs within your ColdFusion datasource where your document table lives, as in the following snapshot:
coldfusion dsn admin

3. Below is the script needed to create the sample table in MSSQL. The table used to store the documents should have a field of type IMAGE (if using MSSQL) in order to store binary data directly. If your database doesn't support a field type for binary data, then you'll need to create a field of type TEXT or its equivalent. Any binary data needing to be stored in a TEXT field will have to be BinaryEncoded/BinaryDecoded using Base64 as the second parameter in order to store and display it in this manner.

SQL Script:
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[docs]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[docs]
GO

CREATE TABLE [dbo].[docs] (
    [id]  uniqueidentifier ROWGUIDCOL  NOT NULL ,
    [filename] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [extension] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [filesize] [bigint] NULL ,
    [uploadedDate] [datetime] NULL ,
    [mimetype] [varchar] (75) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [mybinaryblob] [image] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[docs] WITH NOCHECK ADD
    CONSTRAINT [DF_docs_id] DEFAULT (newid()) FOR [id]
GO

On with the actual code then! Here are the details/snippets:

File selection
File selection is done via a form field of type "File", as in
<input type="file" name="myfile" size="50" />
<!---  --->
<!---  --->

Important: In the FORM tag, it is required that you set the ENCTYPE equal to "multipart/form-data", as in
<form action="<CFOUTPUT>#cgi.script_name#</CFOUTPUT>" method="post" enctype= "multipart/form-data">
<!---  --->
<!---  --->

Form Action code
Step 1 when processing a file upload is to UPLOAD it via CFFILE. This takes the file and places it on the server in a temporary directory designated by the webserver itself. This is also needful so that we can grab the file's metadata (file size, mime type, etc.).  The code to upload the submitted file looks like this:
<cffile action="UPLOAD" filefield="form.myfile" destination="#getDirectoryFromPath(getCurrentTemplatePath())#" nameconflict="OVERWRITE">
<!---  --->
<!---  --->

"form.myfile" is the NAME of the form field that was of type "File"; it should be given to CFFILE without pound signs. If we were to place form.myfile in pound signs (#form.myfile#), the UPLOAD process would be unable to locate the target file, so make sure you don't do that.

Once the file has been uploaded, we need to capture its metadata, which exists immediately after the CFFILE call in a structure called none other than CFFILE. Fancy that.
file metadata
CFDUMP of File Metadata

The recommendation is to capture a good portion of that information and store it along with the file's binary. Here we'll capture it to a structure for use later with the insert query, like so...
         <cfscript>
            stats = structnew();
            stats.ext = CFFILE.ClientFileExt;
            stats.name = CFFILE.ClientFileName;
            stats.subtype = CFFILE.ContentSubType;
            stats.type = CFFILE.ContentType;
            stats.size = CFFILE.FileSize;
        </cfscript>

Step 2 is to read in the binary of the uploaded file, like this:
<cffile action="readbinary" file="#form.myfile#" variable="vBin">
<!---  --->
<!---  --->

Notice in this case we aren't just providing cffile with the name of the form variable, we're giving it the actual path to the uploaded file (wrapping in pound signs).

At this point, we're ready to insert our file into the database.

<cfquery name="insertfile" datasource="#dsn#">
    insert into docs (filename,extension,filesize,uploadedDate,mimetype,mybinaryblob) VALUES (
    <cfqueryparam value="#stats.name#" cfsqltype="cf_sql_longvarchar" />,
    <cfqueryparam value="#stats.ext#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#stats.size#" cfsqltype="cf_sql_bigint" />,
    <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp" />,
    <cfqueryparam value="#stats.type#/#stats.subtype#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#vBin#" cfsqltype="cf_sql_blob" />
    )
</cfquery>

That's it for storage!

RETRIEVAL and DISPLAY
Retrieval is pretty straightforward, too. We're going to retrieve the particular record we're interested in from the database

<cfquery name="getFile" datasource="#dsn#">
    select id,filename,extension,mimetype,mybinaryblob from docs where id = <cfqueryparam value="#form.getfile#" cfsqltype="cf_sql_char" maxlength="36">
</cfquery>

and then feed the retrieved binary and mimetype to a cfcontent tag

<cfcontent type="#getFile.mimetype#" variable="#getfile.mybinaryblob#" reset="No"  >
<!---  --->
<!---  --->


A word on stored images
Images are treated a little differently primarily due to the fact that in most instances you don't just want the page's entire content to consist of one picture, but rather have it retrieved and displayed inline within an <IMG > tag. The only variation in this case is that you have to create an Image Display template that does exactly what the example above states, and use that template as your IMG source, like so:

<IMG SRC="myImageDisplayTemplate.cfm?imageID=11111">
<!---  --->
<!---  --->

(Ben has a more detailed post on the topic of retrieving and displaying images from a database, if you're interested)

That's pretty much it. I've boiled our document management system down as far as I am able, but you can grab this code HERE if you'd like to use it as a starter for a system of your own.

Here is the entire self-posting Form:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<cfparam name="mode" default="" /><!--- parameter indicating whether we're asking for a file, storing a file, or retrieving a file --->
<cfparam name="dsn" default="test"><!--- datasource name to be used in this template --->

<!--- ********************************** UPLOAD A FILE ************************************************** --->

<cfif mode is "upload">
<!--- process new file... --->
        <!--- first, upload it to the server --->
        <cffile action="UPLOAD" filefield="form.myfile" destination="#getDirectoryFromPath(getCurrentTemplatePath())#" nameconflict="OVERWRITE">
         <!--- Now, before we exectute another CFFILE call, grab the uploaded file's metadata for later use --->
        <cfscript>
            stats = structnew();
            stats.ext = CFFILE.ClientFileExt;
            stats.name = CFFILE.ClientFileName;
            stats.subtype = CFFILE.ContentSubType;
            stats.type = CFFILE.ContentType;
            stats.size = CFFILE.FileSize;
        </cfscript>
        <!--- grab the binary version of the uploaded file --->
        <cffile action="readbinary" file="#form.myfile#" variable="vBin">
        <!--- insert it into the database --->
<cfquery name="insertfile" datasource="#dsn#">
    insert into docs (filename,extension,filesize,uploadedDate,mimetype,mybinaryblob) VALUES (
    <cfqueryparam value="#stats.name#" cfsqltype="cf_sql_longvarchar" />,
    <cfqueryparam value="#stats.ext#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#stats.size#" cfsqltype="cf_sql_bigint" />,
    <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp" />,
    <cfqueryparam value="#stats.type#/#stats.subtype#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#vBin#" cfsqltype="cf_sql_blob" />
    )
</cfquery>
        <cfoutput><a href="#cgi.script_name#"?mode="">Do It Again</a></cfoutput>
        <!--- dump this file's metadata --->
        <cfdump var="#stats#">
       
<!--- *********************************** Retrieve a Stored File *************************************** --->   
   
<cfelseif mode is "retrieve"><!--- we're in 'retrieve a file' mode...' --->
        <cfquery name="getFile" datasource="#dsn#">
            select id,filename,extension,mimetype,mybinaryblob from docs where id = <cfqueryparam value="#form.getfile#" cfsqltype="cf_sql_char" maxlength="36">
        </cfquery>
        <!--- this is all there is to it to display a file stored as binary --->
        <cfcontent type="#getFile.mimetype#" variable="#getfile.mybinaryblob#" reset="No"  >
       
<!--- ************************************ Let the User Select a File for Upload or Retrieval ********** --->   
       
<cfelse><!--- show the user interface so they can upload a new file --->
    <cfquery name="getFiles" datasource="#dsn#">
        select id,filename,extension from docs
    </cfquery>
    <form action="<CFOUTPUT>#cgi.script_name#</CFOUTPUT>" method="post" enctype= "multipart/form-data">
        <input type="file" name="myfile" size="50" />
        <input type="hidden" name="mode" value="upload" />
        <input type="submit" value="upload" />
    </form>
        <hr>   
    <form  action="<CFOUTPUT>#cgi.script_name#</CFOUTPUT>" method="post">
        <select name="getfile">
            <cfoutput query="getFiles">
                <option value="#id#">#filename#.#extension#</option>
            </cfoutput>
        </select>
        <input type="hidden" name="mode" value="retrieve">
        <input type="submit" value="Retrieve File">
    </form>
</cfif>

</body>
</html>
Posted by dougboude at 4:57 AM | PRINT THIS POST! | Link | 10 comments
01 September 2006
DeoxyRiboNucleic LISTS (DNL)
I love lists. Although they *seem* to take a backseat to their apparently more elegant "collection brethren" Structure and Array, to me Coldfusion lists have a hidden potential rivaled only by that ultimate List of all Lists, DNA. Four individual components used to create patterns within patterns within patterns, and within those patterns the blueprints for an entire species and everything about it...it's just amazing to me. I have personally used Coldfusion lists in similar fashion (though much less glorious) and so wanted to share a little of that passion and my reasons for it, in the hopes that others too (if they don't already) might be able to appreciate the beauty of a List as I do.

Here's a scenario:

I'm tasked with writing a component to perform e-commerce transactions against one of several different (and as yet unknown) merchant systems. I do know that there are only a handful of transaction types that can be performed (Sale, Pre-Authorization, Capture, Void, or Credit), and that along with each of these transaction types there will be some arguments that are required and some that are not. I also know that each of these arguments will need some additional validation before being submitted to the merchant system, and every argument's validation needs could potentially be different. On top of that, because of the fact that we'll be adding in new merchant systems all the time, it's entirely possible that a whole new set of validation rules could be needed tomorrow in addition to what's already in existence. And to top it all off, because the number of arguments could change overnight, I'm going to rely on the receipt of an argumentCollection rather than hard code my arguments via CFARGUMENT.

I actually did have this scenario a couple of years back, and although there are always many approaches that can be taken to accomplishing a task, I chose to utilize a List to hold my argument validation rules. Why a list? Because since I was a contractor, I knew I wouldn't be around the next time the rules changed, and being the nice guy that I am, wanted to make it as simple as possible for the person who would come after me. By encoding my validation rules for each merchant system within a List (just like a strand of DNA) and by receiving my arguments via a collection, the next programmer only needed to edit one line of an entire CFC in order to make their changes.

Here is an example of validation rules encoded within a List:

<cfset var validationRules = "TransactionType~Always~L|S-A-C-V-CR,CCNum~S-A-V-CR~CC,ExpDate~S-A-V-CR~D,Amount~S-A-CR~N|0,RefNumber~C-V-CR~T|7" />
<!--- --->
<!--- --->


In essence, just like DNA, it is a series of nested lists. Here is the key for deciphering the List:

  • Outer list: [Argument and associated rules],[Argument and associated rules], etc. delimiter ","
    • Argument and associated rules: [Arg Name]~[required when]~[validation] delimiter "~"
      • Required when: [Always||S||A||C||V||CR] delimiter "-"
      • validation: [validation type | validation param] delimeter "|"
        • validation type: [L||T||N||D||CC]
        • validation param: [[list of values]||[integer]]

In the sample list then, we can see for example that the argument "TransactionType" is always required and its value should be any of "S","A","C","V",or "CR".  The argument RefNumber is only required when the transactiontype is "C","V", or "CR", and when it is required should be a text value with length of at least 7 characters. You get the picture. SO then when our merchant system adds a new transaction type, such as "Reversal", we will add the letter "R" to represent this transaction type to any of the existing arguments that will be required for a Reversal. We'll then add any additional arguments with the appropriate values. All done in a single variable within the CFC.

What do we do with such a list? What beast is it that can consume it and actually use it? Well, You write yourself a method that loops through it and, one major list item at a time, interpret whatever rules have been defined and apply them. I've created a template and cfc for demonstrating this in action.

Step 1: template instantiates Payit.cfc, sets up an argument collection, and calls the "DoTransaction" method, passing in the arg structure.
<cfscript>
    argstruct = structnew();
    argstruct.TransactionType = "CR";
    argstruct.ccnum = "423339010000930";//cc number is only 15 characters...should be 16
    argstruct.expdate = "05/01/08";
    argstruct.amount = "35.67";
    argstruct.refnumber = "0098345";
    objPay = createobject("component","Payit").init();
    results = objPay.DoTransaction(argumentcollection=argstruct);
</cfscript>
<cfdump var="#results#" label="Results">

Step 2: "DoTransaction" first passes the incoming argument collection to the "validateArgs" method.
<cffunction name="DoTransaction" access="public" returntype="struct" output="false">
    <cfset var incomingArgs = arguments />
    <cfset var results = structnew() />
    <cfset results = validateArgs(incomingArgs) />
    <cfif results.error IS "[NONE]">
        <!--- Do a transaction... --->
    </cfif>
    <cfreturn results />
</cffunction>   

Step 3: "validateArgs" performs validation and passes a structure back to "DoTransaction" containing the results. (view the entire method at the bottom of this post)

Step 4: DoTransaction either performs its transaction, or it halts processing. In either case, it returns the validation structure to the calling page where it is output. In our example here, the credit card number being submitted was too short, and thus the following response:



Conclusion
Even though my example of using a list to encode detailed validation rules was very much geared towards an e-commerce example, I'm confident that one could find other uses of "ColdFusion DNA" by applying just a little bit of imagination and creativity. In any event, I recommend at least considering a list rather than automatically assuming a structure or array is the best fit every time.

Doug out.

[Download template and CFC]

validateArgs Method:
    <cffunction name="validateArgs" access="private" returntype="struct">
        <cfargument name="args" type="struct" required="true" />
        <cfset var validation = structnew() />
        <cfset var mode = "" />
        <cfset var currlist = "" />
        <cfset var validationRules = "TransactionType~Always~L|S-A-C-V-CR,CCNum~S-A-V-CR~CC,ExpDate~S-A-V-CR~D,Amount~S-A-CR~N|0,RefNumber~C-V-CR~T|7" />
        <cfset validation.success = "true" />
        <cfset validation.error = "[NONE]" />
        <!--- Since everything will always hinge upon the existence of the TransactionType argument, we check for it first thing, stopping if it isn't present.' --->
        <cfif not structkeyexists(args,"TransactionType")>
            <cfset validation.success = false />
            <cfset validation.error = "Transaction Type was required but not passed in" />
            <cfreturn validation />
        <cfelse>
            <!--- grab value of the transaction type. It may not be a valid value, but we'll find that out soon enough' --->
            <cfset mode = args.TransactionType />
            <!--- stuff our mode into the validation structure for troubleshooting purposes --->
            <cfset validation.mode = mode />
           
            <cfloop list="#validationRules#" index="i" delimiters=",">
                <!--- if this item is required for our current mode, make sure it exists... --->
                <cfif listgetat(i,2,"~") IS "Always"><!--- if this argument is always required, check for its existence --->
                    <cfif not structkeyexists(args,listfirst(i,"~"))>
                        <cfset validation.success =  "false" />
                        <cfset validation.error = listfirst(i,"~") & " was required but not passed in." />
                        <cfreturn validation />
                    </cfif>
                <cfelse><!--- our item is only required *sometimes*... --->
                    <cfif listgetat(i,2,"~") IS NOT "">
                        <cfif listfind(listgetat(i,2,"~"),mode,"-") neq 0><!--- if the current mode is in our list of required modes, make sure this arg is present... --->
                            <cfif not structkeyexists(args,listfirst(i,"~"))>
                                <cfset validation.success = "false" />
                                <cfset validation.error = listfirst(i,"~") & " was required but not passed in." />
                                <cfreturn validation />
                            </cfif>
                        </cfif>
                    </cfif>
                </cfif>
                <!--- if this item needs requires that the argument's value be validated, do so' --->
                <cfif listlen(i,"~") eq 3><!--- a third item indicates some validation needed --->
                    <cfset currlist = listgetat(i,3,"~") /><!--- grabbing the validation portion of our item --->
                    <cfif listfind(listgetat(i,2,"~"),mode,"-") neq 0><!--- if this was a required item in the first place... --->
                        <cfswitch expression="#listfirst(currlist,"|")#"><!--- perform the needed validation --->
                            <cfcase value="L"><!--- value must be in a specified list of values... --->
                                <cfif listfind(listlast(currlist,"|"),args[listfirst(i,"~")],"-") eq 0>
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " contained an invalid value." />
                                    <cfreturn validation />   
                                </cfif>
                            </cfcase>
                            <cfcase value="T"><!--- value must be text of at least so many characters... --->
                                <cfif len(args[listfirst(i,"~")]) LT listlast(currlist,"|") >
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " was not at least " & listlast(currlist,"|")  & " characters in length."  />
                                    <cfreturn validation />   
                                </cfif>
                            </cfcase>
                            <cfcase value="N"><!--- value must be a number greater than x... --->
                                <cfif not isnumeric(args[listfirst(i,"~")]) OR args[listfirst(i,"~")] lte listlast(currlist,"|")>
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " is not a numeric value or is less than or equal to " & listlast(currlist,"|")/>
                                    <cfreturn validation />           
                                </cfif>
                            </cfcase>
                            <cfcase value="D">
                                <cfif not isDate(args[listfirst(i,"~")])>
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " is not a valid Date."/>
                                    <cfreturn validation />               
                                </cfif>
                            </cfcase>
                            <cfcase value="CC"><!--- value must be a credit card number ... --->
                                <cfif len(args[listfirst(i,"~")]) NEQ 16 ><!--- this test done purely for simulation. actual cc validation is more involved and complex. --->
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " was not a valid credit card number."/>
                                    <cfreturn validation />   
                                </cfif>
                            </cfcase>
                            <cfdefaultcase><!--- the specified validation rule isn't accounted for! throw error' --->
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " was given a validation code that was not recognized."/>
                                    <cfreturn validation />   
                            </cfdefaultcase>
                        </cfswitch>
                    </cfif>
                </cfif>
            </cfloop>       
        </cfif>
        <cfreturn validation />
    </cffunction>
Posted by dougboude at 4:32 PM | PRINT THIS POST! | Link | 0 comments
Fishtank
a children's story

One day a man caught two little fishes while hunting for crawdads in a small Texas stream with his two children. “Come see what I caught!” he called to Lilly and Brandon. They both looked up from their own crawdad hunt, and wading out of the creek and onto its rocky bank, ran to where their father was. “Come see,” he said again, and the two slowly waded back out to look into the plastic cup their dad held. “Wow!” they said at once while peering into the cup, their excited eyes discerning the two small fishes swimming in the murky water there. “What are they?” Brandon asked inquisitively. “Texas cichlids,” his father quickly replied, “they were hiding under a rock.” “How do you know they’re cichlids?” asked Lilly. “See the little black stripes on their backs?” said dad, “That’s how you know it’s a baby cichlid.” Both children were still watching the little fish swim around and around. “What are we gonna do with them?” Lilly asked. “Well” said dad, “I reckon we can take them home and put them in the big tank. I’ve been looking for a reason to get it cleaned out and set up, and I think it’ll make a fine home for these two little guys.”

 They gathered their gear and made their way up the steep creek bank to where the Jeep was parked. Brandon carried the seine net, dad carried the orange plastic bucket that had the crawdads they had caught in it, and Lilly carefully carried the white plastic cup, making sure that none of the water spilled. Dad let Lilly carry the cup in her lap on the way home, too, showing her how to hold it up when they went over bumps so that no water splashed out.

Once home the kids couldn’t wait to get the two little fishes into their new home, but dad said that they would have to stay in the small tank until tomorrow. “When can we set the big tank up, dad?” asked Lilly. “Tomorrow’s Saturday,” dad said, “so we’ll make it our project of the day. For now, go ahead and put them into the small tank with the guppies. We’ll give them a little breakfast in the morning and then get started on their new home.” Lilly and Brandon couldn’t wait till tomorrow, and thought about their project even as they lay in bed that night.

 The following morning, after everyone had gotten ready for their day and breakfast had been eaten, Brandon asked if he could give the cichlids some breakfast, too. “Sure” said dad. “Just take some of the flake food we have and crush it up real good between your fingers over the water. If they’re not too shy, they’ll come up and get some of it.” Brandon followed his dad’s instructions. The guppies, who were quite used to life in the small tank, immediately began sucking the tiny pieces of fish food off of the water’s surface. The little cichlids, however, just stayed near the bottom in a back corner of the tank, apparently quite shy and skittish. “They’re not eating, Dad” Brandon said. “Well that’s natural, son. They’re so used to having everything trying to eat them that they’re too afraid to come up and eat. They’ll get used to us soon enough though; I ain’t never seen an animal that would let itself starve to death.”

 While Brandon was putting the fish food away, dad and Lilly began gathering the things they would need to set up the big tank. First, they went into the garage and got out all of the large rocks that had been in the tank from before when dad had Oscars, and carried them outside and onto the front porch. “What’s this?” Lilly asked when dad handed her something black and plastic. “That’s a filter” he told her. “It hangs on the outside of the tank and sucks in the dirty water, then lets the clean water fall back in like a waterfall.” “Aw, cool”, said Lilly. Brandon joined them now and carried out the items dad had given him. He didn’t recognize any of them except for the green fishnet, and so asked him what they were. Laying them all out neatly on the front porch, dad began to point out items and explain what their role would be in the tank. “That large flat plastic thing with all the holes in it is called an Undergravel Filter, and it helps keep the tank’s ecosystem working.” Lilly and Brandon grew puzzled. They had heard the word ecosystem before, but had no idea what it meant. Dad set down the bucket of brown gravel he was carrying and began to assemble some of the pieces of the filter, elaborating on what he had already said. “See, we put these fat plastic tubes here in the undergravel filter, then we’ll run some air tubing down into them like this, with a bubbler inside. What happens is that the bubbles rushing up the tubes will actually be sucking fresh water down into the filter.” Dad made a gesture with his hands, showing the water flowing downward toward the plastic grating. “What that does is make sure that the bacteria living in the gravel…oh, forgot to tell you that we’re going to bury this under the gravel first…anyway, the fresh water flowing down through the gravel will keep the bacteria that live there healthy.” The children were very satisfied at the explanation dad was giving them, but they still had more questions. “Well, what’s a bacteria?” Lilly asked. “Ah, excellent question, Pumpkin”, dad said. “Bacteria are teeny tiny little guys whose job it is to clean up all of the things that could dirty up a tank, like food that the fish don’t eat and the fish’s bathroom.” That answer seemed to satisfy the kids, so dad disassembled the filter and laid the pieces back out again. “Alrighty now, it’s time to clean these things up real good so we don’t put anything dangerous back into the tank. Wouldn’t want the little guys to get sick and die.”

One piece at a time dad, Lilly, and Brandon scrubbed them with old toothbrushes and salt water from a blue plastic bowl. After a piece was scrubbed, dad rinsed it off and laid it on a towel in the sun to dry. “What about the gravel, Dad?” Brandon asked. “How are we gonna clean that?”. “Not to worry, son” said dad, “watch this.” Dad uncoiled the green garden hose a few turns off of its holder on the wall of the house and put the nozzle down into the bucket full of gravel. “Can you turn the water on for me Lilly?” Dad asked. Lilly ran to the spigot and slowly opened the valve until he told her to stop. The kids watched as the bucket filled up and up and finally started overflowing. Once it was overflowing, dad pushed his arm down into the gravel as far as he could and started swirling it around and around. Immediately dirt and debris started rising and the water became muddy. The constant flow of the hose, however, pushed the dirty water out and out until finally, in just a few minutes, the water ran clean no matter how much dad swirled the gravel. “Alright, it’s all clean and ready for the tank!” dad exclaimed. He put his hand over one side of the bucket and tipped it that way, letting the water run out while keeping the gravel in.

With all of the parts and the gravel and rocks now clean, they carried them into the house and laid them in front of the fifty five gallon tank that sat on a black stand in the dining room. “Whew!” said dad, “you guys ready for some lunch yet?” Both Lilly and Brandon had worked up quite an appetite, and so dad made PBJs for everyone. The kids had a sandwich in one hand and a glass of milk in the other while they stood in front of the small fish tank and watched the tiny cichlids. The kids could see them clearly now in the clean water of the little tank. They were a grey color, like the rocks in the stream they had found them in, and they had tiny black stripes like a tiger. The most prominent marks on them, though, were two large black spots, one near their tail, and another in the middle of their bodies. To the kids, the fishes seemed more at ease now. They weren't nearly as skittish, and they were exploring the dimensions of their temporary home, picking for food amongst the white gravel. “Dad, they’re not afraid anymore!” Brandon happily exclaimed. “I didn’t think they would be for long,” dad said; “fish adapt pretty quickly to their environment.”

 After they finished their lunches, they set to work putting everything in place for the little cichlids. First, dad put in the undergravel filter and set it up just like he had shown them on the front porch. It covered almost the entire bottom of the tank, and at each end a thick clear plastic tube stuck straight up out of it. Next, they took the bucket of gravel and cupful by cupful they put it in on top of the filter. “We need to make sure there’s a good thick layer of gravel over this,“ dad said, “’cause that’s where the bacteria’s home will be.” With most of the gravel in place, dad started asking for some of the larger rocks. “Hand me that one with the big hole in the middle,” he asked Lilly. She handed it to him and he carefully set it into one corner of the tank, pushing it down into the gravel. Several rocks later dad had created what looked like a small cave in one corner of the tank, with other rocks of curious shapes protruding here and there throughout the rest of the space. “Hey!” the kids said, “That looks like a cave!”. “It is” said dad, “’cause those little guys are gonna need a place to go where they can feel safe; it’ll help them be happier and healthier.” While dad was adjusting some of the other rocks and pouring the remaining gravel here and there to form decorative mounds and slopes, Brandon noticed one more item still left on the towel. “You forgot something, Dad” Brandon said. “Oh yeah” replied their father “the other filter”. Dad opened the door underneath the black aquarium stand and took out a plastic packet. He opened it up and took out something flat and fuzzy looking. “This is what lets this filter keep the water clean”, he said. “All the dirt and other things that come in with the dirty water get trapped right here and only clean water comes out.” “Cool” Lilly said. Dad put the fuzzy thing inside of the filter, assembled the rest of the pieces that went with it, and hung it on the outside of the tank just above the cave. “Well, what do you think we should do now?” dad asked Lilly and Brandon. “Ummm…put the water in?” they said. “Absolutely right!” said dad, and they took the empty gravel bucket into the kitchen and set it in the sink. Dad started running water in it, and while it was filling up, took a small yellow bottle of something and put a few drops into the bucket. “What’s that?” the kids asked. “This will take out anything in this water that’s poisonous to the fish,” he said. When the bucket was full, dad turned off the water and carried it to the tank, where he ever so slowly and carefully poured the water in, making sure that it landed directly on a flat stone he had placed near the end opposite the cave. “I’m pouring it in like this so that we don’t mess up the beautiful landscape we just made.” It seemed like forever before they had the tank full, but it had really only been twelve buckets. The tank was already looking like a cool, magical place to Lilly. The light through the water’s ripples made pretty designs over the gravel and rocks, and let just a little bit of light into the cave. Dad worked inside the area under the tank’s stand for a few minutes, until suddenly to the kids’ pleasant surprise, the soft hum of the pump was heard and silver bubbles streamed upwards in the tubes like pearls. “Oh!,” the kids exclaimed. Brandon loved the sound that the cascade of popping bubbles made; he just stared as they did so, trying to see patterns in the way they came out of the airstones at the bottom of the tubes. At this point dad stopped and sat in front of the tank, Lilly joining him on his lap. None of them could look away as the pump’s hum and the bubbles’ silvery streams and gentle pops made each of them smile. “Oh, forgot one” dad said, and reached inside the stand door once more. At once the soft sound of something clicking underwater was heard as the hanging filter came to life. They heard the water in the filter rising, rising  rising until finally a lovely flowing fall of water fell from its perch and into the tank. “Now this is how to really relax,” dad said as they sat their admiring their handywork. “Sure is” said Brandon, with Lilly echoing last. “So when can we put the fish in, dad?” Lilly asked excitedly. This was, after all, the moment they had been waiting for. “Well, let’s just let everything run for a few hours first, just to make sure the water is good and clean when we put the little fellas in.” The children were perfectly content with that answer, and they both sat there in front of the tank for another half hour, imagining that they were little fishes, riding down down down on the waterfall’s deluge, going in and out of the dark but inviting little stone cave. Before they knew it, it was time for their baths and dinner so they left the tank to run in peace. Even from another part of the house, though, they could still hear the soothing stream of the bubbles and loud trickling sound of the waterfall; even without being able to see the fishtank, just the sound of it made them feel happier.

 With their dinner finished, their teeth brushed, and their pajamas on, dad called Lilly and Brandon into the dining room. “Okay,” he said smiling, “I think it’s time now.” He picked up the green fish net from beside the little tank and slowly and gently put it down into the water. The guppies quickly hugged the surface of the water and moved to one side, while the little cichlids huddled at the bottom in one corner. Dad skillfully moved the net over the little fish, not scaring them at all, until he was able to lift them slowly out of the water. Only after leaving the water did they begin to flip and flop, but not a moment later dad had them in the big tank and swimming free again. The kids sat down in front of the tank, as did dad, and watched to see what the little fishes would do. Immediately they both made their way to the bottom and, staying close together, just hovered there, not daring to move an inch. “They’re getting their bearings,” dad said, “trying to figure out where they are, where the dangers are, and what to do next. It won’t take them long though, just watch.” Almost on dad’s cue, the two little fish drifted quickly over toward the cave dad had created and slipped into its dark recesses. From the right angle, the kids could see their tiny eyes peering out from the shadow’s edge, their little mouths and gills pumping water fast. “I’m so glad you made them a cave, dad” Brandon said. “You were right, it does make them feel safe.” For another half hour they all sat there, just watching the beautiful, disorderly stream of bubbles and listening to the pump’s hum and the sound of clean water cascading into the tank. Finally dad said it was time for bed and told the children that tomorrow sometime they’d see if they could get the little guys to eat something. Happy with anticipation and very pleased with themselves for making such a wonderful and safe home for their new little fishes, Brandon and Lilly went to bed.

Posted by dougboude at 1:34 AM | PRINT THIS POST! | Link | 1 comment