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:

XML:
  1. <mx:ViewStack id="__vs" selectedIndex="{displayIndex}">
  2. <view:MyComponent id="__screen1"/>
  3. <view:UsesExternalHelper id="__screen2" creationPolicy="none"/>
  4. </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

CODE:
  1. [Bindable (ChangeEvent='updateView')]
  2. private function get displayIndex():int
  3. {
  4. return __displayIndex;
  5. }
  6. private function set displayIndex(v:int):void
  7. {
  8. var topIndexInViewStack:int = __vs.childDescriptors.length -1;
  9. if(v <= topIndexInViewStack && v>= 0)
  10. {
  11. __displayIndex = v;
  12. dispatchEvent(new Event("updateView"));
  13. }
  14. }

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:

CODE:
  1. 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

CODE:
  1. [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

CODE:
  1. 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.

XML:
  1. <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

CODE:
  1. 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

XML:
  1. <mx:states>
  2. <mx:State name="one">
  3. <mx:SetProperty target="{__one}" name="x" value="96" />
  4. <mx:SetProperty target="{__one}" name="width" value="200" />
  5. <mx:SetProperty target="{__one}" name="height" value="200" />
  6. <mx:SetProperty target="{__two}" name="x" value="10" />
  7. <mx:SetProperty target="{__two}" name="y" value="104" />
  8. <mx:SetProperty target="{__two}" name="width" value="50" />
  9. <mx:SetProperty target="{__two}" name="height" value="50" />
  10.  
  11. <mx:SetProperty target="{__three}" name="x" value="22"/>
  12. <mx:SetProperty target="{__three}" name="y" value="10"/>
  13. <mx:SetProperty target="{__three}" name="width" value="50" />
  14. <mx:SetProperty target="{__three}" name="height" value="50" />
  15. <mx:SetProperty target="{__one}" name="y" value="10"/>
  16. </mx:State>
  17. <mx:State name="two">
  18. <mx:SetProperty target="{__two}" name="x" value="55" />
  19. <mx:SetProperty target="{__two}" name="y" value="0" />
  20. <mx:SetProperty target="{__two}" name="width" value="200" />
  21. <mx:SetProperty target="{__two}" name="height" value="200" />
  22.  
  23. <mx:SetProperty target="{__one}" name="x" value="0" />
  24. <mx:SetProperty target="{__one}" name="y" value="0" />
  25. <mx:SetProperty target="{__one}" name="width" value="50" />
  26. <mx:SetProperty target="{__one}" name="height" value="50" />
  27.  
  28. <mx:SetProperty target="{__three}" name="x" value="0"/>
  29. <mx:SetProperty target="{__three}" name="y" value="55"/>
  30. <mx:SetProperty target="{__three}" name="width" value="50" />
  31. <mx:SetProperty target="{__three}" name="height" value="50" />
  32. </mx:State>
  33. </mx:states>
  34. <mx:transitions>
  35. <!-- Define a transition for changing from any state to any state. -->
  36. <mx:Transition id="myTransition" fromState="*" toState="*">
  37. <!-- Define a Parallel effect as the top-level effect.-->
  38. <mx:Parallel id="t1" targets="{[__one,__two,__three]}">
  39. <!-- Define a Move and Resize effect.-->
  40. <mx:Resize duration="400"/>
  41. <mx:Move  duration="400" />
  42. </mx:Parallel>
  43. </mx:Transition>
  44. </mx:transitions>
  45.  
  46. <mx:Canvas>
  47. <mx:Panel id="__one" width="50" height="50" click="showState('one')">
  48. <mx:TextArea width="100%" height="100%" text="on this screen click on the individual sub panels to see the states"/>
  49. </mx:Panel>
  50. <mx:Panel id="__two" width="50" height="50" y="55" click="showState('two')">
  51. <mx:TextArea width="100%" height="100%" text="on this screen click on the individual sub panels to see the states"/>
  52. </mx:Panel>
  53. <mx:Panel id="__three" width="200" height="200" x="55" click="showState('')">
  54. <mx:TextArea width="100%" height="100%" text="on this screen click on the individual sub panels to see the states"/>
  55. </mx:Panel>
  56. </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)

