R – loosely coupled Flex 3 sibling components

actionscript-3apache-flex

I am creating an application in which I have two mxml components, MainPanel.mxml and TextPanel.mxml, that are contained in a parent application called index.mxml. When I type something the textbox that exists in TextPanel.mxml, I want to be able to bind this data to a label that exists in TextPanel.mxml. I know this can be accomplished by doing the following:

<mx:Label text="{Application.application.textArea.text1.text}" />

However, I want to make my component more easily exchangeable with the system and this creates dependencies that I don't want.

The following is the solution that I came up with , although I feel like there is a better way out there (Some code omitted for brevity's sake).

index.mxml (parent application):

<mx:Script>
    <![CDATA[

        [Bindable]
        private var _text:String;

        private function handleTextChange(input:String):void {
            _text = input;
        }

    ]]>
</mx:Script>

<tp:TextPanel id="textArea" 
    textChange="handleTextChange(event.stringData)"/>

<cp:MainPanel id="mainArea" text = "{_text}" />

TextPanel.mxml:

<mx:Metadata>
    [Event(name="textChange", type="events.CustomStringDataEvent")]
</mx:Metadata>

<mx:Script>
    <![CDATA[
        import events.CustomStringDataEvent;
        private function textChangeHandler(event:Event):void {
            var input:String = event.target.text;
            dispatchEvent(new CustomStringDataEvent(input, 'textChange'));
        }
    ]]>
</mx:Script>
<mx:TextInput id="text1" change="textChangeHandler(event)"/>

MainPanel.mxml

<mx:Script>
    <![CDATA[

        [Bindable]
        public var text:String;

    ]]>
</mx:Script> 
<mx:Label text="{text}" />

So finally to my question, what is the best practice to create loose coupling between two sibling components?

Best Answer

That's one way of doing it, sure. But you could also create another class, say, to hold the data written by one (or any) of your components, then give each component a reference to that object; that might get you the same effect with a bit less code and manual event handling.

For example, the class object holding the data might look something like this (note the Bindable attribute on the public member:

package
{
    public class MyBindableObject
    {
        [Bindable]
        public var myStringProperty:String = "";        

        public function MyBindableObject()
        {
            //
        }   
    }
}

... and your main app container, which would instantiate the object initially (and hand out references to its subcomponents), like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:local="*" initialize="this_initialize(event)">

    <mx:Script>
        <![CDATA[

            [Bindable]
            private var myObject:MyBindableObject;

            private function this_initialize(event:Event):void
            {
                myObject = new MyBindableObject();
            }

        ]]>
    </mx:Script>

    <mx:TextInput text="{myObject.myStringProperty}" />
    <local:MyCustomComponent myObject="{myObject}" />
    <local:MyOtherCustomComponent myObject="{myObject}" />

</mx:WindowedApplication>

... and MyCustomComponent (note the Bindable & Inspectable attributes), which in this case happens to write to myObject.myStringProperty directly:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Script>
        <![CDATA[

            [Bindable]
            [Inspectable]
            public var myObject:MyBindableObject;

            private function myEventHandler(event:Event):void
            {
                myObject.myStringProperty = txt.text;
            }

        ]]>
    </mx:Script>

    <mx:TextInput id="txt" text="{myObject.myStringProperty}" keyUp="myEventHandler(event)" />

... and MyOtherCustomComponent, which receives the changes made in the preceding component (and which are incidentally propagated to the container app as well):

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Script>
        <![CDATA[

            [Bindable]
            [Inspectable]
            public var myObject:MyBindableObject;

        ]]>
    </mx:Script>

    <mx:TextInput text="{myObject.myStringProperty}" />

</mx:Canvas>

So again, the container app initializes the object instance, binds one of its own subcomponents' properties to the value of that object's property (optionally), and hands references to that object to whichever of its subcomponents might want to use it. The second component, in this case, writes the value, and the other two get the changes immediately, since the myStringProperty on the MyBindableObject class is marked Bindable, and each component contains a listener for changes to that property.

The example is somewhat simple in that it really just sets a string value on some object, thereby offloading the event-dispatching work to the Flex framework, which only eliminates a few lines of code -- but that's probably a good thing here, since there's really no need to design a custom event for simple text-change/property-change events, since Flex handles much or even all of that work for you.

Nevertheless it still depends on how much you're looking to customize your TextPanel. If you wanted to develop it into a more complex component, then I'd probably suggest moving the initial object instantiation into the TextPanel itself, do as you've done by defining and dispatching additional custom events, and having the main app and sibling component listen either for event notifications on the component or on the bindable object, either by textPanel.addEventListener (or similarly inline in MXML) or by textPanel.myObject.addEventListener, depending on which were appropriate.

Related Topic