06.25
I ran into an interesting problem with a TabNavigator. I wanted to intercept the closing of a tab, and if the tab was in an unsaved state present the user with a chance to rethink their action. Unfortunately, With default TabNavigator class, this is impossible because it doesn’t expose the event outside itself. There’s even a comment in the SuperTabBar class that talks about preventing tab closure, which is where I got my start.
Fortunately, we already had an extended version of the TabNavigator, similar to the SlidingTabNav seen here. So, how to actually make the magic work?
The first thing was to override the createChildren function. This is where the TabNavigator creates the tabBar, so we simply wait until that is done and then tag an event listener to the tabBar close event.
override protected function createChildren():void { super.createChildren(); tabBar.addEventListener(SuperTabEvent.TAB_CLOSE, tabClosedHandler, false, 0, true); } |
Now that we’re catching the close event, we can prevent the closure by throwing an event of our own. This is the place where my greenhorns may show through, since I couldn’t seem to just dispatch the existing event and have it work. I’m guessing it’s a side effect of the way the event model works, but I haven’t really looked into it yet. Regardless, I simply created my own event (by cloning the caught event) and dispatched it. Then once it was dealt with, I steal a page from the SuperTabBar itself and check for the default being prevented:
protected function tabClosedHandler(event:SuperTabEvent):void { var newEvent:SuperTabEvent = event.clone() as SuperTabEvent; dispatchEvent(newEvent); if(newEvent.isDefaultPrevented()) { event.preventDefault(); } } |
Of course the class has a nice new Event tag at the top:
[Event(name="tabClose", type="flexlib.events.SuperTabEvent")] |
And now it’s ready to be dealt with like any other component. If I don’t want the tab closed, I simply call the preventDefault() method of the event.
In my case, I always prevent the default, then I call a check function in the tab referenced (SuperTabEvent has a tabIndex property) and if it has unsaved data I throw up a Popup asking for confirmation. If I want to close the tab (confirmed, or no unsaved data) I remove the child and let the tabs rebuild themselves.
OK,_technically_; but for *usability*, not so much . . .
Conceptually, tabs are a space (and time) multiplexing of a related _set_ of data. Therefore it doesn’t make sense to save only part of that set (but not the rest). Logically, it is an inconsistent update.
From a more pragmatic standpoint, the convention is that the transactional controls (Save, Cancel, Apply) appear *outside* the frame of the tab pages. Users are therefore used to having Save (or Cancel, etc.) apply to all the content on the tabs. If your UI doesn’t work that way it will violate their expectations.
I would strongly recommend modifying the interaction design of your application so that it works in the conventional, expected way.
In my case, each tab is a unique object. I’m using tabs in a way similar to a web browser. Each tab is a separate object, unconnected from the other tabs.
Here’s a vague description of what was needed: Type A is a set of types {A, B, C}. If I need to create/alter A’ while editing A, I spawn a new tab with A’. If I finish editing A’, I can close it and move back to A, or perhaps I’ll create D since I need to add D to A at some point. As this is all very confusing, you could accidentally close an unsaved tab, which is what this aims to solve.
There is an argument that could be made for shifting views from List->A->A’, but since A’ is also on the main list it was confusing. The need was to be able to begin editing of one group from within the edit view of another group. The tabular solution was used to allow maximum flexibility to the user.
There are other places where we use a single Save/Cancel outside a set of tabs for just that reason, but that is where a single object spans multiple tabs.