Saturnboy
 6.1

Drawer Component in Flex 4

, , ,

I needed a good way to have a large settings panel with a minimal visual impact. The obvious answer is to hide or minimize or collapse the settings panel when not in use. I thought about using Flexlib‘s WindowShade component (which I’ve dicussed in detail in Styling Flexlib’s WindowShade), but why reuse something when you can reinvent the wheel? Plus, it always helps to hone my Flex 4 custom component kung-fu. So, I chose to implement a simple sliding drawer component from scratch as a Flex 4 component.

The Drawer component is a vanilla container (it actually extends SkinnableContainer), so it will happily take any spark component for its children. Before I dive into my implementation, let’s check out the finished drawer component in action (view source enabled):

Flash is required. Get it here!

Just click on the handle to open and close the drawer.

Extending SkinnableContainer

The Drawer component itself, is just pure AS3, but the demo above uses a few MXML skins to achieve the desired look-and-feel. This is a pretty standard pattern that I see during Flex 4 development, so expect it when you write your own custom components.

We’ll review the component implementation in two steps. First, we focus on the skin state management aspect of the drawer. Here’s the relevant code (taken from Drawer.as):

[SkinState("opened")]
public class Drawer extends SkinnableContainer {
    private var _opened:Boolean = false;
 
    public function get opened():Boolean {
        return _opened;
    }
 
    public function set opened(value:Boolean):void {
        if (_opened != value) {
            _opened = value;
            invalidateSkinState();
        }
    }
 
    override protected function getCurrentSkinState():String {
        return (opened ? 'opened' : super.getCurrentSkinState());
    }
    ...
}

The Drawer component can either be closed (the default) or opened. To model theses states, we use the normal state from the superclass to represent the closed drawer, and add a new opened SkinState to represent the opened drawer. We just expose a simple opened boolean property with a custom getter and setter, and then override the getCurrentSkinState() method. It’s important to remember the states we are talking about are skin states, and not component states (see Flex 4 Component States vs. Skin States for the difference).

Second, we focus on the action of opening and closing the drawer. Here’s the relevant code (taken from Drawer.as):

public class Drawer extends SkinnableContainer {
    ...
    [SkinPart(required="false")]
    public var openButton:Button;
 
    private function clickHandler(event:MouseEvent):void {
        opened = !opened;
    }
 
    override protected function partAdded(partName:String, instance:Object):void {
        super.partAdded(partName, instance);
        if (instance == openButton) {
            openButton.addEventListener(MouseEvent.CLICK, clickHandler);
        }
    }
 
    override protected function partRemoved(partName:String, instance:Object):void {
        super.partRemoved(partName, instance);
        if (instance == openButton) {
            openButton.removeEventListener(MouseEvent.CLICK, clickHandler);
        }
    }
}

The Drawer component includes a simple spark button, as an optional SkinPart, that is used to initiate the state change. The partAdded() and partRemoved() methods are overridden to manage and adding and removing of the button’s click event handler. And lastly, the clickHandler() method flips between skin states by toggling the opened boolean property.

Usage

Using the Drawer is the same as any container. In MXML, just put any child components you want between the container’s open and close tags:

<containers:Drawer ... skinClass="skins.DrawerSkin">
    <!-- components go here -->
</containers:Drawer>

Here we also apply the DrawerSkin to our container.

Skins

The DrawerSkin is responsible for creating the desired look-and-feel and generally making the Drawer component look cool. Here are the interesting parts of the skin:

<s:Skin ...>
 
    <fx:Metadata>
        [HostComponent("containers.Drawer")]
    </fx:Metadata>
 
    <s:states>
        <s:State name="normal" />
        <s:State name="opened" />
        <s:State name="disabled" />
    </s:states>
 
   ...
 
    <s:Button id="openButton" ...
            skinClass="skins.DrawerOpenButtonSkin"
            skinClass.opened="skins.DrawerCloseButtonSkin" />
 
    <s:Group id="contentGroup" ... includeIn="opened" />
</s:Skin>

The DrawerSkin has three skin states, normal and disabled are inherited from SkinnableContainer, but opened is our custom skin state. The skin also includes the optional openButton SkinPart, which itself uses two custom buttons skins, one for the open drawer and one for the closed drawer. Lastly, note that the container’s content is only displayed when the skin is in the opened state via the newfangled inline state syntax: includeIn="opened".

Files

Comments

steveb

7.20.2010

1

How could you adapt this for a drawer that opened horizontally across the screen?

7.20.2010

2

@steveb: The Drawer component itself (Drawer.as) is obviously orientation independent. It is the skin that controls the orientation, which I hard-coded to open up from the bottom.

