Tom Sugden: A Declarative Approach to Flex Popups

« The Trend Towards Inversion-of-Control Frameworks in Flex | Main | Best Practices for the Flex Logging Framework »

July 18, 2009

A Declarative Approach to Flex Popups

Yaniv De Ridder and myself have developed 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:

<popup:PopUpWrapper open="{model.openPopUp}">
   <mx:Label text="Hello World"/>
</popup: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 models. 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 library, source code, unit tests and a sample application are available for download here:

Using Declarative Popups

There are two components with slightly different capabilities:

  • PopUpWrapper - this is the simplest tag. It defers the creation of the popup view until the first time the popup is opened, then reuses the same view every subsequent time. There is no mechanism for releasing that view to be garbage collected.
  • PopUpFactory - this is the more flexible but less simple tag. It also defers creation of the popup view, but gives control over whether or not the view should be reused when the popup is next opened. If reuse is disabled, the view is made eligible for garbage collection upon closure.

Some examples of each are now provided.

The PopUpWrapper Component

Here's a more detailed version of the "Hello World", this time showing all of the properties and events available:

<popup:PopUpWrapper
    open="{model.openPopUp}"
    center="true"
    modal="false"
    childList="{PopUpManagerChildList.POPUP}"
    opening="openingHandler()"
    opened="openedHandler()"
    closing="closingHandler()"
    closed="closedHandler()">
    <mx:Label text="Hello World!"/>
<popup:PopUpWrapper>

The properties provide the same control over the popup as the PopUpManager. The events are dispatched at various points during the life-cycle of a popup:

  • PopUpEvent.OPENING is dispatched after the popup view has been created but just before it has actually been added to the display list.
  • PopUpEvent.OPENED is dispatched just after the popup view has been added to the display list.
  • PopUpEvent.CLOSING is dispatched just before the popup view is removed from the display list.
  • PopUpEvent.CLOSED is dispatched just after the popup view has been removed from the display list.

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.

<popup:PopUpWrapper id="popup">
    <mypopup:MyPopupView/>
</popup: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 Event.CLOSE event. For example:

<mypopup:VBox ... >
    <mx:Label text="My Popup!"/>
    <Button 
        label="Close" 
        click="dispatchEvent(new Event(Event.CLOSE))"/>
</mypopup:VBox>    

The PopUpFactory Component

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:

<popup: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.

<popup:PopUpFactory 
    open="{model.openPopUp}"
    reuse="false">
    <mx:Component>
        <mypopup:MyPopupView
            title="{outerDocument.model.popUpTitle}"/>
    </mx:Component>
</popup:PopUpFactory>

The PopUpFactory tag also provides the same properties as the PopUpWrapper tag, since they both inherit from a common base class.

Popup Behaviors

When we first introduced a declarative approach for popups to our projects, we noticed developers customizing the classes with more and more specific behavior. In one situation, a popup needed to play a transition while opening; in another the popup had to remain centered whenever its contents resized. Soon the popup tags became bloated with code for handling all of these special requirements, each applicable only to one or two popups.

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.

<popup:PopUpFactory
    open="{model.showPopUp}"
    factory="my.package.MyPopUpView">
    <popup:behaviors>
        <mx:Array>
            <popup:ZoomAndFadePopUpBehavior/>
            <popup:KeepCenteredPopUpBehavior/>
        </mx:Array>
    <popup:behaviors>
</popup:PopUpFactory>

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.

How Does It Work?

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.

Conclusion

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.

Posted by tsugden at July 18, 2009 10:07 PM

Related Entries

Comments

Very Nice Tom. I have implemented a similar but simpler and smaller scoped solution on my current project.

For the PopUpWrapper can't you make it eligible for garbage collection by using ITransientDeferredInstance and implementing a dispose method to reset the defferred instance?

In most cases I would like to use the simplicity of the Wrapper but woudl want GC. This would seem to solve both these issues.

I'll be downloading this and using it on my current project.

Posted by: Giles Roadnight at July 22, 2009 8:31 AM

Thanks for your comment, Giles. The ITransientDeferredInstance is a new addition in Flex 4 to support improvements in the states mechanism. It's a great idea to use this instead of the IFactory interface for Flex 4 projects, bypassing in-line components and outerDocument references. I'll probably release a Flex 4 build that consolidates the PopUpWrapper and PopUpFactory into one using ITransientDeferredInstance, but in the meantime feel free to hack away at the source code provided. Best, Tom

Posted by: Tom Sugden Author Profile Page at July 22, 2009 1:04 PM

Very handy, Tom. Thanks for posting this up. I was about to try to write something like this and thought to look around. It's great.

Posted by: Sanford Redlich at September 26, 2009 7:49 PM

Very nice. I've been looking around for "best practices" to write PopUpManager code and this is by far the most elegant solution I've came across which provides

- a very nice declarative syntax
- resource management
- And most importantly, being able to extract out the popup show hide behaviors to external classes/files.

I'd like to point out that when using effects such as WipeDown for the closeEffect the showTarget attribute should be set to false to simulate the hideEffect trigger so the popup does not reappear briefly before being taken away by the manager. Hope this will be helpful to someone.

Posted by: johncch at October 20, 2009 8:44 AM

Tom, Did you ever get around to converting this for Flex 4? I really like the changes to states in flex 4 and popUpAnchor almost gets me what I'm looking for, but it's missing modal access.

Posted by: Clair Roberts at March 21, 2010 9:41 PM

Post a comment




Remember Me?

(you may use HTML tags for style)