Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change tab ordering in Flex (Actionscript, SDK 3.5)

The flex tab ordering is important both for usability (obvious) and accessibility (it defines the screen readers read ordering). However, Flex 3.5 seems to have next to no support to influence it for more complicated applications.

From what I know so far:

  • the tab ordering is calculated by the mx.managers.FocusManager, which takes care of one "tab cycle" for the entire application (i.e. there is not one for each container, but one for the whole app). The exception being embedded .swf files and popups, each of which have their own FocusManager.

  • the logic inside the FocusManager is marked as private, and the class is instantiated in Application.initialize(), so it's not easy to change the behaviour (short of rewriting parts of the SDK, which might pose license trouble)

  • The tab ordering looks for each components tabIndex (int) property. All components which have this property set (>0) are ordered by its value, otherwise the visual hierarchy is used (which uses container nesting and child order inside the containers).

  • All components without tabIndex are sorted to be after the components which have it set (internally, "tabIndex not set" is mapped to "tabIndex = int.MAX_VALUE)

  • two components with the same tabIndex are ordered by visual hierarchy.

This means that you can either use the automatic ordering by visual hierarchy (mostly what you want) OR you can use specifying tabIndexes directly - BUT if you do this you completely screw the automatic ordering. This is especially bad if you work with custom components and want to change the tab ordering inside those: once you do this, you ruined the tab order for all screens using your component. Also you cannot just set the tab ordering only on the highest level mxml file, as often you don't have access to the inner workings of the components you use.

Example:

accVerticalStuff.mxml

<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Label text="One"/>
    <mx:TextInput tabIndex="1" id="One" />

    <mx:Label text="Two" />
    <mx:TextInput tabIndex="2" id="Two"/>

    <mx:Label text="Three" />
    <mx:TextInput tabIndex="3" id="Three"/>

</mx:VBox>

Application.mxml

Expected Result: the tab order would be first the three vertical items on the left, then the three vertical items on the right, i.e. grouped by the containers. The TopLevelElement is the first in the tab order. (it works exactly in this way if no tabIndex is specified, however then we're not able to actuall change the tabIndex to e.g. switch One and Three for whatever reasons we might want to do so)

Actual Result: the tab ordering is horizontal, i.e. jumping between the two input forms. The TopLevelElement (w/o tabIndex specified) is last in the tab order.

Changing the nesting of containers will not work in modularized projects, and changing the ordering of childs would affect their display position (whereas going for Canvas container looses the AutoLayout).

Is there a way (possibly complicated, but preferably short of rewriting the SDK) to specify the tab ordering for single components, independently of other components?

if all else fails, would upgrading to Flex4 help in solving this issue?

like image 786
Zefiro Avatar asked Dec 13 '10 12:12

Zefiro


2 Answers

after a little research i found out the following:

  • if (even a single) DisplayObject has tabIndex > 0 it means that from now pressing TAB will only switcth focus between instances with tabIndex > 0
  • if there's only one instance with tabIndex > 0 it will be focused forever
  • if current.tabIndex - next.tabIndex > 1 it doesn't matter, tabIndex will increase to the next existing value
  • if several instances have equal tabIndex values they will recieve focus in a native order (as if tabIndex is never defined)
    -------------------------------------------------------------------------------

    so:

  • for setting up TAB navigation between some DisplayObjects (no matter where're they located in the display list) - just set tabIndex > 0 for every of them.

  • for removing an instance from TAB list - set tabIndex = 0 (but if there are only DisplayObjects with tabIndex == 0 left in the app - TAB will switch focus naturally between them). tabEnabled = false should be a better choice
  • for creating several groups of DisplayObjects with natural TAB order inside each one while keeping certain order between the groups - select one tabIndex for each group (small values appear first) and apply the tabIndex you selected to every member of a group.

    i think that too much instances in the TAB list are useless and annoying so the best way to manage it should be making it equal to list of top e.g. 10 most useful UI elements for current app state.

and a little bit of code (based on your example):
showclasses.NewFile.mxml:

<mx:VBox xmlns:mx="library://ns.adobe.com/flex/mx"
         xmlns:fx="http://ns.adobe.com/mxml/2009" data="1,2,3" creationComplete="addFocuses();">
    <fx:Script>
        <![CDATA[       
        private function addFocuses():void{
            var focs:Array = data.split(',');
            One.tabIndex = focs[0];
            Two.tabIndex = focs[1];
            Three.tabIndex = focs[2];           
        }       
        ]]>
    </fx:Script>
    <mx:Label text="One"/>
    <mx:TextInput id="One" />
    <mx:Label text="Two" />
    <mx:TextInput id="Two"/>
    <mx:Label text="Three" />
    <mx:TextInput id="Three"/>
</mx:VBox>

Main.mxml:

<mx:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx" 
    xmlns:th="showclasses.*">
    <mx:HBox>
        <th:NewFile data="1,2,3" />
        <th:NewFile data="1,2,3" />
        <th:NewFile data="1,2,3" />
    </mx:HBox>
</mx:Application>

if data values are 1,2,3; 1,2,3; 1,2,3 - focus moves horizontally from left to right (naturally because tabIndex is constant inside each row) and then switches to the left element of the next row down.
if they are 2; null,3; 0,null,1 (null == 0 for tabIndex) - focus will appear in the lower-right corner then in the upper-left and at last in the center.
hope this'll be useful

like image 196
www0z0k Avatar answered Nov 11 '22 18:11

www0z0k


I think some of the info www0z0k gives is no longer accurate (or maybe never was?) Just read the Flex Docs on tabIndex property:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html#tabIndex

Specifies the tab ordering of objects in a SWF file. The tabIndex property is -1 by default, meaning no tab index is set for the object.

If any currently displayed object in the SWF file contains a tabIndex property, automatic tab ordering is disabled, and the tab ordering is calculated from the tabIndex properties of objects in the SWF file. The custom tab ordering includes only objects that have tabIndex properties.

The tabIndex property can be a non-negative integer. The objects are ordered according to their tabIndex properties, in ascending order. An object with a tabIndex value of 1 precedes an object with a tabIndex value of 2. Do not use the same tabIndex value for multiple objects.

The custom tab ordering that the tabIndex property defines is flat. This means that no attention is paid to the hierarchical relationships of objects in the SWF file. All objects in the SWF file with tabIndex properties are placed in the tab order, and the tab order is determined by the order of the tabIndex values.

Edit: In my experience with Flex 3.3.0, assertion #1 and #2 made by www0z0k are wrong. For #1, the controls that you don't explicitly set tabIndex on will just always fall before all the controls you give positive numbers to in the tab order. For #2, it then follows and is observable that if you set just one tabIndex, that one control will just follow all other automatic controls in the tab order.

like image 38
Adam Nofsinger Avatar answered Nov 11 '22 20:11

Adam Nofsinger