Containers: Chapter 2 - Flex 4 Cookbook

by Joshua Noble, Todd Anderson, Marco Casario, Garth Braithwaite
Flex 4 Cookbook

This excerpt is from Flex 4 Cookbook. This highly practical book contains hundreds of tested recipes for developing interactive Rich Internet Applications. You'll find everything from Flex basics to solutions for working with visual components and data access, as well as tips on application development, unit testing, and Adobe AIR. Each recipe features a discussion of how and why it works, and many of them offer sample code that you can put to use immediately.

buy button

The Flex Framework provides two sets of containers: MX and Spark. The introduction of the Spark architecture to the Flex 4 SDK offers a level of abstraction between containers and layouts not available in the MX architecture. An MX container internally manages the size and position of its children based on specified properties and styles. To modify the layout rules of a MX container, you often create a subclass of a similar container or the base mx.core.Container class and override methods such as updateDisplayList() and measure(). In contrast, Spark containers are separated from layout management and allow for a developer to specify layouts available in the spark.layouts package or create custom LayoutBase-based layouts to manage the size and position of child elements. The separation of responsibilities for Spark containers provides enhanced runtime performance in rendering because layout is handled through delegation.

Included in the Spark layout container architecture are two base classes, GroupBase and SkinnableContainerBase. Both handle visual elements, but only the latter provides skinning capabilities for the container. The spark.components.Group and spark.components.DataGroup classes are extensions of GroupBase; both are non-skinnable containers, but they differ in how child elements are declared and represented on their display lists. A Group container holds children that are implementations of IVisualElement and that are added either through MXML markup or through methods of the content API. A DataGroup container uses item rendering for visual elements represented as data items, which are provided as IList implementations. DataGroup also supports virtualization through a layout delegate, which can reduce the rendering time of its visual elements at runtime. spark.components.SkinnableContainer (of which Application is a subclass) and spark.components.SkinnableDataContainer are equivalent to the GroupBase-based containers yet support skinning for the visual makeup of the container itself.

Layout containers—those that handle the size and position of their child elements—have equal parity between the MX and Spark container sets. Though using Spark layout containers is recommended because of their improved performance and level of abstraction, there are no Spark equivalents of the MX navigational containers (such as Accordion and ViewStack). If application requirements dictate the use of navigational containers, the Flex Framework allows for intermixing of MX and Spark containers in the same application. Note, however, that a Spark container cannot be added directly as a child to a MX navigator container. To add a Spark layout container to a MX navigator container, you must wrap the Spark container in a spark.components.NavigatorContent container instance.

Position Children Within a Container

Problem

You want to position child elements of a container using a specified layout.

Solution

Assign a layout from the spark.layouts package to the layout property of the target container and apply constraints on the layout and container as necessary.

Discussion

Spark layout containers, such as spark.components.Group and spark.components.DataGroup, delegate the sizing and positioning of their child elements to LayoutBase-based layouts. As child elements are added to a container, whether declaratively in MXML or programmatically, the management of the display list is handed over to the layout, which renders child elements accordingly. Common layouts, such as those for positioning children vertically or horizontally, are provided in the Flex 4 SDK in the spark.layouts package. Due to the separation of responsibilities between Spark containers and layouts, custom LayoutBase-based layouts can also easily be applied to containers.

The default layout of the base Spark containers is spark.layouts.BasicLayout. When the default BasicLayout is applied to a container, child elements are displayed based on their individual properties, without regard to the size and position of other children:

<s:Group width="300" height="300">

    <s:Button label="button (1)" x="10" y="10" />
    <s:Button label="button (2)" x="10" y="40" />

</s:Group>

Using BasicLayout affords developers fine-grained control over the layout of child elements by enabling them to specify each element’s size and position. Additional constraint properties can be applied to the container as well, to uniformly offset child positions. The child elements of the s:Group container in the following example are displayed exactly as in the previous example, yet the top, left, right, and bottom constraint properties are used:

<s:Group width="300" height="300"
         top="10" left="10" right="10" bottom="10">

    <s:Button label="button (1)" />
    <s:Button label="button (2)" y="30" />

</s:Group>

The HorizontalLayout, VerticalLayout, and TileLayout classes are available to display children sequentially and can be applied using the layout property:

<s:Group width="300" height="300"
         top="10" left="10" right="10" bottom="10">

    <s:layout>
        <s:VerticalLayout gap="10" />
    </s:layout>

    <s:Button label="button (1)" />
    <s:Button label="button (2)" />

</s:Group>

Similar to using constraint properties on a container, distances between the border of the container and the child elements can be specified using the paddingTop, paddingLeft, paddingRight, and paddingBottom properties of the sequential layout classes of the SDK:

<s:Group width="300" height="300">

    <s:layout>
        <s:VerticalLayout gap="10" paddingTop="10" paddingLeft="10"
                          paddingRight="10" paddingBottom="10" />
    </s:layout>

    <s:Button label="button (1)" />
    <s:Button label="button (2)" />

</s:Group>

Some convenience classes are available in the Flex 4 SDK to declare containers with predefined layouts. For example, the children of spark.components.HGroup, spark.components.VGroup, and spark.components.TileGroup containers are laid out sequentially in a predetermined manner. You declare these containers just as you would any other, but the layout property is attributed as read-only:

<s:VGroup width="300" height="300" gap="10"
          paddingTop="10" paddingLeft="10"
          paddingRight="10" paddingBottom="10">

    <s:Button label="button (1)" />
    <s:Button label="button (2)" />

</s:VGroup>

There are many different ways to achieve a desired layout. For instance, all the examples in this recipe will display the same way, although they differ in approach. It is important to remember that containers and layout within the Spark architecture are decoupled, affording you more freedom as to how child elements are visually presented.

Dynamically Add and Remove Children

Problem

You want to add child elements to and remove child elements from a container at runtime.

Solution

Use the addElement(), addElementAt(), removeElement(), and removeElementAt() methods of an IVisualElementContainer implementation.

Discussion

A layout assigned to a Spark container attributes its children as instances of IVisualElement when managing their size and position. Visual components from both the Spark and MX packages are implementations of mx.core.IVisualElement, so you can add MX controls that do not have a Spark equivalent to a Spark layout container. GraphicElement-based elements also implement IVisualElement and afford you a rich set of visual elements to display within a container.

Along with declaring child elements in markup, you can programmatically add children to a container using the addElement() and addElementAt() methods of an IVisualElementContainer implementation. The addElement() method adds a child element to the content layer at an elemental index one higher than any previously assigned. Using addElementAt(), you can set the exact elemental location within the display list. When you add an element using the content API’s add methods, the element’s position depends on the layout delegate specified for the container. The spark.components.Group and spark.components.SkinnableContainer (of which the Spark Application container is a subclass) containers are implementations of IVisualElementContainer.

To add a child at the next available elemental index within the display, use the addElement() method as shown here:

<s:Button label="add" click="{myContent.addElement( new Button() );}" />

<s:Group id="myContent">
    <s:layout>
        <s:VerticalLayout />
    </s:layout>
</s:Group>

The click event of the s:Button control triggers the addition of a new Button child element to the targeted container. Depending on the layout applied to a container, the addition and removal of child elements may or may not affect the position of other child elements. In this example, as each new child element is added, it is offset vertically from the last child element. If BasicLayout were assigned to the container, each additional child element would be presented at a higher z-order within the display.

Using the addElementAt() method, you can set the desired position for the new child within the display list:

<s:Button label="add" click="{myContent.addElementAt( new Button(), 0 );}" />

<s:Group id="myContent">
    <s:layout>
        <s:VerticalLayout />
    </s:layout>
</s:Group>

In this example, as each new Button is added to the container, the control is placed at the elemental index 0. Because VerticalLayout is assigned to the container, each new child added will be placed at the top of the content display, pushing the y-positions of any other child elements downward.

To remove a child element at a specific index, use the removeElementAt() method:

myContent.removeElementAt(0);

Children can also be removed using the removeElement() method, which takes the reference name of a child element as its argument:

myContent.removeElement(myElement);

If the reference name is not available, you can use the getElementAt() method of the content API and specify the index of the child element you wish to remove. The following example uses the getElementAt() method to remove the first element from the container’s display list:

myContent.removeElement( myContent.getElementAt( 0 ) );

The Group and SkinnableContainer layout container classes also support the removal of all child elements using the removeAllElements() method.

As children are added to and removed from a container, the numChildren property of the container is updated. This can help in effectively using the methods of the content API exposed by implementations of IVisualElementContainer.

The following example demonstrates the possible ways to programmatically add child elements to and remove them from a container:

<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">
    <fx:Script>
        <![CDATA[
            import mx.core.IVisualElement;
            import spark.components.Button;

            private function getNewElement():IVisualElement
            {
                var btn:spark.components.Button = new spark.components.Button();
                btn.label = "button " + myContent.numElements;
                return btn;
            }

            private function addFirstElement():void
            {
                myContent.addElementAt( getNewElement(), 0 );
            }

            private function addLastElement():void
            {
                myContent.addElement( getNewElement() );
            }

            private function removeFirstElement():void
            {
                if( myContent.numElements > 0 )
                    myContent.removeElement( myContent.getElementAt( 0 ) );
            }

            private function removeLastElement():void
            {
                if( myContent.numElements > 0 )
                    myContent.removeElementAt( myContent.numElements - 1 );
            }

        ]]>
    </fx:Script>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:Button label="addFirst" click="addFirstElement();" />
    <s:Button label="addLast" click="addLastElement();" />
    <s:Button label="removeFirst" click="removeFirstElement()" />
    <s:Button label="removeLast" click="removeLastElement()" />
    <s:Button label="removeAll" click="myContent.removeAllElements()" />

    <s:Group id="myContent">
        <s:layout>
            <s:VerticalLayout />
        </s:layout>
    </s:Group>

</s:Application>

Reorder Child Elements of a Container

Problem

You want to dynamically reorder the index positions of child elements within a container at runtime.

Solution

Use the setElementIndex() method to change the elemental index of an individual child element, or use the swapElements() and swapElementsAt() methods to transpose the index positions of two children in an IVisualElementContainer implementation, such as a Group or SkinnableContainer. The elemental index corresponds to the order in which a child element is rendered in a layout.

Discussion

As child elements are added to a container, whether programmatically or declaratively in MXML, references to those elements are stored within an Array. The container’s layout delegate uses that Array to display children. A child element can be accessed from this display list using its elemental index, and the order in which the children are displayed can be manipulated by changing these indexes.

In the following example, a child element at the lowest index within the display list is promoted to the highest index using the setElementIndex() method of an IVisualElementContainer implementation, consequentially updating the layout of the other children in the HorizontalLayout:

<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">

    <fx:Script>
        <![CDATA[
            private function reorder():void
            {
                myContent.setElementIndex( myContent.getElementAt(0),
                                           myContent.numElements - 1 );
            }
        ]]>
    </fx:Script>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:Group id="myContent">
        <s:layout>
            <s:HorizontalLayout />
        </s:layout>

        <s:Button id="btn1" label="button 1" />
        <s:DropDownList />
        <s:Button id="btn2" label="button 2" />

    </s:Group>

    <s:Button label="reorder" click="reorder();" />

</s:Application>

The reference to the first element within the display list of the s:Group is accessed using the getElementAt() method of the content API. The child element is moved from the front of the list to the end, and the order in which all child elements are rendered is updated on the layout.

The rendering order can also be manipulated at runtime using the swapElements() method, which takes two references to IVisualElement implementations:

myContent.swapElements( btn1, btn2 );

The swapElementsAt() method swaps the indexes of two elements of a visual container based on the specified index positions:

myContent.swapElements( 0, 2 );

Although a layout will render children sequentially from the list, do not confuse the elemental index of a child with its depth property when considering display. The depth property of an IVisualElement implementation represents the layer position of the child within the layout. Its value is not tied to the length of the child display list, and multiple children can share the same depth. Manipulating the depths therefore differs from manipulating the indexes of children within the display list, in that assigning an index that has already been assigned or is out of the current range (from 0 to the highest assigned index + 1) will throw a runtime exception.

The elemental index and the depth property of a child element may not visually affect the layout of children when using sequential layouts, such as HorizontalLayout and VerticalLayout, but it’s important to understand the concepts of rendering order and layer depth when using BasicLayout or a custom layout. To help you understand how the depth property of an element affects the layout, the following example displays the first declared s:Button control on a layer above the second declared s:Button:

<s:Group id="myContent">

    <s:Button depth="100" label="button 1" />
    <s:Button label="button 2" />

</s:Group>

Although the elemental index of the second s:Button control in the display list is greater than that of the first, the first s:Button is rendered before the second and placed on a higher layer within the default BasicLayout due to the assigned depth property value. The default value of the depth property is 0.

Display Children Using Data Items

Problem

You want to supply to a container an array of data items to be visually represented using item renderers.

Solution

Use the DataGroup container, and set the dataProvider property to an IList implementation and the itemRenderer property to the qualified class name of an IDataRenderer implementation.

Discussion

The DataGroup layout container utilizes item rendering for visual elements represented as data items. Unlike with the Group container, which handles visual elements declared directly in MXML or through methods of the content API, an IList implementation (mx.collections.ArrayCollection or mx.collections.XMLListCollection, for example) is supplied to a DataGroup container, and an item renderer handles the visual representation of each data item in the collection. Included in the Flex 4 SDK are two convenient item renderer classes that can be used to easily present data items visually: spark.skins.DefaultItemRenderer presents data textually, while spark.skins.DefaultComplexItemRenderer renders data items that are implementations of IVisualElement, such as the components from the Spark and MX sets.

In the following example, an array of textual data is supplied to a DataGroup and rendered using the DefaultItemRenderer:

<fx:Declarations>
    <fx:String id="txt">
        Lorem ipsum dolor sit amet consectetur adipisicing elit.
    </fx:String>
</fx:Declarations>

<s:DataGroup itemRenderer="spark.skins.spark.DefaultItemRenderer">

    <s:layout>
        <s:HorizontalLayout />
    </s:layout>

    <s:dataProvider>
        <s:ArrayCollection source="{txt.split(' ')}" />
    </s:dataProvider>

</s:DataGroup>

The data items in the dataProvider of the DataGroup are positioned horizontally from left to right and rendered using the supplied itemRenderer. The value of the itemRenderer property, a qualified class name, is used internally by the DataGroup container to create a new instance of the specified class for each data item in the collection. If the class is an IDataRenderer implementation, the data property of the implementation is updated with the item within the collection at the time of instantiation.

The DefaultComplexItemRenderer class can also be used to easily render IVisualElement data within a DataGroup container:

<fx:Declarations>
    <fx:String id="txt">
        Lorem ipsum dolor sit amet consectetur adipisicing elit.
    </fx:String>
</fx:Declarations>

<s:DataGroup itemRenderer="spark.skins.spark.DefaultComplexItemRenderer">

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:dataProvider>
        <s:ArrayCollection>
            <s:Label text="Using DefaultComplexItemRenderer." />
            <s:Button label="button 1" />
            <s:DropDownList dataProvider="{new ArrayCollection(txt.split(' '))}" />
            <s:CheckBox selected="true" />
            <mx:Button label="button 2" />
        </s:ArrayCollection>
    </s:dataProvider>

</s:DataGroup>

When the itemRenderer property is set to the DefaultComplexItemRenderer class, the DataGroup internally determines each data item to be an IVisualElement implementation and renders the items directly on the display.

Unlike from a Group container, child elements cannot be accessed directly from a DataGroup container. Although the child elements of both a Group and a DataGroup are IVisualElement implementations, the Group class exposes a content API through its implementation of the IVisualElementContainer interface that enables you to dynamically add, remove, and set the indexes of elements directly in the container. The display list of a DataGroup can be altered using the IList instance set as the dataProvider property value for the container. Because the dataProvider property supports binding, the collection can be affected directly at runtime to update the display of visual elements dynamically.

As item renderers are added to and removed from the display list of a DataGroup container, RendererExistenceEvent objects are dispatched. The properties of a RendererExistenceEvent instance correspond to the item renderer instance, the data supplied to the item renderer, and the elemental index within the display list at which it resides.

The following example demonstrates how to dynamically change the display list of a DataGroup container and listen for the addition and removal of item renderer instances:

<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">
    <fx:Script>
        <![CDATA[
            import spark.events.RendererExistenceEvent;
            import mx.collections.ArrayCollection;

            private function itemAdded( evt:RendererExistenceEvent ):void
            {
                trace( "Item Added: " + evt.index + " : " + evt.data +
                        " : " + evt.renderer );
            }
            private function itemRemoved( evt:RendererExistenceEvent ):void
            {
                trace( "Item Removed: " + evt.index + " : " + evt.data +
                        " : " + evt.renderer );
            }

            private function addItem():void
            {
                if( collection.length > 0 )
                    myContent.dataProvider.addItem( collection.removeItemAt(0) );
            }
            private function removeItem():void
            {
                if( myContent.dataProvider.length > 0 )
                {
                    var item:Object = myContent.dataProvider.removeItemAt(
                            myContent.dataProvider.length - 1 );
                    collection.addItem( item );
                }
            }
        ]]>
    </fx:Script>

    <fx:Declarations>
        <fx:String id="txt">
            Lorem ipsum dolor sit amet consectetur adipisicing elit.
        </fx:String>
        <s:ArrayCollection id="collection">
            <s:Label text="Using DefaultComplexItemRenderer." />
            <s:Button label="button 1" />
            <s:DropDownList dataProvider="{new ArrayCollection(txt.split(' '))}" />
            <s:CheckBox selected="true" />
            <mx:Button label="button 2" />
        </s:ArrayCollection>
    </fx:Declarations>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:DataGroup id="myContent"
                 rendererAdd="itemAdded(event);"
                 rendererRemove="itemRemoved(event);"
                 itemRenderer="spark.skins.spark.DefaultComplexItemRenderer">

        <s:layout>
            <s:HorizontalLayout />
        </s:layout>

        <s:dataProvider>
            <s:ArrayCollection />
        </s:dataProvider>

    </s:DataGroup>

    <s:Button label="add" click="addItem();" />
    <s:Button label="remove" click="removeItem();" />

</s:Application>

Use a Custom Item Renderer in a DataGroup

Problem

You want to use a custom item renderer to render data items visually within a DataGroup.

Solution

Create a custom component that implements the IVisualElement and IDataRenderer interfaces and supply the class as the itemRenderer property value for a DataGroup container.

Discussion

