Question

I've got an object that contains a dozen or so fields I want to bind to form elements, so that I can use that object to send the data back to the server to be saved.

Definition of my container object:

private static const emptyLink:Object = {
    id: -1, title:'',
    trigger1:'',trigger2:'',trigger3:'',trigger4:'',trigger5:'',
    linkTitle:'', linkBody:'',
    answer1:'',answer2:'',answer3:'',answer4:'',answer5:''
};

[Bindable] public var currentLink:Object = emptyLink;

currentLink is assigned at runtime to a specific index from an ArrayCollection, I'm just using the emptyLink object for initialization purposes, mostly.

<mx:Panel id="triggerPanel" title="Trigger" width="33%">
    <mx:VBox id="tpBoxes" width="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5">
        <mx:TextInput id="trigger1" width="100%" textAlign="left" text="{currentLink.trigger1}" />
        <mx:TextInput id="trigger2" width="100%" textAlign="left" text="{currentLink.trigger2}" />
        <mx:TextInput id="trigger3" width="100%" textAlign="left" text="{currentLink.trigger3}" />
        <mx:TextInput id="trigger4" width="100%" textAlign="left" text="{currentLink.trigger4}" />
        <mx:TextInput id="trigger5" width="100%" textAlign="left" text="{currentLink.trigger5}" />
    </mx:VBox>
</mx:Panel>

Of course, this compiles and displays just fine, but there are runtime warnings for each instance:

warning: unable to bind to property 'trigger1' on class 'Object' (class is not an IEventDispatcher) warning: unable to bind to property 'trigger2' on class 'Object' (class is not an IEventDispatcher) warning: unable to bind to property 'trigger3' on class 'Object' (class is not an IEventDispatcher) warning: unable to bind to property 'trigger4' on class 'Object' (class is not an IEventDispatcher) warning: unable to bind to property 'trigger5' on class 'Object' (class is not an IEventDispatcher)

And the currentLink object is not updated when the TextInput fields are changed.

The obvious answer is that my object needs to be an instance of a class that implements IEventDispatcher. What that answer doesn't tell me is the particulars of implementing that interface (what's required? what's not?), and if there is a simpler way to do this -- like a built in class that will gladly accept my custom properties and allow for binding, without me having to worry about the particulars of implementing the interface.

Does such a class exist? If not, what's the bare minimum and/or accepted standard for accomplishing this task?

Was it helpful?

Solution

You need to use ObjectProxy (as Chetan mentions) - but you also need to use valueCommit to get the text you enter in the input BACK into your object:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Script>
        <![CDATA[
            import mx.utils.ObjectProxy;
              private static const emptyLink:Object = {
    id: -1, title:'',
    trigger1:'',trigger2:'',trigger3:'',trigger4:'',trigger5:'',
    linkTitle:'', linkBody:'',
    answer1:'',answer2:'',answer3:'',answer4:'',answer5:''
};

[Bindable] public var currentLink:ObjectProxy = new ObjectProxy(emptyLink);


private function handleClick():void
{
    trace(currentLink.trigger1);
}
]]>
</mx:Script>

<mx:Panel id="triggerPanel" title="Trigger" width="33%">
        <mx:VBox id="tpBoxes" width="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5">
                <mx:TextInput  id="trigger1" width="100%" textAlign="left" text="{currentLink.trigger1}" valueCommit="{currentLink.trigger1 = trigger1.text;}"/>

                <mx:Button label="Click" click="handleClick()"/>
        </mx:VBox>
</mx:Panel>        

</mx:WindowedApplication>

OTHER TIPS

Object doesn't dispatch events. Although you have made the variable Bindable, the properties of the object referenced by the variable currentLink can not be bound.

Use ObjectProxy instead.

[Bindable] public var currentLink:ObjectProxy = new ObjectProxy(emptyLink);

The first thing you'll want to know is that binding in Flex 3 is not bidirectional. The binding expression will ensure that if the source of the binding expression (currentLink.trigger1) changes that the target (TextInput) will receive notification of the change and update accordingly. If you want the binding to go in the other direction, there are at least two ways to do this:

  1. Use the mx:Binding tag to direct TextInput.text back to the object
  2. Use BindingUtils to do this programmatically instead.

In Flex 4 they are introducing a new syntax for bidirectional binding @{some.binding.expression} but it's not available in Flex 3.

On the 2nd part: the error that you're receiving is because you are binding to a "generic" prototype Object. When you apply the [Bindable] metadata tag to a property or class, the MXMLC compiler generates AS code that includes use of binding utilities and property change watchers to do make the binding happen. However you can't make the prototype Object do this since it's a built-in. You can create a custom ActionScript class which is bindable (or has certain properties bindable). The MXMLC compiler will generate a class which implements IEventDispatcher and therefore supports binding. This has the advantage of being faster than prototype objects and also gives you compile-time checking, i.e. you will receive a compiler error if you reference an invalid property.

The other alternative is to wrap your prototype in ObjectProxy as one of the other SO members has suggested.

Just a tip on how to find out the offending code in a larger project - put a breakpoint on the two

trace("warning: unable to bind to property '"

lines in the SDK's PropertyWatcher class (Navigate > Open Type > ...). Stacktrace will then help you find the ui component that holds the broken binding.

In general, the reason why you get "unable to bind to property foo on a class, is because you are either missing a getter or setter for foo. You could also make foo scoped to a public variable, (although this breaks encapsulation)

So you need Both of these to make it go away:

public function set foo (o:FooObject) : void {
...
}

or

public function get foo() : FooObject {
...
}

Here's the livedocs reference for the interface. It's pretty much what would be obvious.

To quote:

In general, the easiest way for a user-defined class to gain event dispatching capabilities is to extend EventDispatcher.

Hence,

private static const emptyLink:EventDispatcher = {

I haven't been using Flex for very long, and this might not fit your requirements, but why not use XML? I believe you can set the TextInput text value to attributes in the XML.

I'm using pseudo-code, but something like this makes sense to me:

[Bindable] private static const currentLink:XML = <root>
                                                    <trigger1 value=""/>
                                                    <trigger2 value="" />
                                                  </root>;
...
<mx:TextInput id="trigger1" width ... text="{currentLink.trigger1.@value}" />

Something like this, perhaps?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top