Richard Davies wrote: The UK has a good crop of technology pioneers in cloud computing - for example ElasticHosts, FlexiScale, Flexiant, OnApp - and also some strong government initiatives such as G-Cloud.
We will have to see whether this kind of technical leadership converts into swift mass-market adoption or not.
One of the great things about developing for the Web is the fun factor. While our grimmer, more serious IT cousins are exiled in lands populated by such dread monsters as COBOL and FORTRAN, and must do daily battle with the demon, MainFrame, our lot is far more pleasant.
This article, while it will do nothing to stave off global warming, will give you the tools you need to increase your own fun factor, while offering your clients a very nice interactive feature for their web sites and applications. So, after a moment of silence for our ill-starred comrades...let's get this party started!
Overview: What We Want To Accomplish You've probably seen those small, interactive quizzes on such sites as ESPN. They pull the reader in by asking their opinion on "Who do you want to break Roger Maris' homerun record?" Then they offer a choice of radio buttons corresponding to the leaders in the homerun derby.
Answer the question and you'll get to see a colorful bar chart showing how others have answered.
This seemingly trivial mini-application can offer real value to your clients. Unlike page-long forms that site visitors deplore, these "quizzles" are quick to take and it's fun to see how others have responded. Using these quizzles over time, your clients can build up a database of how their users feel and think. Such knowledge of their customers and prospects can only help your clients in the fiercely competitive world they inhabit.
Coding For Reuse and Maintainability In our shop, we constantly try to make our code reusable and maintainable. It turns out that these two go nicely together: code that is written to be maintainable lends itself to reusability. It also works the other way around.
Your quizzle application is likely to be a hit with your clients. They will want to be able to maintain the application by adding or deleting quizzles and by setting in what order quizzles are displayed. A great selling feature of our quizzle app is your clients' ability to do this themselves, without incurring additional or continuing costs. If we build this right, everyone wins: you'll have a popular app that you develop once and sell many times and your clients buy the applet once and maintain it themselves.
The Database We will use a SQL database to store information about the quizzle itself and user responses to each quizzle offered. For illustrative purposes, we'll use Microsoft Access; you will probably require something more robust when actually deploying your quizzle app. The structure looks like this:
Administrative form Since users will be administering the quizzle app long after you've made your first million; you'll need to provide a way for them to perform administrative functions. These are:
This is a pretty straightforward form. The only thing of slight interest occurs where we give the user the ability to sort by various table columns. Prior to implementing this, we were getting many requests to download tables into a spreadsheet format. In speaking with and observing users, we found that adding this small feature reduced spreadsheet format requests by over 70%.
We accomplish this by using the <cfparam> tag to create a variable called "orderField". Why not just use <cfset> to set the value? We only want to initialize this variable -- that is, assign it a default value. When the user clicks on the heading of any of the other columns, a new value will be assigned to it, overriding the default value. Whereas <cfset> would always assign a value to this variable, <cfparam> assigns one only if no value exists.
Since we're committed to code reuse, we'll create a single form that will handle both edits and additions to the database.
Quizzle Entry form -- in add mode Note that the same URL is invoked to add a new quizzle and to edit an existing one. If you haven't created double-duty forms before, you may want to spend a few minutes looking over this over.
Same form -- in edit mode The biggest problem presented by double-duty forms is that edit forms need to present the information stored in the database for this record while new entry forms have no existing information.
We know that if we will be editing a record, the record's primary key will be passed to us, usually as a URL or a hidden form variable. Once we receive this, we will query the database for the existing information about this particular record.
Let's assume we call this query "getQuizzle." Once this query is run, we will get back a number of variables, all with the "getQuizzle" prefix. We will use the values in these variables to fill in the edit form field values. The variable "getValue.qQuestion" provides us with the information needed for the qQuestion edit field. We will set the "value" parameter of the <input> tag to "#getQuizzle.qQuestion#". The same is true for all the edit fields, their "value" parameters are set to the query variables.
What happens if we are asked to process a new entry? In that case, we will have no primary key passed. There will be no query to run and no query variables. Yet, we have already set the "value" parameters of our form input tags to reflect the results of a run query.
The <cfparam> tag makes this easy. We can initialize these variables to an empty string, even before we know whether we are in "add" or "edit" mode. If we are adding a new entry, we simply have an empty string in our input fields. This causes no problem for inserting new records. Where we are asked to edit an existing record, the value of the "getQuizzle" variables will be overridden when the query is run. Now, regardless of the mode our form is in, no code will break.
Finally, we must give the administrative user a chance to delete quizzles. The action for this is simple enough and is called from quizzleAdmin.cfm when the administrator clicks the "Delete" button.
deleteQuizzle.cfm
<!--
Filename: deleteQuizzle.cfm
Date: 9/6/98
Created by: hal.helms@utaweb.com
Expects: qID
-->
<cfquery
datasource="quizzle"
name="deleteQ">
DELETE * FROM Quizzles WHERE qID = #url.qID#
</cfquery>
<html>
<head>
<title>A UTA IS Web application</title>
</head>
<body bgcolor="white">
<font face="arial, helvetica, sanserif" size="2">
Alas, poor Quizzle -- I knew him well.
</font>
</body>
</html>
You can decide for yourself whether you wish to present a "nag" screen that asks the user "Are you really, really sure you want to delete this quizzle? Have you considered counseling?"
Of Quizzles and Custom Tags Quizzles are meant to be easy to set up and use. Ideally, we want to keep all the fussy code away from the page that holds our quizzle and ColdFusion offers us a wonderful means for doing just that. Custom tags are an ingenious invention that allows us to take ColdFusion code -- even whole ColdFusion pages -- and treat them as if they were one more ColdFusion tag.
If you haven't used custom tags before, I hope this experience will convince you how indispensable this mechanism is. Structurally, they fall somewhere between functions and objects, delivering a much higher level of encapsulation than functions though falling short of objects. And while the initiation into true object oriented languages involves the use of candles and requires you to pledge your first-born, custom tags are much easier to master. In fact, we will create one now. The entire code needed to generate quizzles -- that is, the code you must place on the calling page -- is:
<cf_quizzle quizzleDSN = "quizzle">
This tells ColdFusion to search for a page called "quizzle.cfm" and then execute that page. The snippet "quizzleDSN = quizzle" is called an attribute. We'll explore how these work in the next section. First, though, you must know where ColdFusion looks for custom tags. First, it searches the current directory -- that is, the directory the calling page is in. If it fails to locate the file here, it looks in a directory called Custom Tags, located directly under the main ColdFusion directory.
This mechanism makes it quite easy to have two levels or types of custom tags. You can create some code that is specific to your page or project. You'll probably want to put such code in the current directory. Other code will be much more generic; in this case you can place it in the Custom Tags directory where it will be available to all applications.
Now let's take a look at the code that's being called as a custom tag: quizzle.cfm.
Quizzle Code Just as we created a form that did double duty, handling both new entries and edits to the database, our quizzle code will serve two purposes. First, we must display the initial quizzle question, as well as the possible responses. Once this is done, and the user has taken the plunge, we must serve up a chart showing how others have chosen. If the quizzle is fact-based rather than opinion-based, we may also want to provide the correct answer.
quizzle.cfm
<!--
Filename: quizzle.cfm
Date: 9.3.98
Created by: hal.helms@utaweb.com
Expects: attributes.quizzleDSN
Accepts: attributes.tableWidth
attributes.tableBGcolor
attributes.tableBorderSize
attributes.fontColor
attributes.fontSize
attributes.fontFace
attributes.selection
--->
<script language="JavaScript">
function showError(msg) {
alert(msg);
}
</script>
<!--- make sure we have essential arguments --->
<cfif not IsDefined("attributes.quizzleDSN")>
<cfif not IsDefined("session.quizzleDSN")>
<script>showError("Did not receive DSN for quizzle")</script>
<cfabort>
</cfif>
</cfif>
<!--- initialize non-essential arguments --->
<cfparam name="attributes.tableWidth" default="200">
<cfparam name="attributes.tableBGcolor" default="white">
<cfparam name="attributes.tableBorderSize" default="0">
<cfparam name="attributes.selection" default="random">
<cfparam name="attributes.fontColor" default="black">
<cfparam name="attributes.fontSize" default="2">
<cfparam name="attributes.fontFace"
default="arial, helvetica, sanserif">
<!--- If url.mode is not defined, show initial quizzle --->
<cfif not IsDefined("url.mode")>
<cfinclude template="quizzle1.inc">
<cfelse>
<cfinclude template="quizzle2.inc">
</cfif>
Let's work through this code to make sure we understand how to make our quizzles behave.
First, we create a small JavaScript function that we will use to alert the user to any errors we discover. Why JavaScript instead of ColdFusion? As wonderful as ColdFusion is, it doesn't interface with certain low-level OS functions, such as creating pop-up windows -- yet.
Next, we want to make sure that we have received all essential arguments once this page is called. It turns out that the only essential argument we have to have is the datasource that points to the Quizzle database. We expect to receive this either as an attributes variable or as a session variable.
What is an attributes variable? Simply one that is provided within a custom tag. As we said, custom tags call other ColdFusion pages. The designers at Allaire want to encourage code reuse and maintainability. To promote this, they allow custom tags to include attributes variables, or put more simply, attributes. These are name/value pairs that go along with the custom tag and provide additional information. In this specific, we are making sure either that the name/value pair "quizzleDSN = [some_DSN]" or that the quizzleDSN was set with a session variable.
In our quizzle case, we can deal with a variety of attributes including table widths, custom background colors, and font details. Your users will greatly thank you if you allow, but do not require, them to customize these selections. Accordingly, we've created default values for all the customizable selections. These work in just the same way as the "getQuizzle" variables that we initialized earlier; they provide a default value while allowing themselves to be overwritten.
Using <cfinclude>s for more reusable and maintainable code Next, we come to the heart of the quizzle code -- except there's very little there! Instead of making our page hold all the code first to generate the quizzle and then to provide the answer, we've opted to use <cfinclude> tags. The purpose -- as with much that we do -- is to aid in creating more reusable and maintainable code.
We first test to see if a URL variable called "mode" is present. This will be generated when the user submits the answer. The absence of this variable tells us that we should insert the code to generate the quizzle question: so the call to quizzle1.inc. Of course, if the URL is present, then it's time to present the user with the answers. This is handled by the code contained in quizzle2.inc.
Custom tags, in keeping with their function/object-like nature, have their own scope for all variables contained in them. This means that you don't have to worry about whether your custom tag uses a variable of the same name as your calling page. This is not the case with <cfinclude>d files, which share the same variables as the main page. Unlike custom tags, <cfincludes> do not have a separate scope for variables. Make sure you don't accidentally overwrite your main page variables!]
Examining the included files We'll just touch briefly on the included files, noting anything unusual. Even though they do the bulk of the work, the code is quite plain. Note, though, that this included file itself calls a custom tag to pick an item from a list at random. There is no limit to how many files can be chained together and its use allows code written once to serve in many places.
quizzle1.inc
<cfset session.quizzleDSN = "#attributes.quizzleDSN#">
<!--- selection by date or random? --->
<cfif attributes.selection is "random">
<!--- find out how many quizzles there are --->
<cfquery
datasource="#attributes.quizzleDSN#"
name="getIDlist">
SELECT qID
FROM Quizzles
</cfquery>
<!--- call custom tag to provide a random selection --->
<cf_getRandomListItem aList="#ValueList(getIDlist.qID)#">
<cfset whereClause = "Quizzles.qID = #getRandomListItem#">
<cfelse> <!--- this means it's not random --->
<!--- ...or set for date --->
<cfset whereClause = "Quizzles.qDateStart <= Date()
AND Quizzles.qDateStop > Date()">
</cfif>
<cfquery
datasource="#attributes.quizzleDSN#"
name="getQuizzle">
SELECT *
FROM Quizzles
WHERE Quizzles.qID = #getRandomListItem#
</cfquery>
<!--- How many choices will there be? --->
<cfif getQuizzle.q1 NEQ "">
<cfset responseNumber = "1">
</cfif>
<cfif getQuizzle.q2 NEQ "">
<cfset responseNumber = "2">
</cfif>
<cfif getQuizzle.q3 NEQ "">
<cfset responseNumber = "3">
</cfif>
<cfif getQuizzle.q4 NEQ "">
<cfset responseNumber = "4">
</cfif>
<!--- OK - let's display the quizzle --->
<form
action="quizzle.cfm?mode=answer"
method="POST">
<cfoutput>
<table
width="#attributes.tableWidth#"
border="#attributes.tableBorderSize#"
bgcolor="#attributes.tableBGcolor#"
>
<tr>
<td align="center">
<img
src="images/q-zoneTop.gif"
width=200
height=42
alt=""
border="0">
</td>
</tr>
<tr>
<td align="center">
<font size="1">
<b>#getQuizzle.qQuestion#</b>
</font>
</td>
</tr>
<tr>
<td align="center">
<font size="1">
<!--- send off the primary key and number of
responses as hidden fields --->
<input
type="hidden"
name="qID"
value="#getQuizzle.qID#"
>
<input
type="hidden"
name="responseNumber"
value="#responseNumber#">
<cfif #responseNumber# GT "0">
<input
type="Radio"
name="response"
value="q1">
#getQuizzle.q1#
</cfif>
<cfif #responseNumber# GT "1">
<input
type="Radio"
name="response"
value="q2">
#getQuizzle.q2#
</cfif>
<cfif #responseNumber# GT "2">
<br>
<input
type="Radio"
name="response"
value="q3">
#getQuizzle.q3#
</cfif>
<cfif #responseNumber# GT "3">
<input
type="Radio"
name="response"
value="q4"> #getQuizzle.q4#
</cfif>
</font>
</td>
</tr>
<tr>
<td align="center">
<input
type="image"
src="images/submitQzoneSmall.gif"
border="0"
>
<br>
<img
src="images/q-zonebottom.gif" <br>
width=200
height=18
alt=""
border="0">
</td>
</tr>
</table>
</form>
</cfoutput>
Again, we make use of attributes as a means of customizing generic code. The "selection" attribute is used to determine whether the start and stop dates found in the database are to guide quizzle display, or whether quizzles should be displayed randomly.
Also note that we are setting a session variable called session.quizzleDSN to the same value as attribute.quizzleDSN. The need for this duplication may not be apparent initially. When the main quizzle page is first called, it is done so from a custom tag that has the quizzleDSN included as an attribute. But this page is called again, this time simply by means of the <form> action parameter that recursively calls quizzle.cfm.
We see two uses of the quizzle.cfm page. The first time, it is called as a custom tag. To call any .cfm page as a custom tag, you need only refer to it as <cf_pageName_without_extension>. As a custom tag, it receives the quizzleDSN attribute included in the calling page. Since the next use of this page is as the action parameter of the <form> tag, it will not be passed the quizzleDSN attribute. This presents a problem, as we will have queries that need a datasource. In order to solve this problem before it occurs, we immediately set a session variable to be equal to the attribute variable. Now, while the scope of the session lasts, this will be available to us.
quizzle2.inc
<table
border="<cfoutput>#attributes.tableBorderSize#</cfoutput>"
width="<cfoutput>#tableWidth#</cfoutput>">
<!--- Get correct response for this question --->
<cfquery
datasource="#session.quizzleDSN#"
name="getInfo">
SELECT *
FROM Quizzles
WHERE qID = #form.qID#
</cfquery>
<!--- Did user get right answer? --->
<cfif form.response is #getInfo.qAnswer#>
<cfset score = "Right">
<cfelse>
<cfset score = "Wrong">
</cfif>
<!--- What exactly IS the right answer? --->
<cfif getInfo.qAnswer is "q1">
<cfset rightAnswer = #getInfo.q1#>
</cfif>
<cfif getInfo.qAnswer is "q2">
<cfset rightAnswer = #getInfo.q2#>
</cfif>
<cfif getInfo.qAnswer is "q3">
<cfset rightAnswer = #getInfo.q3#>
</cfif>
<cfif getInfo.qAnswer is "q4">
<cfset rightAnswer = #getInfo.q4#>
</cfif>
<!--- increment the guessed response --->
<cfif form.response is "q1">
<cfset whichResponse = "getInfo.q1Responses">
<cfset updateField = "q1Responses">
</cfif>
<cfif form.response is "q2">
<cfset whichResponse = "getInfo.q2Responses">
<cfset updateField = "q2Responses">
</cfif>
<cfif form.response is "q3">
<cfset whichResponse = "getInfo.q3Responses">
<cfset updateField = "q3Responses">
</cfif>
<cfif form.response is "q4">
<cfset whichResponse = "getInfo.q4Responses">
<cfset updateField = "q4Responses">
</cfif>
<cfset updateValue = #IncrementValue(Evaluate(whichResponse))#>
<!--- update the database results --->
<cfquery datasource="#session.quizzleDSN#" name="updateDB">
UPDATE Quizzles
SET #updateField# = #updateValue#
WHERE qID = #form.qID#
</cfquery>
<cfquery
datasource="#session.quizzleDSN#"
name="getUpdatedInfo">
SELECT *
FROM Quizzles
WHERE qID = #form.qID#
</cfquery>
<!--- create value list for Java applet --->
<cfset totalResponses = #getUpdatedInfo.q1Responses# +
#getUpdatedInfo.q2Responses# + #getUpdatedInfo.q3Responses# +
#getUpdatedInfo.q4Responses#>
<cfset fList = "#getUpdatedInfo.q1#">
<cfset vList = #NumberFormat
(getUpdatedInfo.q1Responses/totalResponses*100, "99.9")#>
<cfif getUpdatedInfo.q2 is not "">
<cfset fList = "#ListAppend(fList, getUpdatedInfo.q2)#">
<cfset vList = #ListAppend(vList, NumberFormat
(getUpdatedInfo.q2Responses/totalResponses*100, "99.9"))#>
</cfif>
<cfif getUpdatedInfo.q3 is not "">
<cfset fList = "#ListAppend(fList, getUpdatedInfo.q3)#">
<cfset vList = #ListAppend(vList, NumberFormat
(getUpdatedInfo.q3Responses/totalResponses*100, "99.9"))#>
</cfif>
<cfif getUpdatedInfo.q4 is not "">
<cfset fList = "#ListAppend(fList, getUpdatdedInfo.q4)#">
<cfset vList = #ListAppend(vList, NumberFormat
(getUpdatedInfo.q4Responses/totalResponses*100, "99.9"))#>
</cfif>
<cfoutput>
<tr>
<td>
<img src="qzone/images/q-zoneTop.gif" width=200 height=42
border="0"> <br>
<font size="1">
<cfif getInfo.giveCorrectAnswer is "yes">
<cfif score is "Right">
<b>You are correct!</b>
<br>
<cfelse>
<b>Sorry!</b>
<br>
The correct answer is <b>#rightAnswer#</b>
<br>
</cfif>
</cfif>
Here's how others answered.<br>
(Figures shown are percentages)
</font>
<center>
<APPLET
CODE="BarChart.class"
CODEBASE="/classes/CFGraphs/"
WIDTH="190"
HEIGHT="200">
<PARAM
NAME="ChartData.Columns"
VALUE="Items,Values">
<PARAM
NAME="ChartData.Items"
VALUE="#fList#">
<PARAM
NAME="ChartData.Values"
VALUE="#vList#">
<param
name="title"
value="Updated Results">
<param
name="titleFontName"
value="Arial">
<param
name="LegendFontName"
value="Arial">
<param
name="BackgroundColor"
value="FFFFFF">
<param
name="ShowDateTime"
value="No">
</APPLET>
</center>
</td>
</tr>
</cfif>
</cfoutput>
</font>
</table>
The entire purpose for the code in this include file is to furnish the ColdFusion Java graph applet with the information it needs to display a real-time analysis of responses. Once this information is put into lists, the Java graphlet takes care of displaying the responses to the quizzle.
In some situations, Java may not be appropriate. In this case, you can fudge the Java applet quite easily. You need answer only a couple of questions: What color do you want your bars to be? How wide should they appear when set next to each other? After you've determined that, create four different colored .gif images in your favorite paint program. (For this little exercise, you need neither much experience in creating graphics nor a high-end paint program -- the Windows Paint program will do.) Make the image as wide as you've decided you want your images to be, but make them only 1 pixel high. Then save these as four separate images.
Now when it's time to display the results of the quizzle, you place the images on the page. The width is set; the height will vary dynamically in order to show a proper bar chart for your quizzle. For example, if you have three responses, one at 20%, one at 30% and one at 50%, you could dynamically set your image heights by multiplying the percentages by 3. This would make the first image 60 pixels tall, the second 90 pixels tall, and the final 150 pixels tall. A little experimenting will help you determine a generic algorithm. And so long as you're developing a generic algorithm, why not create it as a custom tag that you can use whenever you want to employ this little Java-less bar chart?
Conclusion Even in somber times such as our own, the two most read sections of the newspaper continue to be sports and comics. We all hunger for a little relief, a little fun. Quizzles are a small step in this direction. The code required to generate them is, as you have seen, well within the grasp of us ordinary mortals. Yet what response they generate! Put a quizzle on a page, and watch as users immediately gravitate to it.
About Hal Helms Hal Helms is a well-known speaker/writer/strategist on software development issues. He holds training sessions on Java, ColdFusion, and software development processes. He authors a popular monthly newsletter series. For more information, contact him at hal (at) halhelms.com or see his website, www.halhelms.com.
Reader Feedback: Page 1 of 1
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
Click to Add our RSS Feeds to the Service of Your Choice: