How to use Cairngorm Navigation (Requires Parsley or Swiz)Contents
SummaryThe Cairngorm Navigation library provides utilities to ease the creation of point-to-point, hierarchical and deferred navigations, global and local history tracking, wizards, and enter and exit interceptions. It can reduce the amount of Script-block logic needed by allowing dedicated objects to control navigational concerns. The current version is implemented as a Parsley and a Swiz extension. A Spring ActionScript extension is currently in development. Release NotesChanges in Version 0.9:
IntroductionCairngorm is also a mountain range in the Scottish Highlands. Many hikers visit different landmarks there. In order to find landmarks, they orientate themselves and meet at waypoints. A Flex application also has the need to wander between different landmarks (view components). The view components are contained by waypoints (Flex containers). The Navigation library uses these metaphors to explain navigation in Flex. Views are hierarchical and deferred. View containers can contain (or load) other view containers that contain view components. So can waypoints contain other waypoints that contain a landmark. The notation is described using a dot syntax (waypoint1.waypoint2.landmark). In order to declare a waypoint including its landmarks (view components), just declare a Waypoint tag inside a Flex container. <mx:Metadata> [Waypoint] </mx:Metadata> In order to define the landmarks (view components), use a constant class such as this:
public class ContentDestination
{
public static const DASHBOARD:String = "content.dashboard";
public static const NEWS:String = "content.news";
public static const MESSAGES:String = "content.messages";
}
Now, the view components (landmarks) within the view container (waypoint) need to be connected to the constant names. Out of the box, the Navigation library allows to use the automationName to give view components a landmark name, but you can also extend the Navigation library and provide a different mechanism using custom waypoint types. <?xml version="1.0" encoding="utf-8"?> <mx:ViewStack xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:dashboard="sample1.presentation.dashboard.*" xmlns:news="sample1.presentation.news.*" xmlns:messages="sample1.presentation.messages.*" addedToStage="dispatchEvent(new Event('configureIOC', true))"> <mx:Metadata> [Waypoint] </mx:Metadata> <mx:Script> <![CDATA[ import sample1.application.ContentDestination; ]]> </mx:Script> <dashboard:DashboardView automationName="{ ContentDestination.DASHBOARD }" backgroundColor="0x6699CC"/> <news:NewsView automationName="{ ContentDestination.NEWS }" backgroundColor="0x99CC66"/> <messages:MessagesView automationName="{ ContentDestination.MESSAGES }" backgroundColor="0xCC9966"/> </mx:ViewStack> In order to tell the Flex container to show a specific view component, the Navigation library uses messaging. Messaging allows participants to perform this navigation from anywhere in your application without the need for a concrete reference to the Flex container that performs the navigation. A NavigationEvent must be dispatched via the messaging framework of the IoC framework used (Swiz or Parsley) such as the below examples showcase: Parsley:[MessageDispatcher] public var dispatcher:Function private function navigateTo(destination:String):void { dispatcher(NavigationEvent.newNavigateToEvent(destination)); } Or alternatively: [Event(name="navigateTo", type="com.adobe.cairngorm.navigation.NavigationEvent")] [ManagedEvents(names="navigateTo")] private function navigateTo(destination:String):void { dispatchEvent(NavigationEvent.newNavigateToEvent(destination)); } For more information about Parsley Messaging, please visit the official Parsley documentation. Swiz:[Dispatcher] public var dispatcher:IEventDispatcher; public function navigateTo(destination:String):void { dispatcher.dispatchEvent(NavigationEvent.newNavigateToEvent(destination)); } Tracking StateFlex containers and controls might have a need to display the state of navigation. A ToggleButtonBar control that relates to a group of view components being selected within a view container i.e. might need to be in a "pressed" state once the view component is selected. Especially if the NavigationEvent was dispatch by a different part of the Flex application than where the Button control is located, this could be cumbersome to achieve. The NavigationLibrary allows to subscribe to the currently selectedIndex from a waypoint (view container).
[Landmark(name="content")]
public class ContentPM implements ISelectedIndex
{
[Bindable]
public var selectedIndex:int;
...
In this case the ToggleButtonBar just needs to bind directly to the selectedIndex property of the PM:
<mx:ToggleButtonBar
dataProvider="{ model.buttonBar }"
selectedIndex="{ model.selectedIndex }"
itemClick="model.navigationBarHandler(event)"/>
Objects annotated with "Landmark" can optionally also implement the ISelectedName interface, which injects the currently selected landmark name into the subscribed object. Listening to Enter and Exit EventsWhen navigations between view components are performed, related objects (in particular PMs) often need to know when a view component they relate to is first shown, subsequently shown or hidden. The Navigation library allows any objects to declare tags to catch these events. An object that wants to subscribe to any such event needs to declare a Landmark annotation at the class level and specify the landmark it is interested in observing:
[Landmark(name="content.news")]
public class NewsPM
{
...
Subsequently, public methods of the annotated object can be marked with the necessary tags such as Enter, and Exit. [Enter(time="first")] public function firstEnter():void { ... } [Enter(time="next")] public function enter():void { ... } [Enter(time="every")] public function enter():void { ... } [Exit] public function exit():void { ... } Intercepting Enter and Exit Events (Parsley only)Sometimes, PMs need to prevent a navigation before certain actions are performed by it. A PM might need to ask the user for confirmation before the user navigates away from a form with uncommitted data, or a PM might need to wait until a remote service response is received before entering a view component. The EnterInterceptor and ExitInterceptor tags allow PMs to view intercept navigations and either approve or reject the proceeding. [ExitInterceptor] public function exitInterceptor(processor:MessageProcessor):void { message += "Exit interceptor\n"; var listener:Function = function(event:CloseEvent):void { if (event.detail == Alert.OK) { processor.proceed(); } }; Alert.show("Do you really want to exit?", "Warning", Alert.OK | Alert.CANCEL, null, listener); } EnterInterceptor and ExitInterceptor use Parsley's MessageInterceptors behind the scences. For more information about it, please read the official Parsley documentation on Messaging > MessageInterceptors. Nested and Deferred NavigationIntercepting enter and exist events work on all levels within a destination string (i.e. "content.news"). The above might have navigated directly to content.news and therefore triggered the objects annotated with a Landmark name of "content.news". However, the navigation library automatically calls objects annotated with a "content" Landmark name even if the navigation goes directly to "content.news". This works on arbitrary number of levels including deferred view components such as modules or Flex components instantiated over time. It works with the complete lifecycle of first enter, enter, exist, enter intercept and exit intercept. For example if the user defines an exit interceptor on "content.task" and on "content.task.expenses" a navigation to "sidebar.chat" should trigger the interceptor of "content.task.expenses" first, following the interception of "content.task" before it triggers any enter interceptors of "sidebar.chat". Parallel Navigation SupportThis feature allows landmarks to listen to views that are shown on the same level than other landmarks. Usually landmarks on the same level (i.e. content.news or content.messages) would be mutually exclusive. Meaning that the user navigates from content.news to content.messages, content.news is being exited. This is indented for presentation components that are also mutually exclusive such as a ViewStack, TabNavigator, Accordion etc. However, if the presentation components representing the landmarks are both being shown (such as typically the case when Flex view states), then parallel navigation is useful. The sample1 application shows one example of parallel navigation. A Button includes or excludes an optional footer view with invoking a method on the DashboardFooterPM and binding to the currentState property. [Bindable] public var currentState:String=NO_FOOTER_STATE; public function toggleFooter():void { var name:String=DashboardNestedDestination.FOOTER; if (currentState == NO_FOOTER_STATE) { currentState=FOOTER_STATE; dispatcher(NavigationEvent.newNavigateToEvent(name)); } else { currentState=NO_FOOTER_STATE; dispatcher(NavigationEvent.newNavigateAwayEvent(name)); } } Notice that we use the usual "navigateTo" (NavigationEvent.newNavigateToEvent) event when opening the parallel view and a new event "navigateAway" (NavigationEvent.newNavigateAwayEvent) when closing it. The value of the landmark definition also needs to show we're using a parallel view. So, the DashboardFooterPM uses a ".." syntax on the level of the parallel view:
[Landmark(name="content.dashboard..footer")]
public class DashboardFooterPM
{
and
public class DashboardNestedDestination
{
public static const FOOTER:String="content.dashboard..footer";
...
If the view state of the footer presentation component also needs to respond to event from the navigation library, the following code to the DashboardFooterPM is required: [Enter(time="every")] public function everyEnter():void { currentState=FOOTER_STATE; ... } [Exit] public function exit():void { currentState=NO_FOOTER_STATE; ... } Wizard NavigationAnother common navigational need for RIAs is to provide wizard navigations. Wizards define an order in that view components are shown. This predefined order can allow navigational controls such as button bars not having to know about the exact control to navigate to, but instead only to navigate to the next component or the the previous one. The wizard implementation of the Navigation library allows to define a Wizard object externally in a context: <wizard:Wizard waypoint="myWizard"> <mx:Array> <mx:String>{ MessageDestination.STAGE1 }</mx:String> <mx:String>{ MessageDestination.STAGE2 }</mx:String> <mx:String>{ MessageDestination.STAGE3 }</mx:String> </mx:Array> </wizard:Wizard> And inject this Wizard implementation into a navigational control such as a menu bar: <mx:Script> <![CDATA[ import com.adobe.cairngorm.navigation.wizard.IWizard; [Inject] [Bindable] public var wizard:IWizard; ]]> </mx:Script> <mx:HBox> <mx:Button label="Prev" enabled="{ wizard.hasPrevious }" click="wizard.previous()"/> <mx:Button label="Next" enabled="{ wizard.hasNext }" click="wizard.next()"/> </mx:HBox> The wizard steps can be defined as a waypoint with landmarks, exactly the same way as the waypoint and landmark examples show at the beginning of this article. <?xml version="1.0" encoding="utf-8"?> <mx:ViewStack xmlns:mx="http://www.adobe.com/2006/mxml" addedToStage="dispatchEvent( new Event( 'configureIOC', true ) )"> <mx:Metadata> [Waypoint] </mx:Metadata> <mx:Script> <![CDATA[ import sample1.application.MessageDestination; ]]> </mx:Script> <mx:Box automationName="{ MessageDestination.STAGE1 }"> <mx:Label text="1"/> </mx:Box> <mx:Box automationName="{ MessageDestination.STAGE2 }"> <mx:Label text="2"/> </mx:Box> <mx:Box automationName="{ MessageDestination.STAGE3 }"> <mx:Label text="3"/> </mx:Box> </mx:ViewStack> History NavigationHistories can track user navigation and similarly to wizards allow navigational controls to move forward and backwards without needing to know where to move forward and backward to. The history navigation of the Navigation library allows declaring a concrete history object in a context. To track only the navigation between one waypoint, the following code is needed in a context file: <history:WaypointHistory> <mx:String>content</mx:String> </history:WaypointHistory> If every navigation event needs to be tracked, the GlobalHistory class can be declared:
<history:GlobalHistory/>
Both history classes implement the IHistory interface, which can be injected into a navigational control. <mx:Script> <![CDATA[ import com.adobe.cairngorm.navigation.history.IHistory; [Bindable] [Inject] public var history:IHistory; ]]> </mx:Script> <mx:Button label="previous" enabled="{ history.hasPrevious }" click="history.previous()"/> <mx:Button label="next" enabled="{ history.hasNext }" click="history.next()"/> ConfigurationThe configuration depends on the IoC framework being used. ParsleyAdd the Navigation support extention to the Parsley context builder: <spicefactory:ContextBuilder> <cairngorm:CairngormNavigationSupport/> <spicefactory:FlexConfig type="{ Sample1Context }"/> </spicefactory:ContextBuilder> Note that Parsley offers alternative configuration options to metadata. You could annotate i.e. your landmarks in a XML or MXML configuration externally to the PM using it. It is a design decision on how loosley you want to couple. Please refer to Options in Loose Coupling for more information. For a complete example, please view the Sample1 application. SwizAdd the following to your Swiz context: <swiz:config> <swiz:SwizConfig viewPackages="your.presentation.components.*" eventPackages="com.adobe.cairngorm.navigation"/> </swiz:config> <swiz:beanProviders> <swiz:BeanProvider> <swiz:beans> ...your landmark definitions... <!--integration--> <core:NavigationSwizAdaptor/> </swiz:beans> </swiz:BeanProvider> </swiz:beanProviders> <swiz:customProcessors> <landmark:LandmarkProcessor/> <landmark:EnterProcessor/> <landmark:ExitProcessor/> <waypoint:WaypointProcessor/> </swiz:customProcessors> For a complete example, please view the Sample1 application. |
|