Paul Robertson's words, punctuated

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

Creating “dynamic” form fields in ColdFusion Flash Forms -- how and why

Part 3 of My first foray into ColdFusion Flash Forms.

If you are used to using ColdFusion (or any other web development language that outputs HTML) you are probably used to the conceptual notion that these pages are like “templates”:

  1. You create a file containing static text (generally HTML code) mixed with programming code
  2. The server works through the code, processes it all (resolves all variables, loops, etc. to fixed values), and generates text output (the HTML page)
  3. The server sends that text output to the browser, which renders it.

While the CF Flash Forms work in a similar way, the process has some differences which are important to understand in order to make your forms work the way you want them to:

  1. You create a file containing static text mixed with programming code.
  2. For any code inside a CFFORM tag with type="flash", the steps change a bit:
    1. The server works through the code, resolving all variables, loops, etc. to fixed values as though you had hard-coded them within your ColdFusion code.
    2. The server converts the now static CF code within the CFFORM tag to Flex MXML.
    3. The MXML code is sent to the Flex compiler, which generates a SWF file from it.
  3. Within the HTML page that the server sends to the browser, the server adds the appropriate tags to load the SWF file that is dynamically generated by the Flex compiler.

Note: this is just a conceptual explanation of the process, based on my experimentation. I have no idea whether this is actually how the process works, but this seems to be close enough to the reality to help me understand some important details about CF Flash Forms.

The critical part of this explanation is in point 2. Before the CFFORM content gets converted to Flex MXML (which will ultimately become the SWF file), all the variables, loops etc. – essentially, anything within a pair of # signs – gets resolved. The then-static CFML tags are converted to MXML. Why is this important? Compare the following examples.

These are two different ways of creating a dynamic list of checkbox controls in a CF Flash Form, based on data from a query (e.g. a database query). Both assume you have a query named q1 with three fields (id, firstname, lastname). In case you’re curious, these snippets are portions of the code presented in the previous article on two ways to create a “select all” button for checkboxes in a CF Flash 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>
</cfform>

In order to create one checkbox instance per item in the query, this first example uses cfformgroup type="repeater" with a single cfinput type="checkbox" tag nested within it.

<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>
</cfform>

This second example uses a different approach to the same task. In order to make one checkbox per item in the query, this example uses a cfloop tag with a single cfinput type="checkbox" tag nested in it.

The SWF file output of these two examples is practically identical, at least visually. However, behind the scenes there is a significant difference between them. Let’s compare the MXML generated by each of them (obtained by adding a cfdump var="#form1#" to the page after the closing cfform tag).

MXML generated by Example 1 CF code:

.
.
.
<mx:Repeater id="q1" dataProvider="{remoteObject_form1.getData.result.q1._items}">
    <mx:Form marginTop="2" marginLeft="2" marginBottom="2" marginRight="2" widthFlex="1" heightFlex="1">
        <mx:FormItem>
            <mx:CheckBox selected="{UtilFunctions.isTrue(remoteObject_form1.getData.result.chk)}" id="chk" label="{q1.currentItem.firstname + ' ' + q1.currentItem.lastname}"/>
        </mx:FormItem>
    </mx:Form>
</mx:Repeater>
.
.
.

Notice that the cfformgroup type="repeater" gets turned into the MXML mx:Repeater tag, which still contains only a single checkbox tag. In other words, when the CF gets turned into MXML (and when the MXML gets compiled into a SWF) the process of generating the dynamic checkboxes hasn’t happened yet. Not until the SWF file loads in the browser, when the ActionScript underlying the mx:Repeater control loops through the query and creates its child controls, is the actual process of “dynamically generating the checkbox controls” carried out.

On the other hand, compare this to Example 2’s MXML output:

.
.
.
<mx:VBox creationPolicy="all" widthFlex="1" heightFlex="1">
    <mx:Form marginTop="2" marginLeft="2" marginBottom="2" marginRight="2" widthFlex="1" heightFlex="1">
        <mx:FormItem>
            <mx:CheckBox selected="{UtilFunctions.isTrue(remoteObject_form1.getData.result.boxMBAa_1)}" id="boxMBAa_1" label="Rob Smith"/>
        </mx:FormItem>
        <mx:FormItem>
            <mx:CheckBox selected="{UtilFunctions.isTrue(remoteObject_form1.getData.result.boxMBAa_2)}" id="boxMBAa_2" label="John Doe"/>
        </mx:FormItem>
        <mx:FormItem>
            <mx:CheckBox selected="{UtilFunctions.isTrue(remoteObject_form1.getData.result.boxMBAa_3)}" id="boxMBAa_3" label="Jane Doe"/>
        </mx:FormItem>
        <mx:FormItem>
            <mx:CheckBox selected="{UtilFunctions.isTrue(remoteObject_form1.getData.result.boxMBAa_4)}" id="boxMBAa_4" label="Erik Pramenter"/>
        </mx:FormItem>
    </mx:Form>
</mx:VBox>
.
.
.

As you can see, by using cfloop to create the checkbox controls, what (conceptually) happened was that the cfloop was processed and created one cfinput type="checkbox" tag for each item in the query. Each of those items in turn became a MXML mx:CheckBox control. This means that the process of dynamically generating the checkboxes happened on the server, within the ColdFusion processing, rather than on the client in ActionScript (in the SWF file).

Again we ask, then, why does this matter?

Since we are dynamically generating checkboxes from a query, the assumption is that the data will be changing. With the approach in the second example any time the underlying data changes the resulting MXML that is generated by ColdFusion will also need to change. Consequently, any time the data changes (and maybe each time the page is requested, regardless of the underlying data), the server will recompile the SWF file and users will have to download the SWF file again.

On the other hand, with the first approach, in Example 1, no matter how the underlying data changes the MXML that is generated by the ColdFusion page will always be the same. That means that when the data changes, the CF/Flex servers don’t have to recompile the MXML and make a new SWF – they can just use the one that was already generated and is stored in the server’s cache (and the browser’s cache, if the user has browsed to this page previously).

As stated in the ColdFusion Flash Forms documentation, compiling the MXML to SWF is a time-consuming and server-resource-demanding part of the process, so it should be avoided whenever possible. Only if the number of records and number of loops over those records is large should you consider dynamically generating the SWF each time.

As a side note, you may be wondering how the newest data get into the SWF file running on the browser each time, if the same SWF file is reused. Once the SWF loads, it calls back to the CF server (through Flash Remoting, I believe) and loads the data.

Notice in the MXML the mx:Repeater tag has an attribute dataProvider="{remoteObject_form1.getData.result.q1._items}". If you look at the complete MXML generated by the form, down near the end of the MXML code this “remoteObject_form1” object (which is the Flex mechanism used to get the data) is defined in this MXML tag:

<mx:RemoteObject id="remoteObject_form1" source="coldfusion.flash.adapter.CFFormAdapter" endpoint="@ContextRoot()/CFFormGateway/" concurrency="multiple" showBusyCursor="true" fault="appInitFault(event.fault)"/>

And near the top there is a function “appInit” which contains this line of code which actually makes the call to load the data from the server:

remoteObject_form1.getData(__form1_cacheid);

And that’s how it’s done!

Next: Getting meaningful data from dynamically generated fields in ColdFusion Flash Forms

Comments