Sun 9 Apr 2006
In an earlier post i exlpained what states and viewstacks are in the broadest most incomprehensible terms, so after a prompting from Tink i put together a simple example and hopefully a much better and detailed explanation.
abstract:
In the example i have two main views that can be toggled with the buttons on the top saying previous screen and next screen. This toggeling is done via a viewstack's current display index and it gets that info from databinding. Each of those two views in turn has states assigned to them. The first view's states example used mxml to declare the states, whilst the second view (of the collapsable panel) uses an external class which is composed onto the view, the state and effect is thus created via actionscript. The viewstack also illustrates the use of creationpolicy.
the readable information:
on viewstacks, databinding and creationpolicy:
With the what is this behind us, lets jump a but into the explanation of exactly what i did here. the app is viewable here
http://xperiments.lennel.org/ViewStackStatesExample/
and the source is availible via a right click.
In the application entry point (the mx:Application tags) i declare my viewStack that contains the two screens as such:
-
<mx:ViewStack id="__vs" selectedIndex="{displayIndex}">
-
<view:MyComponent id="__screen1"/>
-
<view:UsesExternalHelper id="__screen2" creationPolicy="none"/>
-
</mx:ViewStack>
notice that the ViewStack's selectedIndex (which of the views its displaying) is bound to a property called displayIndex.
the code for that property looks like this
-
[Bindable (ChangeEvent='updateView')]
-
private function get displayIndex():int
-
{
-
return __displayIndex;
-
}
-
private function set displayIndex(v:int):void
-
{
-
var topIndexInViewStack:int = __vs.childDescriptors.length -1;
-
if(v <= topIndexInViewStack && v>= 0)
-
{
-
__displayIndex = v;
-
dispatchEvent(new Event("updateView"));
-
}
-
}
what does this code do. its a simple getter and setter, where the setter checks the bounds on the viewstack so we don't step over the upper limit of the controls within the viewstack and we don't go lower than 0. that is all done in this line:
-
var topIndexInViewStack:int = __vs.childDescriptors.length -1;
so the viewstack has an array called childDescriptors where each descriptor represents each compoin the viewstack. check my previous post on creationpolicy and descriptors to understand how descriptors work. Also notice this binding tag
-
[Bindable (ChangeEvent='updateView')]
this says that the property (a getter in this case) that follows this line must be made bindable by the compiler/flex. when this property changes the event that it dispaches is called updateView as we can see in this line of code in the setter
-
dispatchEvent(new Event("updateView"));
so thats a brief overview of databinding in flex as well. nice and easy i think. ok so lets look at the creationpolicy bit of the second screen in the viewstack.
-
<view:UsesExternalHelper id="__screen2" creationPolicy="none"/>
there are 4 types of creation policy values all, auto, queued and none. i am not going to go into detail into all of them, instead just discussing what none does. a creation policy of none says don't create this component until i tell you to. the advantage of that is that you only need to use the resources involved in that when you need to. i think creationpoilicy will most likely be a post sometime in the future.
to create a child component when its creation policy is set to none you use
-
createComponentFromDescriptor(__screen2.descriptor,true);
where the second argument says recurse into that component and its childcomponents and create them as well. there are otherways to do this, but i still have a lot to say on states!
now on to states:
the file called MyComponent.mxml has got its states laid out in mxml with the transition defined in mxml as well.
look at this mxml
-
<mx:states>
-
<mx:State name="one">
-
<mx:SetProperty target="{__one}" name="x" value="96" />
-
<mx:SetProperty target="{__one}" name="width" value="200" />
-
<mx:SetProperty target="{__one}" name="height" value="200" />
-
<mx:SetProperty target="{__two}" name="x" value="10" />
-
<mx:SetProperty target="{__two}" name="y" value="104" />
-
<mx:SetProperty target="{__two}" name="width" value="50" />
-
<mx:SetProperty target="{__two}" name="height" value="50" />
-
-
<mx:SetProperty target="{__three}" name="x" value="22"/>
-
<mx:SetProperty target="{__three}" name="y" value="10"/>
-
<mx:SetProperty target="{__three}" name="width" value="50" />
-
<mx:SetProperty target="{__three}" name="height" value="50" />
-
<mx:SetProperty target="{__one}" name="y" value="10"/>
-
</mx:State>
-
<mx:State name="two">
-
<mx:SetProperty target="{__two}" name="x" value="55" />
-
<mx:SetProperty target="{__two}" name="y" value="0" />
-
<mx:SetProperty target="{__two}" name="width" value="200" />
-
<mx:SetProperty target="{__two}" name="height" value="200" />
-
-
<mx:SetProperty target="{__one}" name="x" value="0" />
-
<mx:SetProperty target="{__one}" name="y" value="0" />
-
<mx:SetProperty target="{__one}" name="width" value="50" />
-
<mx:SetProperty target="{__one}" name="height" value="50" />
-
-
<mx:SetProperty target="{__three}" name="x" value="0"/>
-
<mx:SetProperty target="{__three}" name="y" value="55"/>
-
<mx:SetProperty target="{__three}" name="width" value="50" />
-
<mx:SetProperty target="{__three}" name="height" value="50" />
-
</mx:State>
-
</mx:states>
-
<mx:transitions>
-
<!-- Define a transition for changing from any state to any state. -->
-
<mx:Transition id="myTransition" fromState="*" toState="*">
-
<!-- Define a Parallel effect as the top-level effect.-->
-
<mx:Parallel id="t1" targets="{[__one,__two,__three]}">
-
<!-- Define a Move and Resize effect.-->
-
<mx:Resize duration="400"/>
-
<mx:Move duration="400" />
-
</mx:Parallel>
-
</mx:Transition>
-
</mx:transitions>
-
-
<mx:Canvas>
-
<mx:Panel id="__one" width="50" height="50" click="showState('one')">
-
<mx:TextArea width="100%" height="100%" text="on this screen click on the individual sub panels to see the states"/>
-
</mx:Panel>
-
<mx:Panel id="__two" width="50" height="50" y="55" click="showState('two')">
-
<mx:TextArea width="100%" height="100%" text="on this screen click on the individual sub panels to see the states"/>
-
</mx:Panel>
-
<mx:Panel id="__three" width="200" height="200" x="55" click="showState('')">
-
<mx:TextArea width="100%" height="100%" text="on this screen click on the individual sub panels to see the states"/>
-
</mx:Panel>
-
</mx:Canvas>
what does this say. there are 3 states defined here (yes i know there are only two state tags!), the third being the default state, where i put the components to start of with. to change a state all i have to do is set the currentState property to a state-name (each state has a name attribute)
-
<mx:State name="one">
or to revert to the default state set the currentstate property to an empty string. so actionscript wise the potential values look as such based on this example
-
currentState = '';
-
-
currentState = 'one';
-
-
currentState = 'two';
to get the transitions to work when i define my transitions
-
<mx:Transition id="myTransition" fromState="*" toState="*">
i say from which state to which state this transition must be used. doing this
-
fromState="*" toState="*"
means from any state to any state use this transition. thus i can define several transitions and start doing stuff like this:
-
fromState="one" toState="*"
-
-
fromState="*" toState="two"
-
-
fromState="one" toState="two"
you get the idea i hope. the great thing about states in this fashion is that my designers can switch to the design view in flexbuilder, choose which state they are working on, drag the components around, my mxml gets updated, and me as the developer doesn't have to do anything designwise. that is great in my opinion.
however lets take the following example:
i have a certain set of states that i want to reuse accross several components, which might change. so if i declare them in mxml and these states change, then i might have to update it in several places. this would normally be solved using a design pattern called composition in code, but mxml does not give us that luxery.
adding states via composition:
those of you who use other frameworks will now of viewhelpers. a viewhelper is a class that is associated with a view that takes certain types logic away from the view itself and encapsulates it. my personal preference in this regard is that any kind of logic that must be applied to the data set before it is displayed should be taken care of in a view helper (thus in my little world it acts as a translator between the model and the view in case they speak different languages) and if the data entered by the user needs to be massaged before a method is called on a controller the viewhelper does that as well. nuff of that, back to the problem at hand.
so what i did is i created a class that adds these states that i want to use in several places to a uiComponent via code, then in my component on the initialize event i create that class and pass in a refrence to the component itself.
so my initialize event's handler fucntion looks like this:
-
private function onInit():void
-
{
-
__collapser = new CollapsableComponentHelper(this,50);
-
}
where the second argument is just something that my class uses, and the first argument is a refrence to the component.
so my class looks like this:
-
package org.lennel.blog.example1.view.helpers {
-
-
import mx.core.UIComponent;
-
import flash.events.Event;
-
import mx.states.State;
-
import mx.states.SetProperty;
-
import flash.util.trace;
-
import mx.effects.Resize;
-
import mx.states.Transition;
-
import mx.effects.Parallel;
-
import mx.core.UIComponent;
-
-
public class CollapsableComponentHelper {
-
public static const OPENED:String = "collapsableViewOpened";
-
public static const CLOSED:String = "collapsableViewClosed";
-
protected var __view:UIComponent;
-
protected var __collapsedSize:Number;
-
private var __isOpen:Boolean = true;
-
public function CollapsableComponentHelper(pView:UIComponent,pCollapsedSize:int = 300):void
-
{
-
__view = pView;
-
__collapsedSize = pCollapsedSize;
-
createStates();
-
}
-
private function createStates():void
-
{
-
var stateArray:Array = (__view.states != null)?__view.states.concat(): new Array();
-
var nState:State = new State();
-
nState.name = "Open";
-
var propw:SetProperty = new SetProperty(__view,"width",200);
-
var proph:SetProperty = new SetProperty(__view,"height",200);;
-
nState.overrides.push(propw);
-
nState.overrides.push(proph);
-
stateArray.push(nState);
-
-
nState = new State();
-
nState.name = "Closed";
-
var propw:SetProperty = new SetProperty(__view,"width",__collapsedSize);
-
var proph:SetProperty = new SetProperty(__view,"height",__collapsedSize);;
-
-
nState.overrides.push(propw);
-
nState.overrides.push(proph);
-
stateArray.push(nState);
-
-
var nTransition:Transition = new Transition();
-
//nTransition.fromState = "Open";
-
//nTransition.toState = "Close";
-
-
var res:Resize = new Resize(__view);
-
res.duration = 1000;
-
//pq.children.push(res);
-
nTransition.effect = res;//pq;
-
__view.transitions = __view.transitions || new Array();
-
__view.transitions.push(nTransition);
-
-
__view.states = stateArray;
-
trace("--");
-
-
}
-
-
public function get isOpen():Boolean
-
{
-
return __isOpen;
-
}
-
public function set isOpen(v:Boolean):void
-
{
-
if(v)
-
{
-
open();
-
}
-
else
-
{
-
close();
-
}
-
}
-
public function open():void
-
{
-
if(!__isOpen)
-
{
-
__view.setCurrentState("Open",true);
-
__isOpen = true;
-
__view.dispatchEvent(new Event(OPENED));
-
}
-
}
-
public function close():void
-
{
-
if(__isOpen)
-
{
-
__view.setCurrentState("Closed",true);
-
__isOpen = false;
-
__view.dispatchEvent(new Event(CLOSED));
-
}
-
}
-
}
-
}
excuse all the garbage in the class i mucked around for a fair bit trying different things.
so what does this do? look at the createStates function. first off i check if this component has other states assigned to it, if it does grab 'em, else create a new array in which i will store my states.
then in this code
-
var nState:State = new State();
-
nState.name = "Open";
-
var propw:SetProperty = new SetProperty(__view,"width",200);
-
var proph:SetProperty = new SetProperty(__view,"height",200);;
-
nState.overrides.push(propw);
-
nState.overrides.push(proph);
-
stateArray.push(nState);
i create a state, give it a name, create a setProperty instance and say on which component i want to set the property, the property's name i want to set and the value, then i push the property into the state's overrides array. then lastly i add this new state to the stateArray. repeat for other states i want to create, add water and then leave in the oven for 20 minutes.
then in the following code
-
var nTransition:Transition = new Transition();
-
-
var res:Resize = new Resize(__view);
-
res.duration = 1000;
-
nTransition.effect = res;
-
__view.transitions = __view.transitions || new Array();
-
__view.transitions.push(nTransition);
-
-
__view.states = stateArray;
i create a new transition, create a new resize effect, giving it the target i want it to affect, set the duration and tell the transition that this resize is the effect i want it to execute. then if there are no transitions on my component defined yet i create a new transitions array and add my transition to it. finally i set my statesArray to be the component's stateArray. now since the default value for fromState and toState is * i advise people to specify those values on the transition they create. i did not bother, but in the class you will see the code commented out.
the rest of the class is pretty self explanitory except maybe this line
-
__view.setCurrentState("Open",true);
and it is pretty straightforeward, but notice the second argument, that boolean specifies wether any transition should be executed or not. (i had set this to false and spent 3 days looking for why my transitions were not working, never looking there, DOH!)
finally:
i hope this explanation is ok, i am not an extremely verbose or clear writer in general (i tend to think in 2 languages and translate badly!), but I really tried making things as clear as possible. my natural tendency is to post the abstract as my blog post but i have been told off about that so...
i have not tried this composition approach with a view that already has states and transitions defined on it. it might break, but i am certain its not going to be difficult to overcome. i would also like to thank fullasagoog and mxna for aggregating me.
April 24th, 2006 at 8:53 am
[…] Johannes has a great post on his blog that goes into detail about viewstacks and states in Flex 2.0 with example code. Worth checking out. […]