The DataGroup container handles making visual representations of data items. In order to properly render visual elements in the display list of a DataGroup and for its layout delegate to handle the size and position of those children, item renderers need to implement at least two interfaces: IVisualElement and IDataRenderer. The layout delegate of a container attributes the visual elements as IVisualElement implementations. The implementation type is also attributed when dispatching RendererExistenceEvents. Implementing the IDataRenderer interface for a custom item renderer exposes the data property, which is used internally by the DataGroup to supply data items to the renderer.

Along with the convenient DefaultItemRenderer and DefaultComplexItemRenderer classes provided in the Flex 4 SDK, Adobe provides a convenience base class for item renderers to be used with DataGroup containers. The item renderer base class—aptly named ItemRenderer—is an extension of spark.components.DataRenderer, which is a Group container that exposes a data property, fulfilling the contract of an item renderer being an implementation of IVisualElement and IDataRenderer. In addition, spark.components.supportClasses.ItemRenderer also provides extra support for styling, states, and event handling and is a good jumping-off point for creating a custom item renderer.

The following custom item renderer is an extension of ItemRenderer and displays the firstName and lastName property values of the supplied data item:

<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                xmlns:s="library://ns.adobe.com/flex/spark"
                xmlns:mx="library://ns.adobe.com/flex/mx"
                width="100%" height="24">

    <s:states>
        <s:State name="normal" />
        <s:State name="hovered" />
    </s:states>

    <s:Rect width="100%" height="100%">
        <s:fill>
            <s:SolidColor color="0xDDDDDD" color.hovered="0xDDDDFF" />
        </s:fill>
    </s:Rect>

    <s:Group width="100%" height="100%">

        <s:layout>
            <s:HorizontalLayout verticalAlign="middle" />
        </s:layout>

        <s:Label text="{data.lastName}," />
        <s:Label text="{data.firstName}" />

    </s:Group>

</s:ItemRenderer>

Because ItemRenderer is an extension of Group, visual elements can be declared directly on the display for visual representation. In this example, two s:Label components are laid out horizontally and placed above a background s:Rect graphic element that updates its color property based on state. When creating a custom item renderer by extending the ItemRenderer class, remember that the item renderer manages its current state internally. You can override the getCurrentRendererState() method to specify how the current state is determined, but in general the extending class will need to at least declare a normal state.

In this example, the background color is updated in response to the current state using dot notation for color property values inline. The CustomItemRenderer created in the previous example is applied to a DataGroup using the itemRenderer property to present visual representations of data from an IList collection:

<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:Script>
        <![CDATA[
            private var authors:XML =
                    <authors>
                        <author>
                            <firstName>Josh</firstName>
                            <lastName>Noble</lastName>
                        </author>
                        <author>
                            <firstName>Garth</firstName>
                            <lastName>Braithwaite</lastName>
                        </author>
                        <author>
                            <firstName>Todd</firstName>
                            <lastName>Anderson</lastName>
                        </author>
                    </authors>;
        ]]>
    </fx:Script>

    <s:DataGroup width="200"
                 itemRenderer="com.oreilly.f4cb.CustomItemRenderer">

        <s:layout>
            <s:VerticalLayout />
        </s:layout>

        <s:dataProvider>
            <s:XMLListCollection source="{authors..author}" />
        </s:dataProvider>

    </s:DataGroup>

</s:Application>

As the collection of XML data is iterated through, a new instance of CustomItemRenderer is created and its data property is attributed to the current data item in the iteration.

If a namespace is declared for the package where a custom item renderer resides, the itemRenderer property of the DataGroup container can also be set using MXML markup, as in the following example:

<s:DataGroup width="200">

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:dataProvider>
        <s:XMLListCollection source="{authors..author}" />
    </s:dataProvider>

    <s:itemRenderer>
        <fx:Component>
            <f4cb:CustomItemRenderer />
        </fx:Component>
    </s:itemRenderer>

</s:DataGroup>

Use Multiple Item Renderers in a DataGroup

Problem

You want to use more than one type of item renderer for a collection of data items in a DataGroup container.

Solution

Use the itemRendererFunction property to specify a callback method that will return an item renderer based on the data item.

Discussion

When working with a collection of data items in a DataGroup container, the itemRenderer and itemRendererFunction properties are used to specify the visual representation of elements. Using the itemRenderer property, instances of a single item renderer class are created and optionally recycled when rendering data elements. Assigning a factory method to the itemRendererFunction property of the DataGroup affords more control over the type of item renderer used for a data element.

The return type of the method specified for the itemRendererFunction property is IFactory. As item renderers are created for data elements internally within the DataGroup, item renderers are instantiated using the newInstance() method of the IFactory implementation that is returned. The following is an example of a method attributed as an itemRendererFunction for a DataGroup container:

private function getItemRenderer( item:Object ):IFactory
{
    var clazz:Class;
    switch( item.type )
    {
        case "normal":
            clazz = NormalRenderer;
            break;
        case "special":
            clazz = SpecialRenderer;
            break;
    }
    return new ClassFactory( clazz );
}

Depending on the type of data element provided to the itemRendererFunction, a specific item renderer is returned. Any method specified as an itemRendererFunction must adhere to the method signature shown in this example. The single argument, of type Object, is the current data element within the collection being iterated through and is attributed as the dataProvider property of the DataGroup. The return type, as mentioned previously, is of type IFactory. The ClassFactory class is an implementation of IFactory, and class declarations are passed to its constructor and used to instantiate new instances of the class when the newInstance() method is invoked.

The following example demonstrates setting a method delegate for the itemRendererFunction property of a DataGroup container:

<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">

    <fx:Declarations>
        <s:ArrayCollection id="animalList">
            <fx:Object type="dog" name="Walter" />
            <fx:Object type="cat" name="Zoe" />
            <fx:Object type="dog" name="Cayuga" />
            <fx:Object type="dog" name="Pepper" />
        </s:ArrayCollection>
    </fx:Declarations>

    <fx:Script>
        <![CDATA[
            import com.oreilly.f4cb.DogItemRenderer;
            import com.oreilly.f4cb.CatItemRenderer;
            import spark.skins.spark.DefaultItemRenderer;

            private function getItemRenderer( item:Object ):IFactory
            {
                var clazz:Class;
                switch( item.type )
                {
                    case "dog":
                        clazz = DogItemRenderer;
                        break;
                    case "cat":
                        clazz = CatItemRenderer;
                        break;                    default:
                        clazz = DefaultItemRenderer;
                        break;
                }
                return new ClassFactory( clazz );
            }
        ]]>
    </fx:Script>

    <s:DataGroup width="200" height="150"
                 dataProvider="{animalList}"
                 itemRendererFunction="getItemRenderer">

        <s:layout>
            <s:VerticalLayout paddingTop="5" paddingBottom="5"
                              paddingLeft="5" paddingRight="5" />
        </s:layout>

    </s:DataGroup>

    <s:Rect width="200" height="150">
        <s:stroke>
            <s:SolidColorStroke color="#000000" />
        </s:stroke>
    </s:Rect>

</s:Application>

As the items of the dataProvider collection are iterated through, the getItemRenderer() factory method is invoked and, based on the type property of the passed-in data item, a custom item renderer is returned. If the data type is not found, the spark.skins.spark.DefaultItemRenderer class of the Flex 4 SDK is returned.

It is important to note that, though a DataGroup can support virtualization through its layout delegate, an item renderer returned using itemRendererFunction is instantiated each time the delegate method is invoked and not inherently recycled as elements are added to and removed from the display list.

Enable Scrolling in a Container

Problem

You want to add scrolling capabilities to a container whose child elements are positioned outside of the defined viewport bounds.

Solution

Wrap a Group or DataGroup container in an instance of spark.components.Scroller and define the dimensions and clipAndEnableScrolling property of the container, or assign a container as the viewport property of a spark.components.ScrollBar instance.

Discussion

Unlike MX containers, which support scrolling internally, the separation of responsibilities within the Spark architecture provides more lightweight containers and affords more control over delegating tasks. Within the new Spark paradigm, you can assign specific controls that handle navigating within a container. The spark.components.supportClasses.GroupBase class, which both DataGroup and Group extend, is an implementation of the IViewport interface. By default, the clipAndEnableScrolling property of a GroupBase-based container is set to false and the container renders child elements outside of any specified bounds. Setting the clipAndEnableScrolling property to true and wrapping the IViewport instance in a Scroller component renders child elements within a defined area and updates the read-only contentWidth and contentHeight properties of the container to the specified dimensions.

To enable scrolling for an IViewport, the container can be wrapped in a Scroller instance with the declared child container attributed as the viewport property value:

<fx:Declarations>
    <fx:String id="txt">
        Lorem ipsum dolor sit amet consectetur adipisicing elit.
    </fx:String>
</fx:Declarations>

<s:Scroller>

    <s:DataGroup width="100" height="100"
                 clipAndEnableScrolling="true"
                 itemRenderer="spark.skins.spark.DefaultItemRenderer">

        <s:layout>
            <s:VerticalLayout />
        </s:layout>

        <s:dataProvider>
            <s:ArrayCollection source="{txt.split(' ')}" />
        </s:dataProvider>

    </s:DataGroup>

</s:Scroller>

Any data elements that are rendered outside of the viewport bounds of a container explicitly set using the width and height properties are displayed based on the scrolling properties of the s:Scroller component. Based on the positions of the child elements within the container viewport, a VScrollBar control and a HScrollBar control are added to the display. The scroll bars are positioned at the viewport’s width and height property values, unless you specify custom values for the Scroller instance’s width and height properties.

