Component Flex July 30, 2009

Page Stack - Navigate by Page Name

Building a navigation system in Flex is harder than it should be. Trying to figure out how to decouple your navigation model from the view is unintuitive. ViewStack is the obvious choice for it, but it leaves you with only two options, neither of which is good.

  1. Use selectedIndex - You can throw your pages/containers in a ViewStack and store the currently selected page index in a model. This works great, except that your model is coupled to the order of the pages in the ViewStack. You can store the indices in constants, but if someone adds a new page in there, it will throw off all the indices.

  2. Use selectedChild - You can mitigate the index problem by storing the selectedChild property on your model, but then your model has a reference to a view. Yuck.

The solution - Page Stack

This simple component allows you to store the name of the selected page. You can set the page. You can then store a string on your model that you bind to the selectedPage property of PageStack. If you set the name on each child of the PageStack, they’ll match up.

MyView.mxml

<components:PageStack selectedPage="{navigation.selectedPage}">
    <mx:Canvas name="{Navigation.FIRST}"/>
    <mx:Canvas name="{Navigation.SECOND}"/>
    <mx:Canvas name="{Navigation.THIRD}"/>                
</components:PageStack>

<mx:Button label="Go to Page 3" click="navigation.selectedPage = Navigation.THIRD"/>

Navigation.as

public class Navigation
{
    public static const FIRST:String = "first";
    public static const SECOND:String = "second";
    public static const THIRD:String = "third";
        
    [Bindable]            
    public var selectedPage:String = FIRST;
}

And here’s the source!

package net.seanhess.components
{
    import flash.display.DisplayObject;

    import mx.containers.ViewStack;
    import mx.core.Container;

    public class PageStack extends ViewStack
    {
        protected var pages:Object = {};
        protected var newChildren:Boolean = false;

        override public function addChildAt(child:DisplayObject, index:int) : DisplayObject
        {
            newChildren = true;
            invalidateProperties();
            return super.addChildAt(child, index);
        }

        override protected function commitProperties() : void
        {
            super.commitProperties();

            if (newChildren)
            {
                newChildren = false;
                pages = {};

                for each (var child:DisplayObject in getChildren())
                {
                    var name:String = child.name;
                    pages[name] = child;
                }
            }
        }

        public function set selectedPage(value:String):void
        {
            var child:Container = pages[value] as Container;

            if (!child)
                throw new Error("Could not find page: " + value);

            selectedChild = child;
        }

        public function get selectedPage():String
        {
            return selectedChild.name;            
        }
    }
}

Sean, Nice post. I am eager to try this out and maybe solve problems with the Glue Library Example. Just to let you know your missing a " after Canvas..{Navigation.Number}. After I added the code and set "public var navigation:Navigation;" on the view I receive an error "Cannot access a property or method of a null object reference." I must be missing something pretty obvious. Thanks!

Thanks for the catch Jonathan. I've corrected it. If I were using Glue, I would make Navigation a manager, put it in the glue map, and inject only the selectedPage to MyView. So, MyView would have public var selectedPage:String, and the page stack would just bind to that.

Your comment was added successfully
Your comment could not be added

Leave a comment