XML:
  1. <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

CODE:
  1. currentState = '';
  2.  
  3. currentState = 'one';
  4.  
  5. currentState = 'two';

to get the transitions to work  when i define my transitions

XML:
  1. <mx:Transition id="myTransition" fromState="*" toState="*">

i say from which state to which state this transition must be  used. doing this

CODE:
  1. fromState="*" toState="*"

means from any state to any state use this transition. thus i can define several transitions and start doing stuff like this:

CODE:
  1. fromState="one" toState="*"
  2.  
  3. fromState="*" toState="two"
  4.  
  5. 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:

CODE:
  1. private function onInit():void
  2. {
  3. __collapser = new CollapsableComponentHelper(this,50);
  4. }

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:

CODE:
  1. package org.lennel.blog.example1.view.helpers {
  2.  
  3. import mx.core.UIComponent;
  4. import flash.events.Event;
  5. import mx.states.State;
  6. import mx.states.SetProperty;
  7. import flash.util.trace;
  8. import mx.effects.Resize;
  9. import mx.states.Transition;
  10. import mx.effects.Parallel;
  11. import mx.core.UIComponent;
  12.  
  13. public class CollapsableComponentHelper {
  14. public static const OPENED:String = "collapsableViewOpened";
  15. public static const CLOSED:String = "collapsableViewClosed";
  16. protected var __view:UIComponent;
  17. protected var __collapsedSize:Number;
  18. private var __isOpen:Boolean = true;
  19. public function CollapsableComponentHelper(pView:UIComponent,pCollapsedSize:int = 300):void
  20. {
  21. __view = pView;
  22. __collapsedSize = pCollapsedSize;
  23. createStates();
  24. }
  25. private function createStates():void
  26. {
  27. var stateArray:Array = (__view.states != null)?__view.states.concat(): new Array();
  28. var nState:State = new State();
  29. nState.name = "Open";
  30. var propw:SetProperty = new SetProperty(__view,"width",200);
  31. var proph:SetProperty = new SetProperty(__view,"height",200);;
  32. nState.overrides.push(propw);
  33. nState.overrides.push(proph);
  34. stateArray.push(nState);
  35.  
  36. nState = new State();
  37. nState.name = "Closed";
  38. var propw:SetProperty = new SetProperty(__view,"width",__collapsedSize);
  39. var proph:SetProperty = new SetProperty(__view,"height",__collapsedSize);;
  40.  
  41. nState.overrides.push(propw);
  42. nState.overrides.push(proph);
  43. stateArray.push(nState);
  44.  
  45. var nTransition:Transition = new Transition();
  46. //nTransition.fromState = "Open";
  47. //nTransition.toState = "Close";
  48.  
  49. var res:Resize = new Resize(__view);
  50. res.duration  = 1000;
  51. //pq.children.push(res);
  52. nTransition.effect = res;//pq;
  53. __view.transitions = __view.transitions || new Array();
  54. __view.transitions.push(nTransition);
  55.  
  56. __view.states = stateArray;
  57. trace("--");
  58.  
  59. }
  60.  
  61. public function get isOpen():Boolean
  62. {
  63. return __isOpen;
  64. }
  65. public function set isOpen(v:Boolean):void
  66. {
  67. if(v)
  68. {
  69. open();
  70. }
  71. else
  72. {
  73. close();
  74. }
  75. }
  76. public function open():void
  77. {
  78. if(!__isOpen)
  79. {
  80. __view.setCurrentState("Open",true);
  81. __isOpen = true;
  82. __view.dispatchEvent(new Event(OPENED));
  83. }
  84. }
  85. public function close():void
  86. {
  87. if(__isOpen)
  88. {
  89. __view.setCurrentState("Closed",true);
  90. __isOpen = false;
  91. __view.dispatchEvent(new Event(CLOSED));
  92. }
  93. }
  94. }
  95. }

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

