List Driven Dynamic Fields
Create Dynamic Checkboxes, Select Boxes, and Radio Groups Without Using A Database
When I was a rookie Coldfusion programmer, I was excited about the ability of Coldfusion
to dynamically drive select boxes, radio buttons, and check boxes. I used every opportunity that presented itself to
create tables which held descriptions, values, variable names, and such. Then I would create a query, loop over it,
and create my controls. I believe my rationale made sense. It would be easier to add another record to the table than
it would be to drag up the template and create a new input object.
Well, time has a way of demonstrating the effectivness of ideas. The idea of table driving everthing became burdonsome.
Any time I wanted to add a new item to a form, I had to open the database, find the table, enter the record, and make
sure I had the correct sort-order entered (every list had to have a sort-order column in order to allow for future addition placement).
If I wanted to change a description, value, etc., I had to open up the database, find the table, find the record, then make
the edit. Then, I had the repeat the process two more times for the practice site and the live site.I could have written
interfaces for all of these tables as well, but I think we all know how tedious that can become. Finally, my database
was getting littered with lots of little tables whose purposes were easy to forget (a discerning reader might ask why I didn't
just create one table to rule them all; I tried, but the number of times that a set of controls needed a special field kept
making table upkeep frustrating).
After about a year and a half of table driving everything, I got tired of the process. Fortunately, a new solution dawned upon me. Others may
have used this idea before, but I had never seen it. I spend a lot of time scouring the net for good ideas, too.
When I shared the idea at a local PUG meeting, no one had ever used it before and many seemed intrigued by the idea.
That was several years ago. I finally got the motivation to post the idea.
The idea came to me when I was working with some lists. I can't remember what I was working on specifically, but an
epiphany dropped like a bombshell on my head. What about list within lists? The only problem I could see at first was
a lack of delimiters. However, there are plenty of characters that most people don't use. In fact, special characters
can be used (you can use #chr(8)# as a delimter if you so choose). If you look at the following list, you will see what
I mean when I say a list within a list.
<cfscript>
lst01 = "1^Is it hard for you to walk^PF01|";
lst01 = lst01 & "2^Is it hard for you to run^PF01|";
lst01 = lst01 & "3^Is it hard for you to play sports or exercise^PF01|";
lst01 = lst01 & "4^Is it hard for you pick up big things^PF01|";
<cfscript>
You can break the list down into parts. The first line contains: 1^Is it hard for you to walk^PF01|. In this
case, "|" is the delimter for the main list while "^" is the delimiter for the list within a list. The inner
list contains three parts: value, description, and variable name. The main list contains four items. A
programmer can use code similar to the following to loop over the whole list and assemble a set of check boxes.
<cfloop list="#lst01#" index="i" delimiters="|">
<cfscript>
intNum = listgetat(i,1,"^");
strDescript = listgetat(i,2,"^");
strVar = listgetat(i,3,"^");
strVal = evaluate("qryTest.#strVar#");
</cfscript>
<input type="Checkbox"
name="#strVar#"
value="#intNum#"
<cfif intNum is strVal>checked</cfif>>#strDescript#<br>
</cfloop>
The code is not revolutionary. It is pretty straight forward. The only thing remotely tricky is the evaluate
function. It is necessary in order to get the values form the query that is returning data to a form.
For formatting purposes, a programmer could throw some TD's and TR's in to make uniform alignment.
Now, the process has been explained. Is it really any better than table driving dynamic fields? I think so.
Here's why:
-
The code is more encapsulated. A programmer does not have to go to a database to make change or additions.
All changes are effective on remote sites as soon as the edited page is pushed to its new location.
This makes debugging easier as well.
-
It is easy to change the list. If an item is not needed, drop // in front of its line in the cfscript tag. It becormes
disabled. No needed for a "disabled" field in a table (you should never delete a definition record from a table because of
data that might be stored in other tables). Want to add an item, just start typing.
-
The code can be reusable. Drop the definition list in a template of definitions. Use cfinclude to bring in the definitions
when needed (this doesn't really break the encapsulation thing because the definition template should be right there
on your server with the template using it).
-
The template is reusable in other projects. Save the page in another project and your done. No
need to create tables or definitions.
-
It's easy for other programmers to follow your code. Everything they need is right there in front of them. It also
makes it easier on the original programmer when he or she hasn't seen the code in 6 months and a "rush" change needs
to be made.
I did not spend a lot of time thinking about negatives for this idea. I like this method and plan on using it for a
while so I just wasn't interested in the other side of the coin.
I am sure people can find numerous negatives. One that comes to mind would be returning data to another form. For example,
the database returns a value of 1 for a question (I am not talking about the descriptions table but the forms table).
Programmers need to match this 1 up with a specific description sometimes for various reason (reports, etc.). An inner join can be used if the controls are table driven. Speed may be an issue. I have
never benchmarked this process. I have heard that arrays run faster in Coldfusion MX. I suppose this code could be converted
rather easily to an array format. I just didn't want to deal with array lengths and loops and at the time, I am fortunate enough to work on low traffic sites..
The following is a code example. A reader can cut and paste this in a template, save, and it should run right away.
<!--- Make a list of data for a query. --->
<cfscript>
lstValues="1,2";
</cfscript>
<!--- Convert the list to a query in order to make the example more realistic. --->
<cfset qryTest = QueryNew("PF01,EF01")>
<cfset temp = QueryAddRow(qryTest)>
<cfset Temp = QuerySetCell(qryTest, "PF01", listgetat(lstValues,1))>
<cfset Temp = QuerySetCell(qryTest, "EF01", listgetat(lstValues,2))>
<!--- List to generate check boxes --->
<cfscript>
lst01 = "1^Is it hard for you to walk^PF01|";
lst01 = lst01 & "2^Is it hard for you to run^PF01|";
lst01 = lst01 & "3^Is it hard for you to play sports or exercise^PF01|";
lst01 = lst01 & "4^Is it hard for you pick up big things^PF01|";
lst02 = "1^Do you feel scared^EF01|";
lst02 = lst02 & "2^Do you feel sad^EF01|";
lst02 = lst02 & "3^Do you feel mad^EF01|";
lst02 = lst02 & "4^Do you have trouble sleeping^EF01|";
</cfscript>
<cfoutput>
<div align="center">
<form name="frmTest">
<table>
<tr>
<td>Question 1</td>
</tr>
<tr valign="top">
<td>
<cfloop list="#lst01#" index="i" delimiters="|">
<cfscript>
intNum = listgetat(i,1,"^");
strDescript = listgetat(i,2,"^");
strVar = listgetat(i,3,"^");
strVal = evaluate("qryTest.#strVar#");
</cfscript>
<input type="Checkbox"
name="#strVar#"
value="#intNum#"
<cfif intNum is strVal>checked</cfif>>#strDescript#<br>
</cfloop>
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>Question 2</td>
</tr>
<tr valign="top">
<td>
<cfloop list="#lst02#" index="i" delimiters="|">
<cfscript>
intNum = listgetat(i,1,"^");
strDescript = listgetat(i,2,"^");
strVar = listgetat(i,3,"^");
strVal = evaluate("qryTest.#strVar#");
</cfscript>
<input type="Checkbox"
name="#strVar#"
value="#intNum#"
<cfif intNum is strVal>checked</cfif>>#strDescript#<br>
</cfloop>
</td>
</tr>
</table>
</form>
</div>
</cfoutput>
Now, if you have stuck with this so far and you examined the code above carefully, you may be asking yourself why I included the variable name in the inner list.
It would not have been a big deal to hard code it in the example above. I will answer this question with an example:
<cfset lstMaster = "Question 1~lst01|Question 2~lst02|>
Hopefully, you read that and a light went off. If not, don't worry about it. You'll get it sooner or later.
I did not expand the writing to cover select boxes or radio button groups. The idea is easily transferable to other controls without much work.
Disclaimer: The terms "Best Practice" and "Coding Standards" are subjective. This means that what is a "best practice" or a "standard" for one may be a pox for another. I am
in no way claiming that anyone should use the above code. It is just an example of something that I find useful.
As far as I know, this work is original. I have not intentionally copied anyone's work.