Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow appModules to specify custom tab orders #5960

Open
derekriemer opened this issue May 17, 2016 · 11 comments
Open

Allow appModules to specify custom tab orders #5960

derekriemer opened this issue May 17, 2016 · 11 comments

Comments

@derekriemer
Copy link
Collaborator

I'm interested in taking this one on.
I propose creating an api for allowing appModules to specify what to do next when tab or shift+tab is pressed. For example, if I press tab in an application and a non-desirable behavior occurs, I would like to be able to easily script NVDA to know that it should override default tab behavior. Fore example, if I tab in teamtalk to the tree view, I skip over the list of messages, but shift-tab hits it correctly. Having an api that lets me specify that when the tabPanel comes into focus, tab moves to the message list would be desirable. My idea is as follows.
Create an api that calls a given method (maybe of the appModuleHandler.AppModule class, similar to nvdaObjectInit that takes an NVDAObject, and specifies whether it should be tabbable or not, and if it is, what tabIndex it should have. We also need to specify that an object should never be able to be tabbed to. We could do this statically, but I'm curious if rather than keeping a list of things which tab can reach, we need this to happen dynamically. So then we'd need a way for an individual NVDAObject or appModule to say "this object has focus, tab was pressed, the object that has these properties should be tabbed to." I know you theoretically could do this with a script, but having an api for it might be nice.

@jcsteh
Copy link
Contributor

jcsteh commented May 17, 2016

I think an AppModule method for this which is always called is a bad idea for several reasons:

  1. Do we really want to "fake" tab order in most cases? This makes the interface very different to that for other keyboard users. There are cases where tabbing is essential for efficiency, but I wouldn't want to encourage this as a general rule. There are cases where one really should just use object navigation.
  2. It would mean we're overriding the tab key always (since we need to check for possible tab order overriding), and most of the time, we don't need to. We never want to intercept a key when we don't need to.
  3. Having a function called/checked for on every press of the tab key when we don't need to is a pointless performance hit.
  4. This is very specific to app modules, but "apps" aren't the only case where one might want to override tab order. What about a framework or component found in multiple apps?
  5. If we do it in app modules, that doesn't account for an app which has multiple foreground windows. Things could get very messy if a user opened a modal dialog and then pressed tab. Plug-in authors tend to miss cases like that.

If we really do need something like this, I think a better way to do it is to create an NVDAObject mix-in class (similar to the stuff in NVDAObjects.behaviors) which supports tab order overriding. The overlay would bind tab and shift+tab. From there, there are a couple ways it can work:

  1. Authors would then just implement previousInTabOrder and nextInTabOrder properties. The disadvantage of this is that the author has to create separate overlay classes for each control in the fake tab order or handle it all within one overlay class.
  2. The overlay could be inserted on an ancestor object and tab and shift+tab would be allowed to propagate to that ancestor. There could then be a method which is called with an object and a direction, returning a new object.
  3. Again, the overlay goes on an ancestor, but we have a method which returns a tab index for an object as you suggested originally. The problem with this is that you have to calculate or keep track of the current tab index, which could be quite expensive if you have to calculate it. (You can't assume the user will only ever press tab; they might move focus some other way.)

@derekriemer
Copy link
Collaborator Author

If we really do need something like this, I think a better way to do
it is to create an NVDAObject mix-in class (similar to the stuff in
NVDAObjects.behaviors) which supports tab order overriding.
Actually, that's a much better solution. By Mix In, what exactly does
that mean? I think I get the premise but I am not quite sure.

  1. Authors would then just implement previousInTabOrder and
    nextInTabOrder properties. The disadvantage of this is that the author
    has to create separate overlay classes for each control in the fake tab
    order or handle it all within one overlay class.
    another disadvantage is that the author is responsible for the logic of
    finding the appropriate objects to go in the order. From my scripting
    experience (correct me if I am wrong) NVDA doesn't have generic methods
    for finding an object in a hierarchy with an interface similar to query
    Selectors. For example, I can't do
    focus.parent.parent.findObject("[role=button]
    UIAClassName=datePickerControl"), and I have no clue how a tab order
    would work for an object that isn't in the object hierarchy, like the
    list items of some multicolumn list boxes. So we'd have appModule code
    similar to obj.nextInTabOrder =
    obj.parent.parent.simpleNext.children[3].children[2].next.firstChild,
    which seems like an accident waiting to happen if the author updates
    their app.
  2. The overlay could be inserted on an ancestor object and tab and
    shift+tab would be allowed to propagate to that ancestor. There could
    then be a method which is called with an object and a direction,
    returning a new object.
    Would this work in browseMode documents? Wouldn't that essentially be a
    treeInterceptorHandler? I think only one treeInterceptorHandler can be
    registered to any object. I like this because it is more flexible for
    the app to dynamically say This object should now not be custom
    tabbable, say if the thing that was nest in tab order changed to be
    unavailable (an inaccessible wizard control hiding the next button for
    example).
  3. Again, the overlay goes on an ancestor, but we have a method which
    returns a tab index for an object as you suggested originally. The
    problem with this is that you have to calculate or keep track of the
    current tab index, which could be quite expensive if you have to
    calculate it. (You can't assume the user will only ever press tab; they
    might move focus some other way.)
    I like this method less and less the more I think about it. It's too
    much like tabIndex on the web, and we all know how tabindex > 0 went down.

@derekriemer
Copy link
Collaborator Author

derekriemer commented Jun 27, 2016

After having to do this for the keyboard shortcuts dialog of notepad++ I believe I have found the best way to do this.

  • implement a property (Maybe nextTab, previousTab) that gives back the object to set focus to when tab is pressed.
  • put scripts on the base NVDA Object that manage this.

An example tab key script is in the prototype implementation. 1
Also, I have designed the overlay class to manage tab order with the properties. The fallback is to send the gesture on if the property is none. This would happen if it returns None, hinting that there's no next tab order to manage.

@jcsteh
Copy link
Contributor

jcsteh commented Jun 30, 2016

  • put scripts on the base NVDA Object that manage this.

Any properties and scripts associated with this should absolutely not be placed on the base NVDAObject. It should be a mix-in behaviour. This means that stuff that doesn't need this isn't impacted by it. We don't want to intercept the tab key unless we must.

@derekriemer
Copy link
Collaborator Author

derekriemer commented Jun 30, 2016

@jcsteh commented on Jun 29, 2016, 9:11 PM MDT:

Any properties and scripts associated with this should absolutely not be placed on the base NVDAObject. It should be a mix-in behaviour. This means that stuff that doesn't need this isn't impacted by it. We don't want to intercept the tab key unless we must.

I'm okay with that approach, maybe a class in behaviors, called CustomTabOrder?

@jcsteh
Copy link
Contributor

jcsteh commented Jun 30, 2016 via email

@derekriemer
Copy link
Collaborator Author

Sure.
And for properties: Should users define a _get_nextInTabOrder and _get_previousInTabOrder method?

@derekriemer
Copy link
Collaborator Author

to keep with the _get_name and friends methods?

@jcsteh
Copy link
Contributor

jcsteh commented Jun 30, 2016 via email

@LeonarddeR
Copy link
Collaborator

cc @derekriemer: Do you have an update for this issue?

@derekriemer
Copy link
Collaborator Author

I have a prototype, and I need to work out the kinks. I haven't worked on this in probably close to a year, but I'd like to finish it at some point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants