Preventing the TabNavigator from changing tabs when one is clicked.
I recently came across an interesting problem at work with a TabNavigator. The problem we had is that when a user clicked on a tab, we needed to validate some data on the client-side. If the data was valid, then the TabNavigator could proceed with changing tabs. Otherwise, if the data was invalid, an Alert error message would be displayed and the TabNavigator would not switch away from the current tab.
There were two solutions that came to my mind. One way would be to override the set selectedIndex function in TabNavigator. The other way would be to intercept an “change” event dispatched by the TabNavigator, perform the validation, and then if necessary prevent the TabNavigator from changing.
In the end I decided to go with the second option because I found it to be a cleaner implementation. This relied on using the “onCapture” property of addEventListener so that I could catch the event as soon as possible (during the capture phase), and prevent it from reaching the TabNavigator (during the target phase). To do this, I included this line of code in the creationComplete function for my component.
1 | tabNavigator.addEventListener(MouseEvent.CLICK, onTabClick, true); |
The third parameter simply means that this is a capture event instead of a traditional bubble event. The difference is explained here. However, it is important to keep in mind that if I needed to remove this event listener, I would need to mark true on the third parameter of removeEventListener like so:
1 | tabNavigator.removeEventListener(MouseEvent.CLICK, onTabClick, true); |
Otherwise, the capture event handler will not be removed if I leave it off.
You may notice that I made the event listener listen to the “click” event rather than the tab “change” event. This kind of threw me off at first. When I listened to the “change” event, the TabNavigator would still continue to change tabs even if I tried to stop it. This occurs because the “click” event is the event that gets the ball rolling with tab changes initially. The “change” event is dispatched as an after-thought to let the developer know that the change has occurred. My event listener looks kind of like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private function onTabClick(event:MouseEvent):void { if (event.target.parent is TabBar) { var selectedIndex:uint= TabBar(event.target.parent).getChildIndex(event.target as DisplayObject); var isValid:Boolean = validateUserInfo(); if (!isValid) { event.stopImmediatePropagation(); event.preventDefault(); } } } |
Now to explain the reasoning behind this… The target that is invoked when a tab is clicked on is a Tab display object. The class Tab is an internal Flex class and not for external use. So, instead I used the Tab’s parent which is TabBar to perform a class check to make sure the we are only handling the event when a Tab is clicked, and not any other part of the TabNavigator.
Next, for our project, we needed to know what the selectedIndex would have been should the Tab have changed. Since the TabNavigator has not been able to process the Tab change, we cannot access the selectedIndex property because it will just reflect the current tab we are on. So, to simulate this, I casted the Tab’s parent to a TabBar and then grabbed the child index of the target (the tab that was clicked) by using the TabBar’s getChildIndex function (inherited from the Container class).
After that, I went about validating the form in another function. This function returns a Boolean value to indicate whether the form is valid or not. If the form is not valid, I prevent the Tab from changing by calling event.stopImmediatePropogation() to stop the event from trickling down to the TabNavigator, and I also call event.preventDefault() just in case the event happened to continue down to the TabNavigator for some strange reason (I don’t think this would ever happen, but I am just paranoid sometimes!).
I found that these steps did the trick for me and I was able to not go through the hassle of override TabNavigator and manually coercing Flex to do what I wanted. Hope this helps!
UPDATE: If you are looking for a good solution to prevent a TabBar from changing instead of the TabNavigator, check out Aaron Hardy’s blog. He used it in a project we did together and it worked great.
Very cool. Props for sharing.
It’s odd, this dispatches two click events for me…
@Cameron
Are you using a TabNavigator or a TabBar? I have seen this behavior with TabBar before as TabBar generates a mouse click event and a simulated mouse click event. To get past this problem, check out Aaron Hardy’s post on this subject.
[...] Try this: Preventing the TabNavigator from changing tabs when one is clicked. Nate’s Code Vault [...]
[...] If you’re dealing with a TabNavigator class instead of the TabBar class, my comrade Nate has a post that could help in that respect. See his post: Preventing the TabNavigator from changing tabs when one is clicked. [...]
Very helpful, contains 2 errors:
1. var selectedIndex:uint= TabBar(event.target.parent).getChildIndex(event.target); gives compiler error, use intead:
var selectedIndex:uint= TabBar(event.target.parent).getChildIndex(event.target as DisplayObject);
2. event.stopImmediatePropogation(); //should be actually stopImmediatePropagation
@Kun
. Thanks for the catches, I’ll update the code above.
Yeah, I did this freehand without Flex Builder
this is not workin for me…i implemented the solution as it is…but the click event is generated only when i click on the empty space present on the tabnavigator but now when i click on the actual tabs.
Hi nate,
I tried the code above but i’m not able to replicate the behaviour..flex doesn’t detect the CLICK event…it’s detecting the other mouse events like mousedown and mouseup…but not click…I’ve create a simple example:
http://ravimarella.com/tabNavigator/launch.html
can you please tell me whats wrong with the code…
thanks,
ravi
@ravi
There is one problem I see right off the bat in your code. In your fnCreate() creationComplete handler you are adding an event listener for the click event and then you are removing it immediately in the next line. Take out the removeEventListener in the next line and see if you are able to hit the onTabClick function.
You should only remove the event listener if you no longer wish to block the tab event.
- Nate
Thanks Nate for your valuable implementation. It helped me a lot to solve the issue in my App.
Thanks,
Sathish Kumar.K
Thanks, this saved me a lot of time.
Wonderful, works perfectly. Saved me hours. Thanks for sharing.
Use the following line to remove Listener automaticaly:
tabNavigator.addEventListener(MouseEvent.CLICK, onTabClick, true, 0, true);
Hi there; I found a possible issue with your example.
var selectedIndex:uint= TabBar(event.target.parent).getChildIndex(event.target as DisplayObject);
Gives you the _NEW_ selected index, not the old one. To get the old one use (from our code)
oldTabIndex = TabBar(event.target.parent).selectedIndex;
newTabIndex = TabBar(event.target.parent).getChildIndex(event.target as DisplayObject);
This way you can validate the current (old) before moving to the new.
Steve,
I explained in my post that selectedIndex is the new selectedIndex. I also explained that the TabNavigator’s selectedIndex property is simply the old selectedIndex when onTabClick is executed. Maybe I just didn’t explain it well enough
In my case, I didn’t need to know the new or the old selectedIndex because if the form is invalid, we don’t allow the TabNavigator to switch anyways (event.stopImmediatePropagation does this for us). I just wanted to show how you could get the new selectedIndex if you wanted to (since it was much more tricky to find than the old selectedIndex).
This was what I was looking for. You saved my day. Thanks