Flex Tab Navigator with Close Buttons
We are working on an application over here that would benefit from having tabbed navigation. To my dismay the Tab Navigator component in Flex does not support this, so you really have to do a bit of lifting yourself. (Gawd! imaging having to actually create something! ). Sarcasm aside, a quick web search brought up this tutorial from Keun Lee , showing a tab close button that is added to the application container when you mouse over the tabBar object of the TabNavigator. While it’s not as pretty as elegant the TabNavigator class itself, Keuns tutorial is quite a bit faster. Since this tutorial does not contain any code, I thought I might post up what we’ve done here.
The core concept is to create an instance of your close tab button when you mouse over the tabBar object of the TabNavigator. I simply created the listener on the TabNavigator itself:
this.tabBar.addEventListener(MouseEvent.MOUSE_OVER, createCloseButtons);
this.tabBar.addEventListener(MouseEvent.MOUSE_OUT, removeCloseButtons);
The next two functions really just simply utilize two variables, closeBtn an extended button customized for use on the tab, and a Boolean variable buttonShow set to false that simply indicates if you are on the button or not. This little vat might not seem necessary, but I’ll explain as we go along. So for brevity, I’ll leave out the function signature and just give you the body of createCloseButton:
if(buttonShow == false){
//creates the custom button
closeBtn = new tabCloseButton();
//add a name to id the button
closeBtn.name = “closeBtn“;
//get the index of the moused over tab
mouseOverIndex = this.tabBar.getChildIndex(this.tabBar.getChildByName(event.target.name));
//Target the tab that the mouse is over
tabButton = this.tabBar.getTabAt(mouseOverIndex);
//generate the insertPoint
insertPoint = new Point(_lotWorkspace.x,_lotWorkspace.y);
//add coordinae to closeBtn
closeBtn.x = (tabButton.x + this.localToGlobal(insertPoint).x + tabButton.width – 20);
closeBtn.y = (tabButton.y + this.localToGlobal(insertPoint).y + 5);
//add an event listener
closeBtn.addEventListener(MouseEvent.CLICK, closeRequestHandler);
//add the button
mx.core.Application.application.addChild(closeBtn);
//set the Boolean
buttonShow = true;}
So okay, now you’re (hopefully) adding buttons to your TabNavigator component. But you’ve got nothing to remove the buttons, and things up there are looking a little fuzzy. Enter function removeCloseBtn. This function is much shorter but a bit tricky. You see if you move your mouse from the tabBar onto the closeBtn, you are essentially firing off a mouseOut event. If you simply remove the close button at this point, the mouse is back on the tabBar, which was underneath the close button the whole time. Without some conditionals, you’ll be stuck with a crazy flickering closeBtn, and it will give you a seizure. So here’s the removeCloseBtn function:
if(event.relatedObject.name != “closeBtn“){
mx.core.Application.application.removeChild(closeBtn);
buttonShow = false;}
Do a trace and check out the relatedObject property in the MouseEvent. It’s pretty critical in this technique. Basically it allows you to see if you are firing a mouseOut from this.tabBar because you are on the closeBtn or not. And if you’re not about to click the closeBtn, remove it.
Well thats about it. I know this is really not the most elegant solution out there, but it works well, and you don’t have to write a whole bunch of code to get it run properly. Happy Coding – I’m going to get some coffee!
9 comments so far
Leave a reply
01. I appreciate your posting the history of the ‘other’ postings concerning this matter. After looking at them, one can conclude that yours is much simpler for us beginners.
02. What I guess you miss, for us beginners, is some clarification concerning the setting of the x-y coordinates of the ‘pop-up’ button.
A. What in the world is _lotWorkspace?
B. Does the correct setting of the coordinates for the ‘pop-up’ button depend upon the layout policies of the container in which the TabNavigator object resides? If so, in what way?
C. Can you give a simple example of the relative coordinate arithmetic? Let’s say that a TabNavigator is at [100, 200] in the Application container. Then, let’s say that the Tab we are mousing over is at [10, 50] in its parent container. What would be the expression to calculate the x-y for the ‘pop-up’ button?
Thank you.
Thanks for the comment Terry. I’ll try to answer your questions in order:
A. The variable _lotWorkspace is a private variable that holds an instance of the Tab Navigator component. Consequently, _lotWorkspace.x and _lotWorkspace.y are the values of the registration point of this component. (Typically the upper left corner). Was going to change the name of this variable for the tutorial to somthing more descriptive, but …sigh.
B. I don’t believe that layout policy will affect this method, as the X and Y of the component _lotWorkspace are reported as they are that moment on the stage. If you find that your buttons are floating in space after a resize however, tie a stage resize event, and recalculate their position.
C. To get this function to work, it’s important to use the localToGlobal property. If you don’t the x,y of the parent tabnavigator is going to show up as 0,0 as you are inside that component. Here is a breakdown of the x component’s math I use for this example:
tabButton.width is the width of the button I am currently over. -20 brings the button to the left twenty px to leave a little border. The other to expressions are the x value of the tabNavigator component in relation to the stage, and the TabButton Component in relation to it’s parent, which for all practical purposes is the TabNavigator. The sum of which should give you the x value of the TabButton relative to the stage. Which is what you need to add the button at the mx.core.application level. (this is a bit cludgy to do that here)
Hope this helps, and Happy Coding.
02. So then, to generalize. Because Adobe did not implement the Tab as a sub-class of Container, which would have greatly simplified matters, we want to position the Cancel Button — which will become a child of the Application Container — on the the Stage so that it will properly align with some other UIComponent that is already on the stage. To accomplish that we need to calculate the offset from the origin of the Application Container.
03. We start with the local coordinates for the origin [Point] of the Tab on which the MOUSE_OVER event was dispatched. We have the Container for that Tab — the TabNavigator — translate that Point to a Global reference. Now we almost have the x-y position to which we would like to set the newly-created Button. We just make whatever incremental pixel adjustments seem appropriate for drawing an icon on some portion of the Tab’s real estate.
04. So first, are you sure that _lotWorkspace is a reference to the TabNavigator instance? If so, your arithmetic seems flawed as it does not account for the additional offset to the particular Tab that is the target of the MouseEvent. Second, have you tested your theory about layout? I wonder if your Application layout property just happens to be ‘absolute’. For your use case, with the Application layout set to ‘vertical’, do your pop-up Cancel Buttons still seem to end up correctly positioned?
05. Using your basic approach, but performing my placement arithmetic, my application is almost working as required. I am still debugging an ending condition after I have deleted the last child from the TabNavigator and one remaining Cancel Button is left on the Stage. I’ll follow your tracing advice and see what event I am missing or mis-handling.
Great job. Its a simple but effective tutorial.
But you mentioned about a custom made button “tabCloseButton”
closeBtn = new tabCloseButton();
But where can I find this custom tab button?
Kris, this is just a simple button instance that I created by extending the button class.
Hi,
I want to know if there is a way to add an event listener for the tab navigator icon. For example a click on the tab navigator icon should throw a popup.
Thanks in advance.
Shilpa, to add an event listener to the click button, myButton.addEventListener(Event.CLICK,someListener); Though be warned that this post is horribly out of date. The proper way to get a close button in a tab navigator tab is to extend the button class that creates the tab.
Is it possible to adapt this to a Tilelist control? If so, could you post or email me a sample. I’ve been trying to adapt it myself, but I’m all out of hair and my forehead is badly bruised from banging it against the wall.
Basically, I have a horizontal tilelist control that loads image files dynamically. I need to be able to hover over an image in the tilelist and display a close button. When the close button is clicked, I’d like the thumbnail to be removed from the tilelist and then behind the scenes, delete the thumbnail and larger image file.
If you could be so kind as to post a sample or lend some advice as to the direction I should look, I would be very grateful.
Thank you.
Karl, this example is a bit outdated. What you need to do in your case is create a custom item renderer for your TileList control. you can either start from scratch (this is what i would do) or extend the ListItemRenderer class. I apologize for not posting an example, but we are up to our eyes in client work right now. Cheers, – E