views:

988

answers:

3

I have a component with two Pie Charts that display percentages at two specific dates (think start and end values). But, I have three views: Start Value only, End Value only, or show Both. I am using a ToggleButtonBar to control the display. What is the best practice for changing this kind of view state? Right now (since this code was inherited), the view states are changed in an ActionScript function which sets the visible and includeInLayout properties on each Pie Chart based on the selectedIndex of the ToggleButtonBar, but, this just doesn't seem like the best way to do this - not very dynamic. I'd like to be able to change the state based on the name of the selectedItem, in case the order of the ToggleButtons changes, and since I am storing the name of the selectedItem for future reference.

Would using States be better? If so, what would be the best way to implement this?

Thanks.

Current logic:

private function pieTypeToggleButtonBar_itemClickHandler():void
{
    // Show/Hide the appropriate Pie Charts based on the user's selection
    switch (pieTypeToggleButtonBar.selectedIndex)
    {
        // "Start Value" is selected
        case 0:
        {
            // Hide the End Value Pie Chart
            endValuePieChartVBox.visible = false;
            endValuePieChartVBox.includeInLayout = false;

            // Show the Start Value Pie Chart
            startValuePieChartVBox.includeInLayout = true;
            startValuePieChartVBox.visible = true;

            break;
        }
        // "End Value" is selected
        case 1:
        {
            // Hide the Start Value Pie Chart
            startValuePieChartVBox.visible = false;
            startValuePieChartVBox.includeInLayout = false;

            // Show the End Value Pie Chart
            endValuePieChartVBox.includeInLayout = true;
            endValuePieChartVBox.visible = true;

            break;
        }
        // "Both" is selected
        case 2:
        {
            // Show the Start Value Pie Chart
            startValuePieChartVBox.includeInLayout = true;
            startValuePieChartVBox.visible = true;

            // Show the End Value Pie Chart
            endValuePieChartVBox.includeInLayout = true;
            endValuePieChartVBox.visible = true;

            break;
        } 
    }
}

<mx:ToggleButtonBar id="pieTypeToggleButtonBar" selectedIndex="1" 
    itemClick="pieTypeToggleButtonBar_itemClickHandler()">
    <mx:Array>
        <mx:Object name="startValue" label="Start Value" />

        <mx:Object name="endValue" label="End Value" />

        <mx:Object name="both" label="Both" />
    </mx:Array>
</mx:ToggleButtonBar>
+1  A: 

The simplest way to do this would be to use the ViewStack component. That way you just select the selectedIndex and all the other panels will hide (watch out for initialization problems with ViewStacks).

Because of the problems i have had with ViewStacks in the past i would probably be inclined to use view states though as an alternative. States have their own problems but they are definitely feasible for this particular problem.

If i were you i would look into either of these options as a solution as the functionality created about has already been created with a standard api.... try and stick to using mx components if they fit your specific needs rather than reinventing the wheel all the time

James Hay
A View Stack will not work unless I duplicate code. A View Stack only shows one component at a time. I also need the option to show both children, not just one or the other.
Eric Belair
Good point... Got the wrong end of the stick
James Hay
+2  A: 

Since the currentState property takes a String, which maps to the name property of a state, then it sounds like using <mx:states> would work well in your case. In fact I use states often for toggling between views in just the way you describe -- setting visible and includeInLayout properties of components (usually Canvas components, or other sorts of containers) with SetProperty:

<mx:states>
    <mx:State name="View State 1">
        <mx:SetProperty target="{component1}" name="visible" value="true" />
        <mx:SetProperty target="{component2}" name="visible" value="false" />
        <mx:SetProperty target="{component3}" name="visible" value="false" />
        <mx:SetProperty target="{component1}" name="includeInLayout" value="true" />
        <mx:SetProperty target="{component2}" name="includeInLayout" value="false" />
        <mx:SetProperty target="{component3}" name="includeInLayout" value="false" />
    </mx:State>
    <mx:State name="View State 2">
        <mx:SetProperty target="{component1}" name="visible" value="false" />
        <mx:SetProperty target="{component2}" name="visible" value="true" />
        <mx:SetProperty target="{component3}" name="visible" value="false" />
        <mx:SetProperty target="{component1}" name="includeInLayout" value="false" />
        <mx:SetProperty target="{component2}" name="includeInLayout" value="true" />
        <mx:SetProperty target="{component3}" name="includeInLayout" value="false" />
    </mx:State>
    <mx:State name="View State 3">
        <mx:SetProperty target="{component1}" name="visible" value="false" />
        <mx:SetProperty target="{component2}" name="visible" value="false" />
        <mx:SetProperty target="{component3}" name="visible" value="true" />
        <mx:SetProperty target="{component1}" name="includeInLayout" value="false" />
        <mx:SetProperty target="{component2}" name="includeInLayout" value="false" />
        <mx:SetProperty target="{component3}" name="includeInLayout" value="true" />
    </mx:State>
</mx:states>

<mx:Script>
    <![CDATA[

        import mx.binding.utils.BindingUtils;
        import mx.binding.utils.ChangeWatcher;

     private function this_creationComplete(event:Event):void
     {
                // Use BindingUtils.bindSetter to hook into selectedIndex-change events
      var cw:ChangeWatcher = BindingUtils.bindSetter(setState, myBar, "selectedIndex");
     }

        private function setState(index:int):void
        {
            currentState = myBar.getChildren()[index].label;
        }

    ]]>
</mx:Script>

<mx:ToggleButtonBar id="myBar">
    <mx:dataProvider>
        <mx:Array>
            <mx:String>View State 1</mx:String>
            <mx:String>View State 2</mx:String>
            <mx:String>View State 3</mx:String>
        </mx:Array>
    </mx:dataProvider>
</mx:ToggleButtonBar>

<mx:Canvas id="component1">
    <mx:Text text="Component 1" />
</mx:Canvas>

<mx:Canvas id="component2">
    <mx:Text text="Component 2" />
</mx:Canvas>

<mx:Canvas id="component3">
    <mx:Text text="Component 3" />
</mx:Canvas>

... following this general pattern. Hope it helps!

Christian Nunciato
Is there any way that I can bind my ToggleButtonBar to the states?
Eric Belair
Not as explicitly as that, because I don't believe the ToggleButtonBar control exposes its currently-selected-item's label property as a Bindable string property. You can achieve the same effect, though, using BindingUtils.bindSetter() -- I'll update my example to show you how.
Christian Nunciato
+1  A: 

The use of states in the first component completely misses the point. You shouldn't be setting visible and includeInLayout, you should be ADDING and REMOVING the components using AddChild and RemoveChild. The use of visible and includeInLayout to control object's state on the screen is a bastardization of their intent. Unfortunately, since Flex doesn't have any sort of conditional logic tags or even conditional attributes to add/remove elements, people often fall back on those two tags. Sometimes that's the only practical thing to do, but in your case or in any case where you have a 'switch' statement, you definitely want to use states.

Matt Hughes