Containers that support skinning, such as BorderContainer, SkinnableContainer, and SkinnableDataContainer, do not implement the IViewport interface. However, the content layer to which child elements are added for each skinnable container is attributed as an IViewport implementation. As such, you have a couple of options for enabling scrolling of child content in a skinnable container.

The skin part that serves as the content layer for a BorderContainer and a SkinnableContainer is the contentGroup. When children are declared for a skinnable container directly in MXML markup, the child elements are added and laid out within the content layer. One way to enable scrolling of the content is to declare an IViewport implementation wrapped in a Scroller component as the only child of the skinnable container, as in the following example:

<s:BorderContainer width="120" height="100" backgroundColor="#FFFFFF">
    <s:Scroller width="100%" height="100%">
        <s:Group>
            <s:layout>
                <s:VerticalLayout horizontalAlign="justify"
                                  clipAndEnableScrolling="true" />
            </s:layout>
            <s:Button label="button (1)" />
            <s:Button label="button (2)" />
            <s:Button label="button (3)" />
            <s:Button label="button (4)" />
            <s:Button label="button (5)" />
            <s:Button label="button (6)" />
        </s:Group>
    </s:Scroller>
</s:BorderContainer>

The Scroller-wrapped Group declared as the only child for the BorderContainer is added as the only child within the contentGroup for the container. Scrolling is not applied to the contentGroup layer specifically, but because its only child element is a Group container wrapped in a Scroller whose dimensions are updated to equal the dimensions of the skinnable container, it appears as if the content for the BorderContainer is made scrollable.

Another approach to providing scrolling for the content of a skinnable container is to apply a custom skin to the container that wraps its content-layer skin part in a Scroller. This is a custom skin for a SkinnableDataContainer:

<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        name="CustomScrollableSkin">

    <fx:Metadata>
        <![CDATA[
            [HostComponent("spark.components.SkinnableDataContainer")]
        ]]>
    </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:DataGroup id="dataGroup"
                     left="0" right="0" top="0" bottom="0"
                     minWidth="0" minHeight="0" />
    </s:Scroller>

</s:Skin>

The CustomScrollableSkin fulfills a contract to serve as a skin to a SkinnableDataContainer by declaring the [HostComponent] metadata and required states. Also declared is the required skin part, dataGroup, which is the content layer for item renderers and is wrapped in a s:Scroller component to enable scrolling within the container.

The custom skin is supplied to the skinnable container as a qualified class name attributed to the skinClass property, as in the following example:

<fx:Declarations>
    <fx:String id="txt">
        Lorem ipsum dolor sit amet consectetur adipisicing elit.
    </fx:String>
</fx:Declarations>

<s:SkinnableDataContainer width="120" height="100"
                          itemRenderer="spark.skins.spark.DefaultItemRenderer"
                          skinClass="com.oreilly.f4cb.CustomScrollableSkin">
    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:dataProvider>
        <s:ArrayCollection source="{txt.split(' ')}" />
    </s:dataProvider>
</s:SkinnableDataContainer>

The ability to use a Scroller to enable scrolling of content within containers is a major convenience. The skin layout of a Scroller component is a private implementation, however, and the skin parts for the scroll bars are considered read-only. To have more control over the layout and the relationship between a viewport and scroll bars, add a ScrollBar-based control to the display list directly and assign an instance of a target container as the viewport property value:

<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">

    <fx:Declarations>
        <fx:String id="txt">
            Lorem ipsum dolor sit amet consectetur adipisicing elit.
    </fx:String>
    </fx:Declarations>

    <s:layout>
        <s:HorizontalLayout />
    </s:layout>

    <s:DataGroup id="group" width="100" height="100"
                 clipAndEnableScrolling="true"
                 itemRenderer="spark.skins.spark.DefaultItemRenderer">

        <s:layout>
            <s:VerticalLayout />
        </s:layout>

        <s:dataProvider>
            <s:ArrayCollection source="{txt.split(' ')}" />
        </s:dataProvider>

    </s:DataGroup>

    <s:VScrollBar viewport="{group}" height="100" />

</s:Application>

In this example, a DataGroup is attributed as the IViewport implementation instance of a s:VScrollBar control. The thumb size of the scroll bar is based on the height of the scroll bar and the contentHeight value of the target container. As the scroll position changes on the scroll bar, the verticalScrollPosition value is passed down to the IViewport implementation and handed to the layout delegate for the container.

While wrapping a container with a Scroller internally detects the need for scroll bars based on the content of the container and dimensions of its viewport, with this approach you have less control over the layout with relation to the target viewport. Targeting a container using scroll bars declared directly on the display list allows more control over the layout, but its visibility is not inherently set based on the contentWidth and contentHeight of a viewport. The visibility of a scroll bar control can, however, be determined based on the container’s dimensions and its viewport counterpart value, as in the following example for updating the visibility of a s:VScrollBar control:

<s:VScrollBar viewport="{group}" height="100"
              visible="{group.height &lt; group.contentHeight}" />

When the clipAndEnableScrolling property of an IViewport implementation is set to true, the read-only contentWidth and contentHeight properties are set based on the bounds of the container’s display list. By comparing the defined height property of the viewport with the contentHeight, the visibility and necessity of the VScrollBar control in this example can be determined.

Scale Children of a Container

Problem

You want to resize, scale, and lay out the child elements of a container based on the dimensions of the container.

Solution

Use the resizeMode property of a GroupBase-based container.

Discussion

Layout delegates applied to Group and DataGroup containers have properties that modify the layout and size of child elements directly or through transformations. Additionally, the layout of children can be modified based on the resizeMode property value of GroupBase-based containers that take the size of the container into consideration. The default value of the resizeMode property is noScale, which specifies that the container resizes itself and children are subsequently resized based on the properties of the layout delegate. The child content of a container can be scaled uniformly by setting the resizeMode property value to scale, which bases the layout of its children on the measured size of the container.

The following example demonstrates switching between the two resize modes within a Group container:

<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">

    <fx:Script>
        <![CDATA[
            import mx.events.SliderEvent;
            import spark.components.ResizeMode;

            private function toggleResizeMode():void
            {
                group.resizeMode = ( group.resizeMode == ResizeMode.NO_SCALE )
                                    ? group.resizeMode = ResizeMode.SCALE
                                    : group.resizeMode = ResizeMode.NO_SCALE;
            }
        ]]>
    </fx:Script>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:Group id="group"
             width="{slider.value}" height="{slider.value}"
             resizeMode="{ResizeMode.NO_SCALE}">

        <s:layout>
            <s:TileLayout orientation="columns" />
        </s:layout>

        <s:Button label="button" />
        <s:Rect width="100" height="100">
            <s:fill>
                <s:SolidColor color="#DDDDDD" />
            </s:fill>
        </s:Rect>
        <s:DropDownList />
        <s:Ellipse>
            <s:fill>
                <s:SolidColor color="#FF5500" />
            </s:fill>
        </s:Ellipse>

    </s:Group>

    <s:HSlider id="slider"
               width="120"
               minimum="100" maximum="300"
               value="300"
               liveDragging="true" />

    <s:Button label="toggle resize" click="toggleResizeMode();" />

</s:Application>

As the value of the s:HSlider control changes, the dimensions of the container are reflected through binding. The resizeMode is changed when a click event is received from the s:Button control, swapping between the scale and noScale modes enumerated in the ResizeMode class.

The TileLayout delegate applied to the Group container has an orientation property value specifying that children should be laid out in columns based on the dimensions of the container. As the width and height of the container change, the layout is based on the amount of space available within the container to display children within a grid of columns and rows. With the resizeMode set to noScale, children are sized based on their defined or inherited properties within the layout. When the resizeMode property is set to scale, the child elements are scaled to fill the dimensions of the container, while still adhering to the column/row rules of the layout delegate.

Apply Skins to a Container

Problem

You want to customize the look and feel of a container that holds visual elements or data items.

Solution

Use either a SkinnableContainer or a BorderContainer as a container for visual child elements and a SkinnableDataContainer as a container for data items, and modify the available style properties.

Discussion

The Group and DataGroup containers are considered lightweight containers and as such do not support skinning or expose style properties. To customize the look of a container, the Flex 4 SDK offers the SkinnableContainer, BorderContainer, and SkinnableDataContainer classes. Which you use depends on the type of content provided to the container. The SkinnableContainer and BorderContainer take instances of IVisualElement as child elements; think of them as Group containers that support skinning. The SkinnableDataContainer can be considered a DataGroup container that supports skinning and uses item renderers to display visual representations of data items.

BorderContainer is actually a subclass of SkinnableContainer and is a convenient container to use if you want to apply border and background styles directly without applying a custom skin. You can set border styles (such as cornerRadius and borderColor) inline within the MXML declaration for the container, or through Cascading Style Sheet (CSS) style declarations. The following example demonstrates setting the border styles of a BorderContainer for IVisualElement children:

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderColor="#000000"
                   borderWeight="2" borderStyle="inset">

    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />

</s:BorderContainer>

Alternatively, you can supply an IStroke instance for the borderStroke property of a BorderContainer. The following example sets a borderStroke on a BorderContainer through MXML markup:

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderStyle="inset">

    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />

    <s:borderStroke>
        <s:SolidColorStroke color="#0000" weight="2" />
    </s:borderStroke>

</s:BorderContainer>

The s:SolidColorStroke element supplied as the borderStroke for the BorderContainer overrides any previously declared color or weight style properties. However, because the IStroke interface does not expose a cornerRadius or borderStyle property, some border style properties need to be provided directly to the BorderContainer if they are desired. Because BorderContainer is an extension of SkinnableContainer, skinning is also supported along with the border convenience styles.