To change the drawer to open horizontally, you’d need to modify the placement of the components in the DrawerSkin (the background Rect, the openButton, and the contentGroup). Also, you’d need to modify the open and close buttons which I again hard-coded to an up arrow and down arrow.

Good luck.

10.25.2010

3

Gotta say thanks, man.
Great article, we don’t see much like that out there. Cool to have someone do things in a clean and detailed fashion!

My future components will not look the same… At least on the code side of things.

10.25.2010

4

Oh! And a quick question: where (and how) would you set a custom component’s default skin? I’ve Googled this a bit (and tried a few alternatives) but couldn’t get anything clean to work… Got tip?

10.25.2010

5

@Quentin: That’s an easy one. The best way to set a “default” skin on a custom component is via CSS like this:

@namespace comps "com.blah.components.*";
 
comps|MyComp {
	skinClass:ClassReference('com.blah.skins.MyCompSkin');
}

Then you can override with a simple styleName in MXML <comps:MyComp styleName="cool" />. And then you wire the overriding skin via CSS like this:

comps|MyComp.cool {
	skinClass:ClassReference('com.blah.skins.MyCoolSkin');
}

10.26.2010

6

Well, what you describe absolutely works but I was looking for a way to really default the skin class, that is, prevent the final user from having to set it at all (neither within a CSS file, nor via inline skinClass attribute)…

I finally found that and I’ll share it there as I think this might interest everyone.

var styles:CSSStyleDeclaration = FlexGlobals.topLevelApplication.styleManager.getStyleDeclaration("net.tw.flex.spark.containers.ToggleBlock");
if (!styles) {
    var defStyles:CSSStyleDeclaration = new CSSStyleDeclaration();
    defStyles.defaultFactory = function():void {
        this.skinClass = Class(ToggleBlockDefaultSkin);
    }
    FlexGlobals.topLevelApplication.styleManager.setStyleDeclaration("net.tw.flex.spark.containers.ToggleBlock", defStyles, true);
} else if (!styles.getStyle('skinClass')) {
    styles.setStyle('skinClass', Class(ToggleBlockDefaultSkin));
}

Note: this chunk of code goes in a static initializer (in a class called ToggleBlock) setting the default skinClass to ToggleBlockDefaultSkin.

Thoughts?

10.26.2010

7

@Quentin: Solid. Using a static initializer is a good solution.

JC

12.9.2010

8

Great sample code. Thanks

I run into a interesting problem when I try to use this in a bigger scale. I have five drawer going, now the problem is since they are draw one of top of another, When the last one slides out it cover up all the other drawer button. Any idea how to make it so the buttons will never be covered if I have multiple of them? I have a feeling, I might even need some kind of manager class to keep track of different state for multiple drawer. Thanks for any idea in advance.

12.9.2010

9

@JC: I’m not sure multiple drawers is the correct solution (or proper UX for that matter). I envision the drawer as a “config panel” that slides out only when you need it.

If you need to put lots of info in the drawer, but don’t want it to be really big, then I suggest using some other visual organizer kind of component (tabs or accordion). For example, you might have one drawer that comes up from the bottom with a horizontal accordion inside it.

JCSL

2.12.2011

10

Nice! Exactly what I was looking for, thanks!

2.18.2011

11

Love it. Using it. Thanks!

Beau N. Brewer

5.10.2011

12

Static initializer is a good solution, but if you have a library of components a better way would be to include a default.css in the /src folder of your library. This isn’t well documented, but works like a charm.

juniorFlex

10.27.2011

13

how to make drawer to right hand side and change the height of it. Couldn’t figure that out.

10.27.2011

14

@juniorFlex: See my comment #2 above.

You’ll need to modify the placement of the components in the DrawerSkin and you’ll also need new open and close buttons (mine are hardcoded to be up and down arrows).

juniorFlex

10.27.2011

15

I played around for the arrows and is there a nice way to make the arrows on the left and right hand side. Again Thanks for the reply .. I did not see your message and posted the same question again .. Once again thank you.

Chris

11.12.2011

16

Great component! I wanted to be able to modify this so that the drawer button is at the top and when clicked it slides down with the contents. I can’t seem to figure out a way to do this with transitions. Any ideas?

11.14.2011

17

@Chris: see comment #2 above.

You’ll need to to flip-flop the open close buttons, so down arrow is open and up arrow is close.

Morlek

1.16.2013

18

Can someone tell me how i do to make this drawer on top of everything wen i slide it and dont “push” my viewstack away? something like this http://kuwamoto.org/2006/04/10/new-flex-component-sliding-drawer-v-05/comment-page-1/#comment-473591

Clinton

7.1.2013

19

Please list the specifics on what needs to be modified to make the drawer slide in and out from the right. I just cant figure out what needs to be changed in Drawerskin.mxml. Thanks in advance!

© 2017 saturnboy.com