CODE:
  1. var nState:State = new State();
  2. nState.name = "Open";
  3. var propw:SetProperty = new SetProperty(__view,"width",200);
  4. var proph:SetProperty = new SetProperty(__view,"height",200);;
  5. nState.overrides.push(propw);
  6. nState.overrides.push(proph);
  7. 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

CODE:
  1. var nTransition:Transition = new Transition();
  2.  
  3. var res:Resize = new Resize(__view);
  4. res.duration  = 1000;
  5. nTransition.effect = res;
  6. __view.transitions = __view.transitions || new Array();
  7. __view.transitions.push(nTransition);
  8.  
  9. __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

CODE:
  1. __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.

just changed

CODE:
  1. delete __instance;

to

CODE:
  1. __instance = null;

link to the as3 templates

http://xperiments.lennel.org/AS3templates.tar

In flex 1.5 when i in mxml i assigned a component a model i did not care if that model existed or not. it failed silently and flex did what it did, bound correctly and the rest. the 8.5 runtime is not so forgiving, and i like binding my models in mxml. hence i had to start using creationpolicy differently than in the past. now i only create object when i need them and when i have all the necesary information. in the past we would only look at creationPolicy when the view was rendering too slow due too many displayobjects being rendered at once.

anyway. in flex 2 if you do this

XML:
  1. <s:SomeComponent creationPolicy="none"/>

then when you need the component you would do this

CODE:
  1. this.createComponentsFromDescriptors(true);

so whats happening in the background is the compiler creates code that looks something like this:

CODE:
  1. import mx.core.UIComponentDescriptor;
  2. import org.lennel.descriptors.SomeComponent;
  3. private function createSomeChildren():void
  4. {
  5. //I COULD NOT GET CUSTOM ARGUMENTS TO WORK
  6. var args:Object = {};
  7. var descriptor:UIComponentDescriptor = new UIComponentDescriptor(args);
  8. // the object it will be attached to
  9. descriptor.document = this;
  10. //the class the descriptor is going to create for me
  11. descriptor.type = org.lennel.descriptors.SomeComponent;
  12. this.createComponentFromDescriptor(descriptor,true);
  13.  
  14. }

so if creation policy is set to auto all the descriptors are called at once, if Q'd in a Q, and well you should get the idea now. notice i use UIDescriptor.
see: livedocs

a new feature of flex 2 is "view" states whereby i can layout multiple views on the same component using mxml (or actionscript) and change state by setting the currentState property on my view. you can also specify which transitions to use etc when switching from one state to another.
viewstacks in flex 1.5 (and 2) sortoff offer the same functionality.

in flex 1.5 some of the people who work with me tried to create 40 switcing icons that switched between 2 states.they quickly discovered this was a bad idea since all the movieclips got created at once, then the flex framework kicks in with its layout logic, so suddenly the time it took before the user could see a screen jumped from 1 second to 8 seconds. so the next approach was adjusting the creationPolicy, but this let them down an axis of evil (creationpolicy is not always as  straightforward as expected). so what they did was create the clip in flex and use the embed directive. problem solved.
(this could have been done in flex 2 by creating a mxml component with 2 states.)

this example however highlights a limitation of viewstatcks in the past (i have not looked into the dynamics of viewstacks in flex2 so am not certain if all the views gets created immediatly but i suspect so) and maybe in the present. in contrast to this (view) states add and remove the clips as is needed. this is awsome in my opinion. also the zorn plugin for eclipse allows you to look at states independently when designing a form. its just been pointed out to me as well that the design view in zorn allows you to toggle between viewstacks as well

An advantage of building your unit tests as you develop and tying these tests to some application entry point (a mx:Application tag for flex or a movieClip in a AS3 project) is that the eclipse editor uses incremental compilation to show your errors to you. thus developing something that exists in isolation and gets used nowhere won't give you proper feedback. having unit tests solves this problem nicely since every class you are writing has a test and every test is linked to the main test runner thus you can be garunteed that every peice of code you write is somehow imported and compilled.

« Previous PageNext Page »