Custom skin classes are set on the SkinnableContainerBase-based containers (such as BorderContainer) through the skinClass property, whose value can be set either inline using MXML markup or via CSS, because skinClass is considered a style property. When you create a custom skin for a container, the skin is entering into a contract with the container to provide the necessary skin parts and states for the host. These skin parts are referenced using an agreed-upon id property value and, depending on the type of skinnable container, relate to content layers for visual elements.

To create a custom skin for a skinnable container, extend the spark.skins.SparkSkin class and declare the HostComponent metadata and necessary state and skin part elements. The following is an example of a custom skin fulfilling a contract to be applied to a SkinnableContainer:

<s:SparkSkin name="CustomGroupSkin"
             xmlns:fx="http://ns.adobe.com/mxml/2009"
             xmlns:s="library://ns.adobe.com/flex/spark"
             xmlns:mx="library://ns.adobe.com/flex/mx">

    <fx:Metadata>
        [HostComponent("spark.components.SkinnableContainer")]
    </fx:Metadata>

    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <s:Rect width="100%" height="100%">
        <s:fill>
            <s:LinearGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:LinearGradient>
        </s:fill>
        <s:fill.disabled>
            <s:RadialGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:RadialGradient>
        </s:fill.disabled>
    </s:Rect>

    <s:Group id="contentGroup"
             width="100%" height="100%"
             left="10" right="10" top="10" bottom="10">

        <s:layout>
            <s:VerticalLayout horizontalAlign="justify" />
        </s:layout>

    </s:Group>

</s:SparkSkin>

The CustomGroupSkin declares the type of container component that the skin will be applied to within the [HostComponent] metatag. In this example the host component is a SkinnableContainer, and as such contains a Group container with the id property value of contentGroup. The contentGroup property of SkinnableContainer is considered a skin part and represents the content layer on which visual child elements are drawn. Along with the host component and contentGroup, contractual states are declared to represent the enabled and disabled visual states of the container. You can declare additional states as needed, but at a minimum, normal and disabled need to be added to the available states to support the enabled property of the container. Styles can be applied to elements based on these states, as is shown using inline dot notation for the fill type of the s:Rect element.

To apply the custom skin to a SkinnableContainer instance, set the skinClass style property to a Class reference either inline or using CSS. The following example applies the CustomGroupSkin skin inline using a fully qualified class name:

<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">

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:SkinnableContainer id="container"
                          width="200" height="200"
                          skinClass="com.oreilly.f4cb.CustomGroupSkin">

        <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
        <s:Button label="button (1)" />
        <s:DropDownList />

    </s:SkinnableContainer>

    <s:Button label="enable container"
              click="{container.enabled=!container.enabled}" />

</s:Application>

When applying a custom skin, the layout can be set within the skin class (as in this example). It should be noted, however, that if a layout is applied to a container directly in MXML markup, that layout will override any layout supplied to the content layer declared in the skin.

Creating a custom skin for a SkinnableDataContainer that uses data elements to represent children is similar to creating a custom skin for a SkinnableContainer instance. The difference between the two involves the type of host component declaration and skin part reference. The following custom skin declares the host component references as the SkinnableDataContainer class and contains a DataGroup container with the reference id of dataGroup:

<s:SparkSkin name="CustomDataGroupSkin"
             xmlns:fx="http://ns.adobe.com/mxml/2009"
             xmlns:s="library://ns.adobe.com/flex/spark"
             xmlns:mx="library://ns.adobe.com/flex/mx">

    <fx:Metadata>
        [HostComponent("spark.components.SkinnableDataContainer")]
    </fx:Metadata>

    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <s:Rect width="100%" height="100%">
        <s:fill>
            <s:LinearGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:LinearGradient>
        </s:fill>
        <s:fill.disabled>
            <s:RadialGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:RadialGradient>
        </s:fill.disabled>
    </s:Rect>

    <s:Scroller width="100%" height="100%">
        <s:DataGroup id="dataGroup"
                     width="100%" height="100%">

            <s:layout>
                <s:VerticalLayout paddingLeft="10" paddingRight="10"
                                  paddingTop="10" paddingBottom="10" />
            </s:layout>

         </s:DataGroup>
    </s:Scroller>

</s:SparkSkin>

The CustomDataGroupSkin fulfills a contract with SkinnableDataContainer to provide a DataGroup instance as the content layer for data elements supplied to the skinnable container. With the host component metatag and necessary states declared, the custom skin is applied to a SkinnableDataContainer through the skinClass style property:

<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">

    <fx:Declarations>
        <fx:String id="txt">
            Lorem ipsum dolor sit amet consectetur adipisicing elit.
        </fx:String>
    </fx:Declarations>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:SkinnableDataContainer id="container" width="200" height="200"
                              itemRenderer="spark.skins.spark.DefaultItemRenderer"
                              skinClass="com.oreilly.fcb4.CustomDataGroupSkin">

        <s:dataProvider>
            <s:ArrayCollection 
            id="collection" source="{txt.split(' ')}" />
        </s:dataProvider>

    </s:SkinnableDataContainer>

    <s:Button label="enable container"
              click="{container.enabled=!container.enabled}" />

</s:Application>

The full extent of skinning and styling possibilities that the Flex 4 SDK provides is discussed in Chapter 6, Skinning and Styles, but these examples demonstrate the basic contractual agreement that custom skins must fulfill when working with SkinnableContainerBase-based containers.

Set the Background Image of a BorderContainer

Problem

You want to set the background image of a BorderContainer and control how the graphic is applied as a fill.

Solution

Use either the background image style properties of the BorderContainer or the backgroundFill property to apply a BitmapFill directly.

Discussion

The BorderContainer is a convenience container for IVisualElement child elements that exposes style properties pertaining to the border and background displays of a container not found directly on its superclass, the SkinnableContainer. When using a SkinnableContainer, border and background styles are handled by a skin class applied to the container. The BorderContainer class provides style properties for the border and background that can be set inline in MXML markup or through CSS; it also provides two properties, borderStroke and backgroundImage, that allow you to apply styles using graphic elements.

The following example demonstrates setting the style properties for a background image inline on a BorderContainer:

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderStyle="inset"
                   backgroundImage="@Embed(source='background.jpg')"
                   backgroundImageFillMode="{BitmapFillMode.REPEAT}">

    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />

</s:BorderContainer>

Alternatively, the style properties of a BorderContainer can be applied using CSS and set using the styleName property:

<fx:Style>
    @namespace s "library://ns.adobe.com/flex/spark";
    @namespace mx "library://ns.adobe.com/flex/mx";

    .imageBorder {
        backgroundImage: Embed(source='background.jpg');
        backgroundImageFillMode: repeat;
    }
</fx:Style>

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderStyle="inset"
                   styleName="imageBorder">

    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />

</s:BorderContainer>

In the previous examples, an image from the local resource is embedded and supplied as the backgroundImage style property value. The fill mode for the image is set using the backgroundImageFillMode style property, which can take three values—clip, scale, and repeat—all of which are enumerated properties of the BitmapFillMode class. When a background image is styled with clipping, the image is rendered at its original dimensions within the container. A background image with a scale value for the fill mode is scaled to the dimensions of the container, and the repeat value repeats the image in a grid to fill the container region. Each background image fill mode also takes into account the container display when the cornerRadius style property is set.

Background image styles can also be applied using the backgroundFill property of a BorderContainer. The backgroundFill property takes an implementation of the IFill interface and will override any background style properties that have been set. You can assign to the backgroundFill property a BitmapFill instance that exposes a fillMode property that you can use to apply the same styling you might do with the style properties of a BorderContainer:

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderStyle="inset">

    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />

    <s:backgroundFill>
        <s:BitmapFill source="@Embed('background.jpg')"
                      fillMode="{BitmapFillMode.REPEAT}" />
    </s:backgroundFill>

</s:BorderContainer>

Use a Control Bar

Problem

You want to add a control bar to an Application or Panel container.

Solution

Use controlBarContent to add visual elements to a control bar group, and use controlBarLayout to define the layout for the control bar group.

Discussion

Both the Application and Panel containers support the addition of a control bar group by declaring an array of IVisualElement instances as the value of the controlBarContent property. The contentBarGroup property is a Group container whose default property value for child elements is the controlBarContent property. Visual elements declared in MXML markup are added to the group to display a control bar, as in the following example:

<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">

    <fx:Declarations>
        <s:ArrayCollection 
                 id="authors" source="{['Josh Noble',
                'Garth Braithwaite', 'Todd Anderson']}" />
    </fx:Declarations>

    <s:Panel title="Control Bar Example" width="300" height="120">

        <s:controlBarContent>
            <s:DropDownList id="authorCB" width="120" dataProvider="{authors}" />
            <s:Button label="select"
                      click="{printField.text=authorCB.selectedItem}" />
        </s:controlBarContent>

        <s:Group width="100%" height="100%">
            <s:Label id="printField" horizontalCenter="0" verticalCenter="0" />
        </s:Group>

    </s:Panel>

</s:Application>

The s:DropDownList and s:Button controls are displayed in a control bar on the bottom region of the Panel container, and upon the receipt of a click event from the Button, the selected item from the DropDownList is printed in the s:Label component of the content group for the Panel.

