Perfect Pop Up Panels in Actionscript 3
Click the above-left picture of the Perfect Pop Up Panel demo to start the demo in a new window.
Perfect Pop Up Panels in Actionscript 3
Some few times in ancient history, I managed to create pop-up tabbed panels that worked correctly. However, each time, I either forgot my previous work, or was unable to find it, and so I recreated it repeatedly. It was probably not designed in an especially re-usable manner anyway.
So, frustrated, that, yet again, I recently found myself re-inventing this wheel, I created a system designed to be re-usable and decided to publish it on my blog so that I would be able to readily re-find it. If someone else gets benefit from it, all the better.
This may not be the canonical method to create such, but it works solidly, it is object oriented and easily extended, and it seems to me to be relatively straightforward. If there is a move canonical means to accomplish such, I wouldn't mind a heads up.
If that size is too small, you can see it the full size of your browser window by clicking the picture at top.
Perhaps it's presumptuous of me to call my pop up panels perfect, but I was relatively pleased with the results. There were a couple of requirements that I had for them:
That left a few considerations to the approach: what the panel is, where the panel exists, & who handles the popping.
The panel could be either a graphic or a movieclip. The panel could either contain the contents or the contents could internally have a panel-shapped graphic as an element in its display list. The popping mechanism could exist in the panel itself, the contents, or the parent calling class.
Because I wanted the panel to be able to contain components or simlar items that require mouse interaction, my first approach of poping the panel down when mousing out of the panel graphic/movieclip didn't seem to work because the mouse_out event gets triggered when moving over a component. This could be solved by doing a point hit test with the current mouse position against the panel, but this got to be burdensome. Also, there's always the case of someone quickly mousing away from the panel and the mouse_out event somehow not getting triggered. This could alternately be solved by creating an invisible shield behind each panel whenever it gets popped, that catches any mouse_over activity, but this prevents multiple panels from being opened concurrently. Such a shield would work, but not as part of the panel class.
Ideally, all the code for popping would be handled by the panel itself, afterall, it does know how far it needs to move up and down, but I found it simpler to have a parent of the panel handle that. In such a distributed code model, the parent could either be the contents or higher level that handles all panels. I'd already determined the hard way that I didn't want to add popping code to content code, so, that left the document class.
So, my eventual design made the panel to effectively be a class that just draws the panel graphic and addChild-s the passed in content and tab movieclips. The contents know nothing of the panel and communicate with parents by dispatching custom events. All of the brains for handling the popping occurrs in the calling class
I've created a class called PopPanel that takes 5 parameters:
As the document class (or your own calling class) is instrumental in the proper popping & dropping of panels, I discuss this at length. If the call is understood, the panel itself is relatively straightforward.
I tend to force panel placement at the bottom of the screen, but you can choose to do otherwise. Consecutive panels are placed overlapping so that only their tabs show and with a spacing between them of panelOffset. In the document class, I create the panels in an array defined at compile time, but they can be stuffed in at runtime instead. This is how I accomplish it:
Of course, this presumes that I have classes with names of those given above. I deliberately made the tab movieclips to be only 20x20. By default, PopPanel.as uses a margin of 10 that applies to all edges of the tab and the contents. In my case, I have predefined constants for WHITE and BLACK. Specifying true for the tabOnRight parameter creates the tab in the appropriate position on the panel, but the tab itself always starts at x=0 on the symbol -- the panel portion itself is what gets shifted in x. Having the panels start at the tab start position makes it easier to place them properly from the document class. Importantly, though, because my document class doesn't currently concern itself with the width of the panel, using this method may place a portion of the panel off the left edge of the stage depending on its width and how many panels appear before it. This is the case with the 3rd panel (rocket ship). I deliberately left this as a reminder of this limitation, however, the document class code could be amended to account for this, but the tabs would be unevenly spaced. Lacking any idea as to how to properly space tabbed panels so that the tabs are evenly spaced but the tabs' contents don't overshoot the stage, I figured it's probably best to just keep all tabs on the left unless the panel's contents go off the right side of the stage.
The document class also has a zero alpha stage-sized rectangle called shield which must be added to the display list prior to the panels. This lets me know when the mouse has left a popped panel.
In the document class, in order to effect proper setup and popping of the panels, I have the following functions:
The purpose here is self evident. The panels are taken from array panels and added to the display list, positioned, and given event listeners.
This routine re-adds the panels' event listeners if they have been removed.
Triggered by a click on the tab, this routine pops the panel up, removes the panel's listeners, and turns the shield on.
This swaps the current panel's Z position with that of the topmost panel ensuring that the current panel won't be occluded by tabs or other panels.
Runs as an event handler to a mouse_over involving the shield, this routine returns all panels to their down states, resets their listeners, turns the shield off, and has the panels do any necessary cleanup.
Adds the event listener for the shield.
Removes the shield's event listener.
The panel with "123" as its tab's movieclip has a button and a color picker in its content's movieclip. This is to demonstrate that the panels don't interfere with mouse clicks for their contents. However, the content's movieclip must either appropriately deal with all events generated by its contents, or, if events should affect the document class, events should be bubbled up to it. I arbitrarily gave one of the panels an inner glow filter to provide it a rounded 3D look. I generally don't use the black hairline edge when doing so. I also gave it a drop shadow filter, another nice touch if you have no performance concerns. Because some panels will have animations, pop-able subpanels, or certain components, I found it necessary to have each panel process it's own content minimization procedure. Rather than require a stubb in every content, I check for the presence of popDown() with the hasOwnProperty method.
If, for example, in the case of the color picker, the user exits the panel while the color picker's color panel is popped out, the color panel will stay in place even though the panel has popped back down. So, the shield's event handler, which gets activated on mouse_over (effectively leaving the panel), tells each contents' movieclip to deal with issues itself (in the contents' popDown routine.
BTW, the popping is done via AS3's built in tweening engine. This can readily be swapped out for Tweener, Tweenlite, or your favorite more efficient engine. Elastic on up, Bounce on down. Also, it's pretty acceptable UX-wise to have the panel simply jump up and down rather than do any tweening -- just in case the tweens start to grate after a bit.
Here's a peek at the code
Again, all the brains for the system reside in the calling class, in my case, the document class.
As previously mentioned, this class'es guts are not as interesting as the results. Of the three functions, setupBG creates the panel graphic, setupTab places the tab, and setup contents places the contents. Relatively straightforward. The important thing is the set of functionality in the document class parent of the tabs.