views:

1203

answers:

2

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?

+1  A: 

What you've done here is a kind of implementation of the mediator pattern, which is a perfectly acceptable way of doing things, and probably the "best practice" you're looking for, in my opinion, since the components you're using are not directly dependent on each other -- they just dispatch events and let someone else handle the "higher-level" functionality.

An alternative way to do this is dependency injection, where you give the components references to each other and let them communicate directly. This way you would make the components dependent on each other (or maybe not exactly each other, but anything that implements the same interfaces) but you wouldn't have to write any mediator code. Here, as opposed to the mediator pattern, the "higher-level" functionality I was referring to would in fact be the responsibility of the components themselves instead of someone else (the mediator.)

hasseg
+2  A: 

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"&gt;

    <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"&gt;

    <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.

Christian Nunciato
I don't see why you made the property Inspectable. There's really no need for that.
sharvey