Panel and Application each have a default layout for the optional control bar when it is added to the display list that has predefined constraints and displays child elements in a horizontal sequence. The layout of the control bar can be modified using the controlBarLayout property:

<s:controlBarLayout>
    <s:VerticalLayout horizontalAlign="justify" />
</s:controlBarLayout>

Any LayoutBase-based layout can be attributed as a controlBarLayout. In this example, all IVisualElement instances from the controlBarContent list will be added to the control bar group in a vertical sequence and resized to the elemental region width-wise within the group.

When a control bar is added to an Application, it resides by default in the upper region of the Application container. The contentBarGroup property is considered a skin part, allowing for the location and style of the control bar to be modified by setting a custom skin.

Modify Layout of Content Elements in a Panel

Problem

You want to modify the default layout of the content elements in the display list of a Panel container.

Solution

Create a custom skin class that fulfills the contract of a skin for a Panel component and set it as the skinClass property value. Within the custom skin, modify the layout and declaration of skin parts to change the display of the control bar and content.

Discussion

By default, the layout property of a Panel is applied to the contentGroup skin part, which is the group of visual elements that are displayed between the title bar and optional control bar of the container. The Panel’s default skin class handles the layout of the title bar, content group, and control bar, ordering them in a top-down fashion. You can modify the position and size of each of these content elements by providing a custom skin class to the Panel using the skinClass property.

When creating a custom skin, you must ensure that the skin adheres to a contractual agreement with the target host component and declares any required states and content references, referred to as skin parts. The titleDisplay and contentGroup skin parts refer to the title bar and main content display regions of a panel. The optional contentBarGroup skin part refers to the control bar. When creating a custom skin class for a Panel container that supports a control bar, you must declare all three skin parts as well as the corresponding states of normal, disabled, normalWithControlBar, and disabledWithControlBar, as in the following example:

<s:SparkSkin name="CustomPanelSkin"
             xmlns:fx="http://ns.adobe.com/mxml/2009"
             xmlns:s="library://ns.adobe.com/flex/spark"
             xmlns:mx="library://ns.adobe.com/flex/mx">

    <s:states>
        <s:State name="normal" />
        <s:State name="normalWithControlBar" />
        <s:State name="disabled" />
        <s:State name="disabledWithControlBar" />
    </s:states>

    <fx:Metadata>
        [HostComponent("spark.components.Panel")]
    </fx:Metadata>

    <s:RectangularDropShadow id="shadow" alpha="0" />

    <!-- Border -->
    <s:Rect left="0" right="0" top="0" bottom="0">
        <s:stroke>
            <s:SolidColorStroke color="0" alpha="0.5" weight="1" />
        </s:stroke>
    </s:Rect>

    <!-- Background -->
    <s:Rect id="background" left="1" top="1" right="1" bottom="1">
        <s:fill>
            <s:SolidColor color="0xFFFFFF" />
        </s:fill>
    </s:Rect>

    <!-- Content -->
    <s:Group width="100%" height="100%" top="1" left="1" right="1" bottom="1">

        <s:layout>
            <s:VerticalLayout gap="0" horizontalAlign="justify" />
        </s:layout>

        <!-- Control Bar -->
        <s:Group>
            <s:Rect width="100%" height="30">
                <s:fill>
                    <s:SolidColor color="0xCCCCCC" />
                </s:fill>
            </s:Rect>
            <s:Group id="controlBarGroup"
                     left="5" right="5" top="5" bottom="5"
                     minWidth="0" minHeight="0" height="30">
                <s:layout>
                    <s:HorizontalLayout />
                </s:layout>
            </s:Group>
        </s:Group>

        <!-- Content -->
        <s:Scroller width="100%" height="100%" >
            <s:Group id="contentGroup" />
        </s:Scroller>

        <!-- Title Bar -->
        <s:Group>
            <s:Rect width="100%" height="30">
                <s:fill>
                    <s:SolidColor color="0xEEEEEE" />
                </s:fill>
            </s:Rect>
            <s:Label id="titleDisplay" lineBreak="toFit"
                left="10" height="30" verticalAlign="middle"
                fontWeight="bold" />
        </s:Group>

    </s:Group>

</s:SparkSkin>

CustomPanelSkin is an extension of spark.skins.SparkSkin. It enters into a contract with spark.components.Panel (the host component declared in the [HostComponent] metatag) to expose the content elements for the title, content group, and control bar and any necessary elements for display. The default layout of the target panel in this example is changed by positioning the control bar at the top and the title bar at the bottom. These content elements are declared as controlBarGroup and titleDisplay, respectively, within a Group container with a VerticalLayout.

Visual elements provided in the controlBarContent property of a panel are added to the controlBarGroup skin part, and visual elements added directly to the panel are displayed in the contentGroup skin part. The title property value of the Panel container is printed out in the titleDisplay component. To apply a custom skin to a Panel, set the skinClass property value to the fully qualified name of the custom skin class, as in the following example:

<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">

    <fx:Declarations>
        <s:ArrayCollection 
                 id="authors" source="{['Josh Noble',
                'Garth Braithwaite', 'Todd Anderson']}" />
    </fx:Declarations>

    <s:Panel title="Control Bar Example"
             width="300" height="120"
             skinClass="com.oreilly.f4cb.CustomPanelSkin">

        <s:controlBarContent>
            <s:DropDownList id="authorCB" width="120" dataProvider="{authors}" />
            <s:Button label="select"
                      click="{printField.text=authorCB.selectedItem}" />
        </s:controlBarContent>

        <s:Group width="100%" height="100%">
            <s:Label id="printField" verticalCenter="0" horizontalCenter="0" />
        </s:Group>

    </s:Panel>

</s:Application>

The full extent of skinning and style possibilities available in the Flex 4 SDK is discussed in Chapter 6, Skinning and Styles, but the examples presented here demonstrate the basic contractual agreement that a custom skin class must adhere to in order to modify the look and feel of a panel containing an optional control bar group display.

Track Mouse Position Within a Container

Problem

You want to keep track of the mouse position within a container for visual elements.

Solution

Add an event handler for a mouse gesture event and use the contentMouseX and contentMouseY properties to retrieve the mouse position within the container, regardless of the position of the interactive element that dispatched the event.

Discussion

When an event handler for a mouse gesture is declared for an event on a container, a MouseEvent object is passed to the method with the localX and localY properties attributed to the mouse position within the interactive element that originally dispatched the event. UIComponent-based elements have contentMouseX and contentMouseY read-only properties that relate to the mouse position within those elements, regardless of its position within any child elements. Because the properties are read-only, their values cannot be bound to. You can retrieve these values within an event handler for a mouse gesture, as in the following example:

<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">

    <fx:Library>
        <fx:Definition name="RadialBox">
            <s:Rect width="100" height="100">
                <s:fill>
                    <s:LinearGradient>
                        <s:entries>
                            <s:GradientEntry color="0xFF0000" />
                            <s:GradientEntry color="0x00FF00" />
                            <s:GradientEntry color="0x0000FF" />
                        </s:entries>
                    </s:LinearGradient>
                </s:fill>
            </s:Rect>
        </fx:Definition>
        <fx:Definition name="LinearBox">
            <s:Rect width="100" height="100">
                <s:fill>
                    <s:RadialGradient>
                        <s:entries>
                            <s:GradientEntry color="0xFF0000" />
                            <s:GradientEntry color="0x00FF00" />
                            <s:GradientEntry color="0x0000FF" />
                        </s:entries>
                    </s:RadialGradient>
                </s:fill>
            </s:Rect>
        </fx:Definition>
    </fx:Library>

    <fx:Script>
        <![CDATA[
            import mx.graphics.SolidColor;
            private var bmd:BitmapData;

            private function handleGroupCreation():void
            {
                bmd = new BitmapData(group.contentWidth, group.contentHeight);
                bmd.draw( group );
            }
            private function handleMouseMove( evt:MouseEvent ):void
            {
                var xpos:int = group.contentMouseX;
                var ypos:int = group.contentMouseY;
                var rectColor:SolidColor = new SolidColor(bmd.getPixel(xpos,
                                                                       ypos));
                chip.fill = rectColor;

                contentLocalPoint.text = "Content Local: " + xpos + " : " + ypos;
                mouseLocalPoint.text = "Mouse Local: " + evt.localX + " : "
                                       + evt.localY;
            }

        ]]>
    </fx:Script>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:Group id="group"
             creationComplete="handleGroupCreation();"
             mouseMove="handleMouseMove(event);">

        <s:layout>
            <s:HorizontalLayout gap="2" verticalAlign="middle" />
        </s:layout>

        <fx:RadialBox />
        <s:Button label="button (1)" />
        <fx:LinearBox />

    </s:Group>

    <s:Label id="contentLocalPoint" />
    <s:Label id="mouseLocalPoint" />

    <s:Rect id="chip" width="30" height="30">
        <s:stroke>
            <s:SolidColorStroke color="0x000000" />
        </s:stroke>
    </s:Rect>

</s:Application>

As each mouseMove event is received in the Group container, the handleMouseMove() method is invoked and the fill color value for the s:Rect graphic element is updated based on the pixel color under the mouse cursor using the contentMouseX and contentMouseY properties. Two s:Label components print out the mouse position in its relation to the container and the interactive element, such as the s:Button control, that first dispatched the event. Because graphic elements are not considered interactive (and thus do not dispatch mouse events), the local mouse positions printed out will be the same as the content mouse positions when the cursor is over a graphic element.

