Paul Robertson's words, punctuated

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

Dynamically change a cfinput field's label (new and improved)

In a previous article I demonstrated a workaround approach to dynamically changing the label of a cfinput field in a ColdFusion Flash Form. Thanks to some great feedback from Laura Arguello, I’ve now worked out some improvements to the solution, so that it doesn’t require a “fake” label as the previous approach did. So, here it is, the new and improved way of dynamically changing the label of a cfinput field. (If you read the previous article, the explanations are similar – the solution is just simpler.)

If you want to skip the explanation, here is the finished working code.

Note: this example is intentionally more complicated than it might need to be – in some cases, it is possible to accomplish the same results using only data binding and without needing any additional ActionScript. Laura’s post gives a great example of this. For these examples, I intentionally decided to make it a bit more complicated, because it also allows for more flexibility in the final result. I recommend looking at both the simpler and the more complex approaches, and deciding what meets your needs best.

To begin, 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. To make this example more real-world, I’ve put a number as the value in each <option> tag – the ActionScript code in the event handler will need to use this number to determine what value to set for the label. 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" label="Method:" width="150" onchange="#payMethod_onChange#">
            <option value="-1"></option>
            <option value="10">Department Account</option>
            <option value="11">Purchase Order</option>
            <option value="12">Check</option>
            <option value="13">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 directly using ActionScript. Ugh.

The new solution

(If you’ve been following along, here is where things start to change from the previous approach.)

So there is no way to directly change the actual label using ActionScript. However, as Laura kindly pointed out, we can define a binding statement for the label attribute of the cfinput tag, and that binding will cause the label to dynamically change any time the value it’s bound to changes:

<cfinput name="payDetail" label="{}" type="text" width="85" />

So we can get the text into the label dynamically using a binding 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.

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 attribute 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:

<cfinput type="hidden" name="payDetailLbl" value="" />
<cfinput name="payDetail" label="{paymentForm.payDetailLbl}" type="text" width="85" />

If you haven’t worked with them before, Flex (and CF Flash Forms) binding statements are always surrounded by curly braces. The natural inclination might be to just use the name of the hidden form field (“payDetailLbl”) in the curly braces, but that doesn’t work. In fact what you need to do is 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, as 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.

All that’s left to do now is 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" label="{paymentForm.payDetailLbl}" type="text" visible="false" 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">
payDetail.visible = true;

switch (payMethod.selectedItem.data) {
    case "10": /* Department acct */
        paymentForm.payDetailLbl = "Account #:";
        payDetail.width = 85;
        break;
    case "11": /* PO */
        paymentForm.payDetailLbl = "PO Number:";
        payDetail.width = 100;
        break;
    case "12": /* Check */
        paymentForm.payDetailLbl = "Check #:";
        payDetail.width = 85;
        break;
    case "13": /* Credit Card */
        paymentForm.payDetailLbl = "Card #:";
        payDetail.width = 120;
        break;
    default: /* no selection */
        payDetail.visible = false;
        break;
}
</cfsavecontent>

The code checks 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 most common case (with the text field visible) is set (payDetail.visible = true). Otherwise this line would need to be repeated for almost every one of the possible conditions.
  2. The switch statement checks the selected item in the combo box. (payMethod.selectedItem.data).
  3. Depending on the item that is selected, the width of the text field is adjusted (since credit card numbers are usually longer than check numbers) and the hidden form field’s value is set (paymentForm.payDetailLbl = "[some label]"). Setting the hidden form field causes the label to be changed, because the cfinput tag’s label attribute is bound to the hidden field’s value.

A couple of other points to note:

  • The “default” case (which in this case is triggered if no payment method is selected) hides the text field completely by setting its visible property to false.
  • For the “case” values in the switch statement, even though the values are numbers they are wrapped in quotation marks. This is because the payMethod.selectedItem.data value is a String, and the switch statement in ActionScript uses strict equality to make its comparison (meaning it checks both the value and the data type of the variable being compared).
  • If you try this out at this point, you’ll notice that the form is “jumpy” – every time the label of the text field changes, the position of the form fields shifts because the label’s width changes. To get around this, in the final code I padded the combo box’s label with spaces on the left hand side so that it is always wider than the text field’s label – so it’s width (which doesn’t change) determines the position of the form fields.

That’s all there is to it. You can download the final code if you want to try it out..

Comments