Paul Robertson's words, punctuated

Thoughts on development, user-centered design, code, etc. by Paul Robertson

Getting meaningful data from dynamically generated fields in ColdFusion Flash Forms

Part 4 of My first foray into ColdFusion Flash Forms.

Note: This article will make more sense if you read the previous one first.

In the previous articles I talked about two approaches to creating a repeating set of form controls (in this case checkboxes) in a ColdFusion Flash Form, based on data in a query. I recommended one approach, which is to use a cfformgroup type="repeater" tag to create multiple controls, because it doesn’t require the SWF file to be regenerated each time the underlying data changes. The alternative, using a cfloop tag to create cfinput type="checkbox" controls, does require the SWF file to be regenerated each time the underlying data changes.

However, the cfloop approach has a big advantage. By creating the checkbox controls separately within your CFML, you can specify a separate name for each checkbox control, and it is easy to differentiate between them when the form is submitted.

Consider these two examples (which are the same as in the previous article, except that I have added a “submit” button to the form).

<cfform name="form1" format="flash" height="600" width="450">

<cfformgroup type="repeater" query="q1"> <cfinput type="Checkbox" name="chk" Label="{q1.currentItem.firstname + ' ' + q1.currentItem.lastname}"> </cfformgroup> <cfformgroup type="horizontal"> <cfinput type="submit" name="submitBtn" value="Send Data" width="100"> </cfformgroup> </cfform>

As described previously, this example uses a cfformgroup type="repeater", which dynamically generates the checkboxes on the client, using ActionScript in the SWF file.

<cfform name="form1" format="flash" height="400" width="450">
    <cfformgroup type="VBox">
        <cfloop query="q1">
            <cfinput type="checkbox" name="boxMBAa_#id#" label="#firstname# #lastname#">
        </cfloop>
    </cfformgroup>
    <cfformgroup type="horizontal">
        <cfinput type="submit" name="submitBtn" value="Send Data" width="100">
    </cfformgroup>
</cfform>

Again, the difference here is that a cfloop is used to generate the checkboxes in the server, so in the generated MXML (and the generated SWF file) they are separate checkbox controls.

When you submit these forms, the results are surprisingly different.

The output of the first example (using cfdump var="#form#") looks like this (assuming the first and the third checkboxes are checked):

Results of the first form.

While the output of the second example looks like this:

Results of the second form.

Obviously there is a significant difference. When we submit the first form, we only get one name/value pair containing the values for all the checkboxes. Not only that, but all we get for each checkbox is a true/false value. There is no practical way that we can figure out which associated value in our underlying data source was selected. However, with the second example, because each checkbox was created separately on the server (and we were able to give each one its own name) we see four different name/value pairs – one for each checkbox.

This is a serious problem for the cfformgroup type="repeater" approach to dynamically creating form controls. If there isn’t any way to get the data back, there isn’t really any point to creating the form a in the first place. I hoped that there would be a way to assign a “data” value to each checkbox which would get returned in place of the true/false value (in the same way that multiple checkboxes with the same name in an HTML form return a value). But there doesn’t seem to be a way to do this.

Unfortunately, I think this boils down to a limitation in the fact that the CF Flash Forms are using Flex but are submitting their data as if they were HTML forms. If we were actually building this same form using Flex, then there would be no need to get anything more than a true/false value from the checkbox. If we wanted to know which underlying values were selected, within our Flex application we could write code that would loop through the checkboxes to find out which ones are checked, and use that in combination with the underlying data (which would also be available to the ActionScript) – we could then manipulate that data, send it to the server, etc. however we like.

But that isn’t how CF Flash Forms work – they submit their data to the server as though it was from a traditional web form. Note that we could actually use an alternative approach (e.g. Flash Remoting) to submit our data, rather than using the standard “submit” behavior of the Flash Form – but that seems like a lot of extra work just so that we can have a dynamically generated set of form controls built the right way according to Macromedia’s recommendations.

However, I was able to come up with a workaround. Admittedly the data submitted by the form isn’t necessarily as clean and convenient as with the “checkboxes generated by cfloop” approach, but it at least makes it possible to get the data associated with checkboxes.

The solution: input type="hidden". As you probably know, within an HTML form you can use a “hidden” form field to store a value which gets submitted by the browser but which doesn’t have any visible manifestation on the page. With a CF Flash Form, you can add an input type="hidden" field to the form which has the same effect. So to get the id values associated with our checkboxes, we just need to create a hidden form field which contains all the id values in a comma separated list. Just add these lines to your code:

.
.
.
<cfparam name="mylist" default="">
<cfloop query="q1">
    <cfset mylist = listappend(mylist,#q1.id#)>
</cfloop>
.
.
.
<cfform name="form1" format="flash" height="600" width="450">
.
.
.
    <!--- the code for the repeating form controls (i.e. the cfformgroup type="repeater") goes here --->
    <cfinput type="hidden" name="ids" value="#mylist#">
</cfform>
.
.
.

Notice that outside the cfform tag we use cfloop to create a variable that is a ColdFusion “list” containing all the id values from our query. The contents of that variable are then set as the value of our hidden form field.

So now when we submit our form, we see results that look like this:

Results of the first form, with the addition of a hidden form field.

Notice the one (significant!) difference – there is now an extra name/value pair being submitted by our form, containing a comma-separated list of the ids from our query. Within the page that processes this form submission, we will have to loop through the two form fields (the one containing the true/false values and the other containing the ids) to match them up, in order to determine which values are the “selected” ones.

At this point, astute readers will probably be thinking “wait a minute, in the last article Paul said that it’s bad to use cfloop with CF Flash Forms, because the server has to regenerate the data each time the data changes. What gives?”

That was exactly my concern with using this approach. However, the CF engineers apparently anticipated this need (and here I have to give big kudos to those CF engineers, because I think what they’ve done is just brilliant). Take a look at the MXML generated when the <input type="hidden" /> is included. Try to find what the <input type="hidden" /> gets turned into in MXML. Give up? Look near the top for a tag named mx:Model. It looks like this:

<mx:Model id="form1">
    <chk>{chk.selected}</chk>
    <ids>{remoteObject_form1.getData.result.ids}</ids>
</mx:Model>

Notice that it contains two elements, which correspond exactly to the values that are submitted with the form. The <chk> is how the true/false values actually get put into the form field named “chk” that is submitted with the form. And there’s our hidden form field, in the <ids> tag. Notice something very important about it: it doesn’t contain a hard-coded list of values which would need to be recompiled each time. Rather, it contains a binding to an ActionScript value (the stuff in curly brackets {}). At the end of the previous article I pointed out that the “remoteObject_form1” is an object defined in the Flex MXML that essentially is how the SWF file gets the latest data each time it is loaded in the browser, without needing to recompile on the server. The value contained in the <ids> tag is a binding to the “ids” property of that same remote object. This means that each time the SWF file loads and gets the latest data from the server, it also gets the latest values to submit as the ids form field – but still without requiring the server to recompile the MXML.

Again, all I can say is that whoever wrote the code that is smart enough to turn my ColdFusion code (that I just wrote off pretty much off the top of my head)…

<cfparam name="mylist" default="">
<cfloop query="q1">
    <cfset mylist = listappend(mylist,#q1.id#)>
</cfloop>

…into this MXML…

<mx:Model id="form1">
.
.
.
<ids>{remoteObject_form1.getData.result.ids}</ids>
</mx:Model>

…is just brilliant.

(Well, either that or I just got really really lucky and happened to type just the right CF code that gets turned into that MXML. Okay, we all know how likely that is. =)

Comments