The contentMouseX and contentMouseY properties represent the mouse position within the target UIComponent-based element, including regions that are only accessible through scrolling. The global mouse position with regards to any layout constraints applied to the container itself can be retrieved using the contentToGlobal() method:

var pt:Point = group.contentToGlobal( new Point( group.contentMouseX,
                                                 group.contentMouseY ) );
globalPoint.text = "Global Point: " + pt.x + " : " + pt.y;

Likewise, any global point can be converted to content-local coordinates using the globalToContent() method of a UIComponent-based component.

Drag and Drop Between Visual Containers

Problem

You want to enable drag-and-drop capabilities so you can move visual elements between containers.

Solution

Enable any interactive visual element as a drag initiator by assigning a mouseDown event handler to the element and enable any container as a drag recipient by assigning it drag-and-drop event handlers. Upon invocation of the mouseDown handler, assign the relevant data to a DragSource instance to be handed to the DragManager. When entering a dragEnter event for the drop target, determine the acceptance of a drop operation on the container based on the appropriate data. Once a drop container has accepted a dragDrop event, remove the dragged visual element from its owner and add it to the target drop container.

Discussion

Drag-and-drop support can be added to any element that extends mx.core.UIComponent. Within a drag-and-drop operation there is an initiator and a receiver. Any instance of UIComponent can receive the series of operations initiated by a drag gesture and dispatch events accordingly; these events include dragEnter, dragExit, dragOver, dragDrop, and dragComplete.

To initialize a drag-and-drop gesture, add data relevant to the drag-and-drop operation to a DragSource object within a mouseDown event handler. The DragSource object is given to the DragManager through the static doDrag() method. The DragSource object held by the DragManager is used to determine the acceptance of a drop event on a target container and is handled in the dragDrop event handler to perform the appropriate action.

The following example demonstrates moving children from one visual element container to another:

<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">

    <fx:Script>
        <![CDATA[
            import mx.core.IUIComponent;
            import mx.managers.DragManager;
            import mx.core.DragSource;
            import spark.components.SkinnableContainer;
            import mx.events.DragEvent;
            import mx.core.IVisualElement;

            private function handleStartDrag( evt:MouseEvent ):void
            {
                // grab the item renderer and relevant data
                var dragItem:IUIComponent = evt.target as IUIComponent;
                var dragSource:DragSource = new DragSource();
                dragSource.addData( dragItem, "item" );
                DragManager.doDrag( dragItem, dragSource, evt );
            }

            protected function handleDragEnter( evt:DragEvent ):void
            {
                if( evt.dragSource.hasFormat( "item" ) )
                    DragManager.acceptDragDrop( evt.target as IUIComponent );
            }

            protected function handleDragDrop( evt:DragEvent ):void
            {
                var dragItem:Object = evt.dragSource.dataForFormat( "item" );
                var dragItemOwner:SkinnableContainer = ( dragItem.owner as
                                                         SkinnableContainer );
                dragItemOwner.removeElement( dragItem as IVisualElement );

                var targetOwner:SkinnableContainer = ( evt.target as
                                                       SkinnableContainer );
                targetOwner.addElement( dragItem as IVisualElement );
            }
        ]]>
    </fx:Script>

    <s:SkinnableContainer width="200" height="180"
                          dragEnter="handleDragEnter(event);"
                          dragDrop="handleDragDrop(event);"
                          skinClass="com.oreilly.f4cb.CustomBorderSkin">
        <s:layout>
            <s:HorizontalLayout />
        </s:layout>
        <s:Button label="drag me (1)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (2)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (3)" mouseDown="handleStartDrag(event);" />
    </s:SkinnableContainer>

    <s:SkinnableContainer x="210" width="200" height="180"
                          dragEnter="handleDragEnter(event);"
                          dragDrop="handleDragDrop(event);"
                          skinClass="com.oreilly.f4cb.CustomBorderSkin">
        <s:layout>
            <s:VerticalLayout />
        </s:layout>
        <s:Button label="drag me (4)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (5)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (6)" mouseDown="handleStartDrag(event);" />
    </s:SkinnableContainer>

</s:Application>

When a s:Button control in the display list of either of the SkinnableContainers dispatches a mouseDown event, the handleStartDrag() method is invoked and a DragSource object is added to the DragManager. The static doDrag() method of the DragManager initiates a drag-and-drop gesture. It requires at least three arguments: the drag initiator item reference, a DragSource object, and the initiating MouseEvent. The image rendered during a drag operation is a rectangle with alpha transparency, by default. The dragged image (referred to as a drag proxy) can be changed through the dragImage and imageAlpha arguments of the doDrag() method.

Assigning event handlers for dragEnter and dragDrop events identifies the containers as targets for the drag-and-drop actions initiated by the Button controls. Within the handleDragEnter() method, the data format is evaluated to see whether the target container accepts drop actions. The static acceptDragDrop() method of the DragManager registers the container as a drop target. Once a container is accepted as a receiver for drag-and-drop actions, any subsequent actions associated with the gesture are passed to the container and the appropriate events are dispatched. Within the dragDrop event handler, the drag initiator held on the DragSource object of the operation is used to remove that object from its current owner container and add it to the target drop container.

Though the previous example demonstrates using the default operation of moving an element from one container to another, it is possible to implement a copy operation. The following example demonstrates copying a visual element from one container to another without allowing the initiating owner to receive a drop operation:

<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">

    <fx:Script>
        <![CDATA[
            import spark.components.Button;
            import mx.managers.DragManager;
            import mx.core.DragSource;
            import spark.components.SkinnableContainer;
            import mx.events.DragEvent;
            import mx.core.IVisualElement;
            import mx.core.IUIComponent;

            private function handleStartDrag( evt:MouseEvent ):void
            {
                // grab the item renderer and relevant data
                var dragItem:Button = evt.target as Button;
                var transferObject:Object = {label:dragItem.label,
                                             owner:dragItem.owner};
                var dragSource:DragSource = new DragSource();
                dragSource.addData( transferObject, "item" );
                DragManager.doDrag( dragItem, dragSource, evt, null, 0, 0, 0.5,
                                    false );
            }

            protected function handleDragEnter( evt:DragEvent ):void
            {
                if( evt.dragSource.hasFormat( "item" ) )
                {
                    var targetOwner:SkinnableContainer = ( evt.target as
                                                           SkinnableContainer );
                    var transferObject:Object = evt.dragSource.dataForFormat(
                                                                       "item" );
                    if( targetOwner != transferObject.owner )
                    {
                        DragManager.acceptDragDrop( evt.target as IUIComponent );
                    }
                }
            }

            protected function handleDragDrop( evt:DragEvent ):void
            {
                var transferObject:Object = evt.dragSource.dataForFormat( "item" );
                var dragItem:Button = new Button();
                dragItem.label = transferObject.label;

                var targetOwner:SkinnableContainer = ( evt.target as
                                                       SkinnableContainer );
                targetOwner.addElement( dragItem as IVisualElement );
            }
        ]]>
    </fx:Script>

    <s:SkinnableContainer width="200" height="180"
                          dragEnter="handleDragEnter(event);"
                          dragDrop="handleDragDrop(event);"
                          skinClass="com.oreilly.f4cb.CustomBorderSkin">
        <s:layout>
            <s:HorizontalLayout />
        </s:layout>
        <s:Button label="drag me (1)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (2)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (3)" mouseDown="handleStartDrag(event);" />
    </s:SkinnableContainer>

    <s:SkinnableContainer x="210" width="200" height="180"
                          dragEnter="handleDragEnter(event);"
                          dragDrop="handleDragDrop(event);"
                          skinClass="com.oreilly.f4cb.CustomBorderSkin">
        <s:layout>
            <s:VerticalLayout />
        </s:layout>
        <s:Button label="drag me (4)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (5)" mouseDown="handleStartDrag(event);" />
        <s:Button label="drag me (6)" mouseDown="handleStartDrag(event);" />
    </s:SkinnableContainer>

</s:Application>

When the mouseDown event handler is invoked, a generic object representing the initiating s:Button (with any appropriate property values preserved) is created and passed as the data transfer object of the DragSource instance. The handleDragEnter event handler is used not only to determine the validity of the drag initiator, but also to see if the source and target of the operation are the same. If so, no further drag-and-drop operations are allowed on the container that dispatched the dragEnter event. If the target container is a valid receiver for the drag-and-drop action, the handleDragDrop() method is invoked and a new Button control is created based on the generic object of the DragSource and is added to the target container.

Using the DragManager is a convenient way to move and copy visual elements from one container to another. If you need more control over the drag-and-drop operation, however, you can transfer elements from one container to another using mouse event handlers and methods of the content API, such as addElement() and removeElement().

Drag and Drop Between Data Containers

Problem

You want to enable drag-and-drop capabilities between multiple DataGroup containers so you can easily add and remove data items.

Solution

Assign a mouseDown event handler to item renderers as they are added to a DataGroup container and assign drag-and-drop event handlers to any receiving data containers. Upon receipt of the mouseDown event, assign the data held on the target item renderer as the DataSource handled by the DragManager to initiate the drag-and-drop operation. Use the data from the DataSource object to determine the acceptance of a drop gesture for the target container as drag events are dispatched and, when the dragDrop event is received, remove the dragged data from the collection of the initiating container and add the data to the collection of the target drop container.

Discussion

