You want to create a container that holds multiple child containers that are lazily instantiated upon request.
Create a custom GroupBase-based container and assign an Array-based property to the [DefaultProperty] metatag for the container that represents the declared MXML children. Expose selectedIndex and selectedChild properties to represent the currently displayed child container, and override the protected commitProperties() method to add the appropriate child to the display list of the view stack.
The Spark container set does not provide equal parity to the navigational containers in the MX container set (such as
Accordion and
ViewStack). You can create Spark equivalents to these MX navigational containers, however, using the content API, as well as state management and the new skinning capabilities of the Spark architecture.
The
ViewStack container from the MX component set acts as a navigation container for multiple child containers within a single display. As the selected container is changed, the current container is removed from the display list of the
ViewStack and replaced with the requested container. Optionally, child containers can be lazily created using what is referred to as
deferred instantiation. Although the Spark container set does not offer such a container, you can create a similar one, as shown in the following example:
package com.oreilly.f4cb
{
import mx.core.IVisualElement;
import spark.components.BorderContainer;
import spark.events.IndexChangeEvent;
[Event(name="change", type="spark.events.IndexChangeEvent")]
[DefaultProperty("content")]
public class CustomViewStack extends BorderContainer
{
[ArrayElementType("mx.core.IVisualElement")]
protected var _content:Array;
protected var _selectedIndex:int = −1;
protected var _selectedChild:IVisualElement
protected var _pendingSelectedIndex:int = −1;
override protected function commitProperties() : void
{
super.commitProperties();
// if pending change to selectedIndex property
if( _pendingSelectedIndex != −1 )
{
// commit the change
updateSelectedIndex( _pendingSelectedIndex );
// set pending back to default
_pendingSelectedIndex = −1;
}
}
protected function updateSelectedIndex( index:int ):void
{
// store old for event
var oldIndex:int = _selectedIndex;
// set new
_selectedIndex = index;
// remove old element
if( numElements > 0 )
removeElementAt( 0 );
// add new element
selectedChild = _content[_selectedIndex];
addElement( _selectedChild );
// dispatch index change
var event:IndexChangeEvent = new IndexChangeEvent(
IndexChangeEvent.CHANGE,
false, false,
oldIndex, _selectedIndex );
dispatchEvent( event );
}
private function getElementIndexFromContent( element:IVisualElement ):int
{
if( _content == null ) return −1;
var i:int = _content.length;
var contentElement:IVisualElement;
while( --i > −1 )
{
contentElement = _content[i] as IVisualElement;
if( contentElement == element )
{
break;
}
}
return i;
}
[Bindable]
[ArrayElementType("mx.core.IVisualElement")]
public function get content():Array /*IVisualElement*/
{
return _content;
}
public function set content( value:Array /*IVisualElement*/ ):void
{
_content = value;
// update selected index based on pending operations
selectedIndex = _pendingSelectedIndex == −1 ? 0 :
_pendingSelectedIndex;
}
[Bindable]
public function get selectedIndex():int
{
return selectedIndex = _pendingIndex == -1"
"? 0"
": _pendingIndex
}
public function set selectedIndex( value:int ):void
{
if( _selectedIndex == value ) return;
_pendingSelectedIndex = value;
invalidateProperties();
}
[Bindable]
public function get selectedChild():IVisualElement
{
return _selectedChild;
}
public function set selectedChild( value:IVisualElement ):void
{
if( _selectedChild == value ) return;
// if not pending operation on selectedIndex, induce
if( _pendingSelectedIndex == −1 )
{
var proposedIndex:int = getElementIndexFromContent( value );
selectedIndex = proposedIndex;
} // else just hold a reference for binding update
else _selectedChild = value;
}
}
}
The
content property of the
CustomViewStack in this example is an array of
IVisualElement-based objects and is declared as the
[DefaultProperty] value for the class. Consequently, any child elements declared within the MXML markup for a
CustomViewStack instance are considered elements of the array, and the view stack manages how those child elements are instantiated.
The
selectedIndex and
selectedChild properties are publicly exposed to represent the requested child to display within the custom view stack. Lazy creation of the child containers is accomplished by deferring instantiation of children to the first request to add a child to the display list using the
addElement() method of the content API.
The
CustomViewStack container can be added to an application in MXML markup just like any other container, as long as the namespace for the package in which it resides is defined:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:f4cb="com.oreilly.f4cb.*">
<fx:Declarations>
<fx:String id="lorem">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
</fx:String>
</fx:Declarations>
<fx:Script>
<![CDATA[
private function changeIndex():void
{
var index:int = viewstack.selectedIndex;
index = ( index + 1 > viewstack.content.length - 1 )
? 0 :
index + 1;
viewstack.selectedIndex = index;
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout />
</s:layout>
<f4cb:CustomViewStack id="viewstack" width="300" height="300"
skinClass="com.oreilly.f4cb.CustomBorderSkin">
<s:Group id="child1"
width="800" height="100%"
clipAndEnableScrolling="true">
<s:layout>
<s:VerticalLayout horizontalAlign="justify" />
</s:layout>
<s:Button label="top" />
<s:Button label="bottom" bottom="0" />
</s:Group>
<s:Panel id="child2"
width="100%" height="200"
title="Child 2">
<s:Scroller>
<s:Group width="100%" height="100%">
<s:layout>
<s:VerticalLayout horizontalAlign="center" />
</s:layout>
<s:Button label="panel button 1" />
<s:Button label="panel button 2" />
</s:Group>
</s:Scroller>
</s:Panel>
<s:DataGroup id="child3"
width="100%" height="100%"
itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:dataProvider>
<s:ArrayCollection source="{lorem.split(' ')}" />
</s:dataProvider>
</s:DataGroup>
</f4cb:CustomViewStack>
<s:Button label="switch index" click="changeIndex();" />
<s:HGroup>
<s:Button label="select child 1"
enabled="{viewstack.selectedChild != child1}"
click="{viewstack.selectedChild = child1}" />
<s:Button label="select child 2"
enabled="{viewstack.selectedChild != child2}"
click="{viewstack.selectedChild = child2}" />
<s:Button label="select child 3"
enabled="{viewstack.selectedChild != child3}"
click="{viewstack.selectedChild = child3}" />
</s:HGroup>
</s:Application>
Children of the
CustomViewStack are declared in markup, but they are added to the defined
[DefaultProperty] metatag and are not initially added to the display list of the view stack. Instead, it is deferred to the container to create children as they are requested using the
selectedIndex and
selectedChild properties. The
selectedIndex and
selectedChild properties are bindable and allow for visual and functional updates to the
s:Button controls in the
Application container for this example.
To enable scrolling within the view stack, a custom skin is applied that fulfills the contract for a
BorderContainer-based container. A
Group container with a reference
id of
contentGroup is declared and wrapped within a
Scroller component, as in the following example:
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Metadata>
<![CDATA[
[HostComponent("spark.components.BorderContainer")]
]]>
</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="disabled" />
</s:states>
<s:Rect width="100%" height="100%">
<s:stroke>
<s:SolidColorStroke color="#000000" />
</s:stroke>
<s:fill>
<s:SolidColor color="#FFFFFF" />
</s:fill>
</s:Rect>
<s:Scroller width="100%" height="100%"
left="2" right="2" top="2" bottom="2">
<s:Group id="contentGroup"
left="0" right="0" top="0" bottom="0"
minWidth="0" minHeight="0" />
</s:Scroller>
</s:Skin>
This example demonstrates a technique for accomplishing deferred instantiation of child elements of a Spark-based navigation container that can be applied to creating equivalents of navigation containers from the MX set within the Flex 4 SDK.
This recipe was originally contributed by Todd Anderson as part of O'Reilly's Flex 4 Cookbook.
+