Paul Robertson's words, punctuated

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

Dynamically change a cfinput form field's label (ColdFusion Flash Forms)

Note: After I wrote this article, Laura Arguello offered some valuable feedback which led me to a similar but better solution. Since I learned something new, I wanted to pass it along, so this article is now superseded by (a better way to) dynamically change a cfinput field’s label.

I’ve seen a couple of entries in my server logs for people trying to figure out how to dynamically change the label of a cfinput field in a ColdFusion Flash Form. This is something I ran into myself a few weeks back, so I thought I’d share what I learned.

Unfortunately, this seems like it should be really easy, but it’s actually impossible to do “for real” (at least as far as I’ve found). I did figure out a way to get the same result using a bit of a workaround. To demonstrate this idea, I’ve built a simple CF Flash Form that has a combo box (cfselect) that lets you choose a payment method. Depending on your choice, the label of a cfinput type="text" field changes. If you want to skip the explanation, here is the finished working code. If you want to know more of the how and why, here goes:

Before I start, in this example there is a combo box (<cfselect>) in the form, as well as a text input field with a label (<cfinput type="text">). The selected item in the combo box determines the label that is assigned to the cfinput text field; to control this, we will write some ActionScript for the cfselect’s onchange event (but we’ll get to that later). So that I don’t have to keep repeating it in later code listings, this is the CF code for the combo box and the form. Notice that to keep this example simple I’ve just put the desired label in as the value in each <option> tag. Obviously in a real-world scenario that will probably be some sort of number or database key. Also notice that I’ve given the form a name – this is critical to the solution.

<cfform name="paymentForm" format="flash">
<cfformgroup type="horizontal">
    <cfselect name="payMethod" width="150" onchange="#payMethod_onChange#">
            <option></option>
            <option value="Account #">Department Account</option>
            <option value="PO Number">Purchase Order</option>
            <option value="Check #">Check</option>
            <option value="Card #">Credit Card</option>
        </cfselect>
</cfformgroup>

<!---
The cfinput text field will be added here.
--->

</cfform>

I think much of the confusion about being able to change a cfinput text field’s label dynamically lies in the way you define the label on a cfinput text field (by setting the “label” attribute in the tag – very easy). Naturally that leads to the (incorrect) assumption that you can just access a “label” property of the text field in ActionScript to change that label. Therein lies the biggest complication with using ActionScript in CF Flash Forms. Remember, the tags you define, and their attributes, are all part of ColdFusion. However, this is really all just a wrapper for the real Flex engine that is under the hood. Once the ColdFusion code gets turned into Flex MXML, the ColdFusion code is gone and you need to use the Flex object model to access the properties of your form fields (more on this subject).

One of the places this gives us trouble is with the label of a cfinput field such as an ordinary text input field. In CF, you might define it something like this:

<cfinput name="payDetail" label="PO Number:" type="text" width="85" />

We have a basic cfinput text field. Pretty straightforward. Under the hood, this cfinput field gets turned into the following MXML:

<mx:FormItem label="PO Number:">
    <mx:TextInput id="payDetail" text="{remoteObject_paymentForm.getData.result.payDetail}" width="85" heightFlex="0"/>
</mx:FormItem>

Notice the critical difference here: the single cfinput tag actually gets turned into two distinct MXML tags (which means they are two different objects from the perspective of accessing them using ActionScript). The name you give to the cfinput field (“payDetail”) is assigned as the id of the <mx:TextInput> control. This is useful so that we can set it’s value, for instance. However, notice that the label we assigned is actually part of the <mx:FormItem> tag that wraps the <mx:TextInput> control. That means that the label itself is not a property of the mx:TextField, and the mx:TextField control knows nothing about it. In addition, the mx:FormItem tag doesn’t have any id assigned to it. This is critical – the id attribute of the MXML tag is what you use in ActionScript to refer to that object, so an item with no id can’t be accessed with ActionScript. This means, of course, that you can’t change the label using ActionScript. Ugh.

So there is no way to change the actual label using ActionScript. There is there an alternative, a way we can simulate a label in a way which will allow it to be changed. One way to add arbitrary text to a CF Flash Form is using a <cfformitem type="text"> element. So rather than using the built-in label for our form field, we can add a text formitem and use it as our field label. In order to get them to display like the form field’s label did, wrap them both in a cfformgroup type="horizontal":

<cfformgroup type="horizontal">
    <cfformitem type="text" width="75">PO Number:</cfformitem>
    <cfinput name="payDetail" type="text" width="85" />
</cfformgroup>

In order to make it match the look of the real form field label, you will need to add some style attributes to the cfformitem element, but I left that out of this example for simplicity (the final code does contain styles). This ColdFusion code gets turned into the following MXML:

<mx:FormItem direction="horizontal" creationPolicy="all" widthFlex="1" heightFlex="1">
    <mx:Text width="75" heightFlex="1">
        <mx:text>{remoteObject_paymentForm.getData.result.CFFORMITEM_4}</mx:text>
    </mx:Text>
    <mx:TextInput id="payDetail" text="{remoteObject_paymentForm.getData.result.payDetail}" width="85" heightFlex="0"/>
</mx:FormItem>

Something interesting to note is that the label text that is specified in the ColdFusion (“PO Number:”) doesn’t actually appear in the MXML – instead it is represented by the Flex binding statement {remoteObject_paymentForm.getData.result.CFFORMITEM_4}. That’s very promising – it means that that text is actually being accessed dynamically, which means we can probably also change it. In fact we can. The mx:Text control that is created from our cfformitem type="text" tag doesn’t have an id attribute, so we can’t access it directly using ActionScript, but one of the attributes we can use in the cfformitem type="text" tag is the “bind” attribute, which allows us to specify an object property that will serve as the source of the text. So we’ll get the text into the label dynamically using a bind statement; the only question is, what do we specify as object/property to “bind” our label to? It can’t just be any ActionScript or other code – it has to be an actual object or property of an object.

Remember I warned you, this does require a bit of a workaround. We need to have a form field whose content will control the label of our text input field. Since we probably don’t want to have some random control appearing in the form, our best option is to use a hidden form field (cfinput type="hidden"). The hidden form field won’t actually serve a purpose in terms of the data that is submitted by the form – it will simply act as a container that the label formitem is bound to. When the user chooses an item from the combo box, we will use ActionScript to set the value of the hidden form field to whatever we want the label to show, and Flex’s data binding will automatically update the label to match. Here’s what the CF code looks like with those changes:

<cfformgroup type="horizontal">
    <cfformitem type="text" width="75" bind="{paymentForm.payDetailLbl}" />
    <cfinput type="hidden" name="payDetailLbl" value="" />
    <cfinput name="payDetail" type="text" width="85" />
</cfformgroup>

For me, one tricky part was figuring out the exact syntax to use in the “bind” statement to refer to the hidden form field’s value. If you haven’t worked with them before, Flex (and CF Flash Forms) binding statements are always surrounded by curly braces. First I tried using just the name of the hidden form field (“payDetailLbl”) but that didn’t work. After a bit I figured out that I needed to use [form name-dot-hidden field name] (“paymentForm.payDetailLbl” in this case). The reason for this is because hidden form fields are not actually added to the MXML as Flex controls, the same way visible controls are. Rather, they are added as elements in the mx:Model tag (which is essentially a general data repository that ultimately contains a value for each form field, which is how they get submitted back to ColdFusion when you submit a CF Flash Form). Those mx:Model elements are given names, and can be accessed in your cfform’s ActionScript as child properties of the form, as we do here.

We’re almost there now – all we have to do is make it work =). More specifically, we need to write the ActionScript which will change the label when a new value is selected in the combo box (its onchange event). Just for looks, we’ll also make it so that when no payment method is selected in the combo box, the input field is hidden. That means it will be hidden by default, so we need to make a minor change to our text input field so that it looks like this (the change is bolded):

<cfinput name="payDetail" visible="no" type="text" width="85" />

Finally, the ActionScript itself will be written like this (yes, I’m still using cfsavecontent until my server admin installs CF MX 7.1):

<cfsavecontent variable="payMethod_onChange">
if (payMethod.selectedItem.data == "") {
    payDetail.visible = false;
    paymentForm.payDetailLbl = "";
} else {
    payDetail.visible = true;
    paymentForm.payDetailLbl = payMethod.selectedItem.data;
}
</cfsavecontent>

The code uses the value of the selected <option> from our <cfselect> tag . In ActionScript you access this using the selectedItem.data property of the combo box (whose id comes from the cfselect’s name). So in this case the value of whichever item is selected in the combo box appears in the code as payMethod.selectedItem.data. The code does the following things:

  1. The if statement checks if the default (blank) item was chosen.
  2. If so, it hides the input field (payDetail.visible = false) and sets the label to "" (an empty string).
  3. Otherwise, a real value is selected, so the input field is shown (payDetail.visible = true) and the hidden form field’s value is set (paymentForm.payDetailLbl = payMethod.selectedItem.data). Remember that setting the hidden form field causes the label to be changed, because it is bound to the hidden form field through the “bind” attribute.

That’s all there is to it. You can download the final code if you want to try it out, or to see how I added style formatting to the field to make it look like a real field label. One thing you may notice is that in the final code I added a label to the combo box as well. In order to make things line up nicely, it uses another fake label created with cfformitem type="text", even though in that case the label isn’t actually dynamic.

Comments