Programming is a wonderful mix of art and science; source code is both a poem and a math problem. It should be as simple and elegant as it is functional and fast. This blog is about that (along with whatever else I feel like writing about).

Friday, July 04, 2008

Flex Injection: Passing a Function to a Component

Sometimes, in Flex, I find myself in the situation of using a ViewStack to define different screens of my application. And I want to be able to navigate between those views easily, in addition to using components to make development simpler.

I've found that having a "Back" button inside a component can be useful, but if the back button is hidden inside the component, it can't access the ViewStack (since it's in the parent).

I thought it would be useful to be able to pass a little bit of ActionScript to the component that would jump back to the previous view. It would effectively inject functionality from the parent to the child. Here's what my component looks like:
<?xml version="1.0" encoding="utf-8" ?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
<mx:Script>
<![CDATA[

[Bindable] private var _labelText:String;
[Bindable] private var _buttonClick:Function;

public function set labelText(str:String):void {
_labelText = str;
}

public function set buttonClick(func:Function):void {
_buttonClick = func;
}

]]>
</mx:Script>

<mx:Label text="{_labelText}" />
<mx:Button label="Back" click="{_buttonClick()}" />
</mx:VBox>
As you can see, this component allows you to define some text to display and the back button's click handler. The click handler has to be a Function object.

Note that when you call this Function object, you have to put () at the end. It thinks this is actually a method that it can call, so you have to call it like any other method.

But when you instantiate this component, you can't just do something simple like buttonClick="viewStack.selectedChild=anotherView" ... while that may be the ActionScript code you want to execute, and it may work when you're defining the actual click event, the component will interpret this as a String instead of a Function, and it won't work. So you actually have to create a Function object in the parent.
<?xml version="1.0" encoding="utf-8" ?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:components="components.*"
width="100%"
height="100%">
<mx:Script>
<![CDATA[
[Bindable] private var backToViewZero:Function =
function():void { viewStack.selectedChild = viewZero };
]]>
</mx:Script>

<mx:ViewStack id="viewStack" width="100%" height="100%">
<mx:VBox id="viewZero" width="100%" height="100%">
<mx:Label text="Start here!" />
<mx:Button label="One" click="viewStack.selectedChild=viewOne" />
<mx:Button label="Two" click="viewStack.selectedChild=viewTwo" />
</mx:VBox>
<components:MyBox id="viewOne" labelText="One" buttonClick="{backToViewZero}" />
<components:MyBox id="viewTwo" labelText="Two" buttonClick="{backToViewZero}" />
</mx:ViewStack>

</mx:Application>
Here, we create an anonymous method and assign it to the Function object, and then we can pass it to the component when we instantiate it.

This works splendidly, and is a pretty cool way to inject functionality down into your components that wouldn't otherwise be possible. (The component doesn't have to reach up to the parent somehow, which means your component doesn't need to know what the ViewStack is called, or about any of the other views; instead, it just needs to know that it's going to do something, and the parent is responsible for knowing what.)

It leaves me wondering, though, if there's a way to get around the problem of passing in code and having it interpreted as a String rather than the preferred Function. Perhaps by having the buttonClick setter take a String and create the Function object at that point, using something like eval(). Unfortunately, ActionScript 3.0 doesn't have eval(), so I don't know if this is possible. Maybe I'll figure something out later. In the mean time, I'll be creating Function objects in the parent.

3 comments:

Anonymous said...

Just what I needed, Thanks :0)

Anonymous said...

Thanks. Pretty cool solution.

Anonymous said...

Thanks ! Great solution !