The Cairngorm Popup library contains components for managing the opening, closure and general behavior of popups. They are designed to minimize Script-block logic in MXML components and to reduce code duplication across applications that manage multiple popups.
Changes in Version 1.12:
The Cairngorm Popup library is a small Flex library for opening and closing popups. Instead of using the PopUpManager directly and writing script-block logic to manage their creation and removal, a pair of simple MXML tags are available for declaring within view components. Here's the "Hello World" of declarative popups:
<cairngorm:PopUpWrapper open="{model.openPopUp}"> <mx:Label text="Hello World"/> </cairngorm:PopUpWrapper>
The PopUpWrapper tag is a non-visual component that manages opening and closing the popup. When its open property is set to true, a popup is opened containing the component wrapped by the tag; in this case a Label. When the open property is set back to false, the popup closes again. Alternatively, the component may dispatch an Event.CLOSE event, which will be handled by the PopUpWrapper itself.
This approach helps to keep MXML views components clean and free from ActionScript logic, whilst removing duplicated code wherever the PopUpManager is needed. The opening and closure can be controlled conveniently through bindings, as above, which plays nicely with Presentation Model. There are also simple ways to control the life-cycle of the popup and to apply special behaviors, such as effects that play while it opens and closes.
The remainder of this post covers the two components available – PopUpWrapper and PopUpFactory – explaining the differences between them. The source code, unit tests and a sample application are available for download from the Adobe Open Source SVN.
Since Flex SDK 4 was released, the PopUpFactory component is deprecated as SDK 4 provide a new interface for deferred instantiation. The ITransientDeferredInstance extends IDeferredInstance and add the ability for the developer to reset the deferred instance which make the PopUpFactory redundant with PopUpWrapper.
There are two components with slightly different capabilities:
Some examples of each are now provided.
Here's a more detailed version of the "Hello World", this time showing all of the properties and events available:
<cairngorm:PopUpWrapper open="{model.openPopUp}" center="true" modal="false" childList="{PopUpManagerChildList.POPUP}" opening="openingHandler()" opened="openedHandler()" closing="closingHandler()" closed="closedHandler()" reuse="true"> <mx:Label text="Hello World!"/> <cairngorm:PopUpWrapper>
The properties provide the same control over the popup as the PopUpManager.
Please note that the reuse property is only available in the SDK 4 version of the library
The events are dispatched at various points during the life-cycle of a popup:
Here's another example where the popup is opened or closed using buttons instead of binding. The view used for the actual popup is a custom component.
<cairngorm:PopUpWrapper id="popup"> <mypopup:MyPopupView/> </cairngorm:PopUpWrapper> <mx:Button label="Open" click="popup.open = true"/> <mx:Button label="Close" click="popup.open = false"/>
The popup view itself can also instruct the closure of the popup by dispatching an CloseEvent.CLOSE event. For example:
<mypopup:VBox...> <mx:Label text="My Popup!"/> <Button label="Close" click="dispatchEvent(new CloseEvent(CloseEvent.CLOSE))"/> </mypopup:VBox>
Please note that the PopUpFactory is deprecated in the SDK 4 version of the library
The PopUpFactory tag uses a different mechanism for specifying the popup view. It is in fact the same approach that list-based controls use for their item renderers. The factory property can either be set to the class name for the popup view, or else an in-line component can be declared. Here is a simples use case, specifying the popup view class name:
<cairngorm:PopUpFactory open="{model.openPopUp}" factory="my.package.MyPopupView" reuse="false"/>
Notice that the PopUpFactory includes a reuse property. This gives control over whether or not the popup view is reused, in contrast to the PopUpWrapper tag which always reuses the view and never makes it eligible for garbage collection.
When working with the PopUpManager directly it is common to make popups eligible for garbage collection once they have been removed. However there are good use cases for both approaches. If an application shows a large popup at start-up and then never again, it is wasteful of resources to keep it in memory. Alternatively, if a popup is repeatedly shown and hidden, such as a context menu, it is more efficient to reuses a single instance. As with all Flex development, the Flex Profiler should be used to ensure popup views are successfully garbage collected, since they can be a source of memory leaks.
Here is another example where the popup view is declared as an in-line component, in a similar manner to an item renderer on a list-based control.
<cairngorm:PopUpFactory open="{model.openPopUp}" reuse="false"> <mx:Component> <mypopup:MyPopupView title="{outerDocument.model.popUpTitle}"/> </mx:Component> </cairngorm:PopUpFactory>
The PopUpFactory tag also provides the same properties as the PopUpWrapper tag, since they both inherit from a common base class.
It can be common to customize how popups behave. In one situation, a popup might need to play a transition while opening; in another a popup might have to remain centered whenever its contents resize.
The IPopUpBehavior interface was introduced to extract these specialities into their own classes. In this way, a set of behaviors can be created, independent of one another, and the appropriate behaviors can be applied for a given situation. This design creates a nice extensibility point, allowing developers to write their own behaviors without needing to customize the declarative tags at all.
Here is an example of a popup with a number of behaviors applied.
<cairngorm:PopUpWrapper id="popup1" popup="samples.MyPopup" modal="{cb.selected}"> <cairngorm:behaviors> <cairngorm:KeepCenteredBehavior/> <cairngorm:ZoomAndFadeBehavior/> </cairngorm:behaviors> </cairngorm:PopUpWrapper>
In this case, two custom popup behaviors are being applied. The first plays a zoom and fade effect upon opening and closure, while the second ensures that the popup remains centered whenever its contents is resized. The declarative approach makes it simple to configure these behaviors.
To write your own popup behavior, just implement the IPopUpBehavior interface:
public interface IPopUpBehavior { function apply(opener:PopUpBase):void; }
A reference to the popup component is passed through the apply() method to each behavior. The behavior can then attach event handlers in order to control the behavior of the actual popup while it is opened or closed. Some examples are provided in the sample project (download above). A behavior can also suspend and resume closure by calling the suspendClosure() and resumeClosure() methods of the PopUpEvent class. This allows something asynchronous, such as the playing of an effect, to take place during closure, before the popup is actually removed from the display list.
The design for the popup tags is relatively simple. There's a common base class – PopUpBase – that is extended by the two tags: PopUpWrapper and PopUpFactory . The base class contains the common properties – open , center , modal – and also holds an array of behaviors, applying them during opening and closure. The two concrete tags implement different creation and destruction policies using the standard mechanisms for deferred instantiation and object factories: IDeferredInstance and IFactory . The Flex compiler takes care of the rest, converting the declared popup view into an appropriately configured DeferredInstanceFromFunction/Class or ClassFactory object. See the Flex Language Reference for more details.
Note: With Parsley 2.3.1 and Flex 4, we recommend to use the Parsley PopUp tag instead of the PopupParsley library and it's Parsley related behaviors.
While the Popup library is not dependent on the Parsley Application framework, it can be used in combination with it. The opening and closing events of the PopUpWrapper and PopUpFactory can be listened to, in order to add and remove popup views to Parsley's ViewManager. The PopupTest library offers a behaviour for this task:
<cairngorm:behaviors> <mx:Array> <parsley:AddPopUpToParsleyContext/> </mx:Array> </cairngorm:behaviors>
This example of the PopUpTest project showcases more. For more information on why Parsley requires this extra step, please refer to the official Parsley documentation under Dynamic View Wiring.
We've been using versions of these tags on our projects for several years now. They help us to separate concerns, keeping our views as simple declarations of components, free from script block logic that would make them harder to understand and more difficult to unit test.
It's worth noting that Flex 4 includes its own declarative tag – PopUpAnchor – which also takes advantage of MXML and deferred creation, but it is designed for drop-down menu scenarios. There are some similarities, but the PopUpWrapper and PopUpFactory components are designed for more general popup handling. Perhaps a future version of the SDK will include declarative tags that make it simple to manage the life-cycle of popups and apply different behaviors, but in the meantime we hope you enjoy using these components.