Within a drag-and-drop operation, there is an initiator and a receiver. The initiator begins the drag-and-drop operation by invoking the static doDrag() method of the DragManager, typically in response to a user gesture such as a mouseDown event. Any UIComponent-based element can be a receiver of drag-and-drop gestures and dispatch events accordingly. Some list-based components in the Flex SDK, such as List, have built-in support for managing drag-and-drop operations to help automate the process of moving data from one container to another or within a container itself. DataGroup and SkinnableDataContainer do not have built-in support, but they can be enabled to receive drag-and-drop operations as they are extensions of UIComponent.

The example for this recipe is split up into two parts, a view and a controller, to better demonstrate how to programmatically move data from one data container’s collection to another. The view is an extension of an ActionScript-based controller and is made up of two scroll-enabled s:SkinnableDataContainer containers with their own specified data collections and itemRenderer instances:

<ApplicationViewController xmlns="*"
                           xmlns:fx="http://ns.adobe.com/mxml/2009"
                           xmlns:s="library://ns.adobe.com/flex/spark"
                           xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Script>
        <![CDATA[
            import com.oreilly.f4cb.CustomScrollableSkin;
        ]]>
    </fx:Script>

    <fx:Declarations>
        <s:ArrayCollection id="collectionOne">
            <s:Button label="button (1)" />
            <s:Button label="button (2)" />
            <s:Button label="button (3)" />
        </s:ArrayCollection>
        <s:ArrayCollection id="collectionTwo">
            <s:Button label="button (4)" />
            <s:Button label="button (5)" />
            <s:Button label="button (6)" />
        </s:ArrayCollection>
    </fx:Declarations>

    <layout>
        <s:HorizontalLayout />
    </layout>

    <s:SkinnableDataContainer width="160" height="140"
                              rendererAdd="handleRendererAdd(event)"
                              dataProvider="{collectionOne}"
                              dragEnter="handleDragEnter(event);"
                              dragDrop="handleDragDrop(event);"
                              itemRenderer="spark.skins.spark.
                                                    DefaultComplexItemRenderer"
                              skinClass="com.oreilly.f4cb.CustomScrollableSkin">
        <s:layout>
            <s:VerticalLayout paddingLeft="5" paddingRight="5"
                              paddingTop="5" paddingBottom="5" />
        </s:layout>
    </s:SkinnableDataContainer>

    <s:SkinnableDataContainer width="160" height="140"
                              rendererAdd="handleRendererAdd(event)"
                              dataProvider="{collectionTwo}"
                              dragEnter="handleDragEnter(event);"
                              dragDrop="handleDragDrop(event);"
                              itemRenderer="spark.skins.spark.DefaultItemRenderer"
                              skinClass="com.oreilly.f4cb.CustomScrollableSkin">
        <s:layout>
            <s:HorizontalLayout paddingLeft="5" paddingRight="5"
                                paddingTop="5" paddingBottom="5" />
        </s:layout>
    </s:SkinnableDataContainer>

</ApplicationViewController>

The collection for each SkinnableDataContainer container is a set of s:Button controls. The containers’ itemRenderer instances differ in how they render data on the content layer: the first declared container renders each button with its skin intact by assigning the DefaultCompleteItemRenderer class as the item renderer, while the second declared container renders only the label assigned to the button control by assigning the DefaultItemRenderer class as the item renderer.

The handleRendererAdd() method is assigned as an event handler for rendererAdd. Similar to the elementAdd and elementRemove events of the content API, DataGroup and SkinnableDataContainer dispatch rendererAdd and rendererRemove events whenever an element representing a data object from the collection is added to or removed from the content layer of the container, respectively. Event handlers for the dragEnter and dragDrop events are assigned to each container in order to handle those specific operations during a drag-and-drop operation:

package
{
    import flash.display.DisplayObjectContainer;
    import flash.events.MouseEvent;

    import mx.collections.IList;
    import mx.core.DragSource;
    import mx.core.IUIComponent;
    import mx.core.IVisualElement;
    import mx.events.DragEvent;
    import mx.managers.DragManager;

    import spark.components.Application;
    import spark.components.Group;
    import spark.components.SkinnableDataContainer;
    import spark.components.supportClasses.ItemRenderer;
    import spark.events.RendererExistenceEvent;

    public class ApplicationViewController extends Application
    {
        private var dragItem:Group;

        protected function handleRendererAdd( evt:RendererExistenceEvent ):void
        {
            // assign weak reference listener to visual item renderer
            var item:IVisualElement = evt.renderer;
            ( item as DisplayObjectContainer ).mouseChildren = false;
            item.addEventListener( MouseEvent.MOUSE_DOWN, handleStartDrag, false,
                                   0, true );
        }

        private function handleStartDrag( evt:MouseEvent ):void
        {
            // grab the item renderer and relevant data
            var target:UIComponent = evt.target as UIComponent;
            var dragItem:Object = {owner:target.owner,
                                   data:( target as
                                   IDataRenderer ).data};
            var dragSource:DragSource = new DragSource();
            dragSource.addData( dragItem, "itemRenderer" );
            DragManager.doDrag( target, dragSource, evt );
        }

        protected function handleDragEnter( evt:DragEvent ):void
        {
            if( evt.dragSource.hasFormat( "itemRenderer" ) )
                DragManager.acceptDragDrop( evt.target as IUIComponent );
        }

        protected function handleDragDrop( evt:DragEvent ):void
        {
            var dragItem:Object = evt.dragSource.dataForFormat( "itemRenderer" );
            var ownerCollection:IList = ( dragItem.owner as
                                          SkinnableDataContainer ).dataProvider;
            ownerCollection.removeItemAt( ownerCollection.getItemIndex(
                                          dragItem.data ) );

            var targetCollection:IList = ( evt.target as
                                           SkinnableDataContainer ).dataProvider;
            targetCollection.addItem( dragItem.data );
        }
    }
}

The event object for a rendererAdd event dispatched from a DataGroup or a SkinnableDataContainer is a RendererExistenceEvent. The item renderer that dispatched the event can be referenced using the renderer property of the event object and is attributed as an IVisualElement. In this example, a weak-referenced mouseDown event handler is assigned to the item renderer upon receipt of a rendererAdd event by the ApplicationViewController and is attributed as the handleStartDrag() method.

When the mouseDown event handler is invoked, the initiating element is attributed as a UIComponent instance and a generic Object is created to hold the data property assigned to the element during instantiation. The generic Object is assigned as the drag data on a DragSource object, which is passed into the DragManager to initiate a drag-and-drop operation. When the dragged item enters the content layer of a container, the dragEnter event handler assigned to that container is invoked and is used to determine whether the data being dragged is acceptable for the container, using the static acceptDragDrop() method of DragManager. If the container is accepted as a receiver, the dragDrop event handler is invoked upon a drop operation. Upon an accepted drop operation, the generic Object handled by the DragSource is used to remove the dragged data from the collection of its original container and add it to the collection of the drop target container.

Using the DragManager is a convenient way to move data items from one container to another. However, if more control over the operation is needed, data items can be transferred between containers or within a single container using the mouse event handlers and methods of the collections API, such as addItem(), addItemAt(), and removeItem().

Add a Spark Layout Container to a MX Navigation Container

Problem

You want to add visual components from the Spark set to a MX navigation container.

Solution

Add a Spark NavigatorContent container as a child of the desired MX navigation container. Elements from both the Spark and MX component sets can be added as children to the NavigatorContent container.

Discussion

Although it is recommended to use Spark containers in preference to MX containers because of their improved runtime performance and separation of responsibilities, the two sets do not have identical navigation containers in the Flex 4 SDK. Consequently, depending on development requirements, use of MX navigation containers may be necessary. Containers and components from the Spark set cannot be declared directly as content for Halo containers, however, because child containers are attributed as implementations of INavigatorContent.

To add Spark elements to a MX container, they must be added to a NavigatorContent container as in the following example:

<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">

    <mx:Accordion width="300" height="300" headerHeight="50">
        <s:NavigatorContent label="Container 1"
                            width="100%" height="100%">
            <s:layout>
                <s:VerticalLayout />
            </s:layout>
            <s:Button />
            <s:Ellipse width="100" height="100">
                <s:fill>
                    <s:SolidColor color="0xFFCCFF" />
                </s:fill>
            </s:Ellipse>
            <s:DropDownList />
        </s:NavigatorContent>
        <s:NavigatorContent label="Container 2"
                            width="100%" height="100%">
            <s:layout>
                <s:HorizontalLayout />
            </s:layout>
            <s:CheckBox />
            <s:Rect width="100" height="100">
                <s:fill>
                    <s:SolidColor color="0xCCFFCC" />
                </s:fill>
            </s:Rect>
            <s:HSlider />
        </s:NavigatorContent>
    </mx:Accordion>

</s:Application>

NavigatorContent is an extension of SkinnableContainer and implements the INavigatorContent interface. The INavigatorContent interface exposes common properties, such as label and icon, for child content of MX navigation containers and extends IDeferredContentOwner. The IDeferredContentOwner interface is an extension of IUIComponent. It fulfills a contract for NavigatorContent, being a valid child of a navigation container from the MX set, and also allows for deferred instantiation of the container. Because Spark containers support children from both the Spark and MX component sets, elements from both architectures (including GraphicElement-based elements) can be added to a NavigatorContent container.

Create a Spark-Based ViewStack

Problem

You want to create a container that holds multiple child containers that are lazily instantiated upon request.

Solution

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.

Discussion

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.

If you enjoyed this excerpt, buy a copy of Flex 4 Cookbook.