Accessibility im jadice web toolkit

Artikel wurde auf Basis von jadice web toolkit 5.10.50.0 erstellt

Dieser Artikel beschreibt, wie Accessibility in Webanwendungen mit dem jadice web toolkit  erzielt werden kann.


Vergabe von Tabindices

Um die Tastatursteuerung von interaktiven Schaltflächen für blinde und motorisch eingeschränkte Nutzer zu ermöglichen, ist das Setzen des Tabindex-Attributs auf diesen notwendig.

https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex

Grundsätzlich gilt es, nur die Werte "0" (fokussierbar) und "-1" (explizit nicht fokussierbar) für den Tabindex zu verwenden.
Andere Werte - im Versuch eine manuelle Reihenfolge zu schaffen - können zu erheblichen Bedienungsschwierigkeiten führen.

Zum Setzen der Tabindices in GWT reicht i.d.R. folgender Call:

myWidget.getElement().setTabIndex(0); 

Eventuell müssen auf den Elementen zusätzlich noch KeyDownEvent-Listener registriert werden, damit der Nutzer bspw. eine Schaltfläche
auch mit SPACE und ENTER bedienen kann.

Vergabe von ARIA-Attributen

Für Nutzer mit Screenreader ist es sehr hilfreich, weitere Metadaten über die angezeigten, insbesondere die fokussierbaren, Elemente bereitzustellen. Dies erfolgt über die ARIA-Attributes.

https://wiki.selfhtml.org/wiki/HTML/Attribute/aria-*

Die wichtigsten Attribute in Kürze:

- role : Verleiht dem Element Semantik, bzw. stellt das Bedienkonzept des Elements klar. Bspw. kann ein <div> hiermit als "button" markiert werden.
- aria-label : Text, der vom Screenreader ausgegeben wird
- aria-disabled : Teilt dem Screenreader mit, ob die Schaltfläche deaktiviert ist
- aria-live:  Für programmatisch aktualisierte Inhalte, definiert das Verhalten mit dem aktualisierte Werte ausgegeben werden.

Listen von ARIA-Rollen:
- https://www.codeinwp.com/blog/wai-aria-roles/
- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques

In GWT können die Attribute zumeist über die Roles-Klasse gesetzt werden:

Roles.getButtonRole().set(showHideLeftPanel.getElement()); 
Roles.getButtonRole().setAriaLabelProperty(showHideLeftPanel.getElement(), EnterpriseMessages.INSTANCE.hideLeftPanel()); 

Zu Testen ist dies sowohl über die Verwendung eines Screenreaders (bspw. NVDA), als auch die Betrachtung des Accessibility TreeViews in den Chrome Dev Tools:
- https://www.nvaccess.org/download/
- https://developer.chrome.com/blog/full-accessibility-tree/

Unterstützung des betriebssysstem-eigenen Hochkontrastmodus

Nutzer mit visuellen Einschränkungen tendieren dazu, den Hochkontrastmodus ihres Gerätes zu verwenden. Dieser wird standardmäßig vom Browser unterstützt. Allerdings kann es notwendig sein, dass in diesem Modus einige Style-Korrekturen vorgenommen werden müssen, damit alles wirklich für den Nutzer gut erkennbar ist (bspw. Rahmen um Buttons).

Dies lässt sich mit folgender Media-Query im Stylesheet realisieren, die dann mit den eigenen Anpassungen befüllt werden kann:

@media only screen and (forced-colors: active) {} 

https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors

Zusätzlich sind Icons ausschließlich über den Icon-Font einzubinden, da Bilder nicht vom Hochkontrastmodus angepasst werden.

Umsetzung eines eigenen Hochkontrast-Themes

Besteht die Anforderung an einen eigenen Hochkontrastmodus, so kann hierfür die Theming API verwendet werden:

https://webtoolkit.jadice.com/showcase/index.html?connection=longpoll#!ThemingExample

Responsiveness

Um die Applikation auf kleineren Geräten accessible zu machen, sollte ein Mindestmaß an responsivem Verhalten gegeben sein.
Dazu zählt u.a. das Hinzufügen einer horizontalen Scrollbar bei der Toolbar bei Überlauf, sowie das automatische Einklappen
von Seitenpanels bei kleinen Fenstern.

Dies muss je nach Integration individuell implementiert werden. Als Startpunkt gibt es den Window Resize Handler:

https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event


 Code-Beispiel Seitenpanels
public class JadiceDocumentViewer implements IsWidget, RequiresResize {

    private boolean leftPanelAutoHide = false;
    private boolean rightPanelAutoHide = false;
    private Runnable updateLeftShowHidePanel;
    private Runnable updateRightShowHidePanel;
    private FlowPanel showHideRightPanel;
    protected FlowPanel showHideLeftPanel;
    private HandlerRegistration windowResizeHandlerRegistration;

    // ...

    public JadiceDocumentViewer() {
        // ...

        documentView.forceLayout();

        // Prepare a handler for any behaviors that can be considered "responsive" to size changes.
        // This handler will be registered as a onWindowResize event handler and called once initially.
        Runnable updateWindowSize = () - > {
            // Determine if the window was made larger or smaller, horizontally.
            // In the default update, we assume it was made "narrower" so we can hide elements that need to be hidden.
            final boolean direction = lastWidth == null || lastWidth > Window.getClientWidth();

            lastWidth = Window.getClientWidth();

            // Flag for calls to doLayout() at the end - used to prevent unneccesary re-rendering.
            boolean updateLayout = false;

            // First thing we check is, if we need to collapse or expand any side panels.
            // This will remove clutter on smaller windows, while respecting the choice of the user
            // if he has manually set a panel to be hidden.
            final boolean exceedsLeft = LEFT_TAB_PANEL_WIDTH > Window.getClientWidth() / 4;
            final boolean exceedsRight = RIGHT_TAB_PANEL_WIDTH > Window.getClientWidth() / 3;

            if (direction) { // Window is getting smaller.
                if (showLeftPanel && exceedsLeft) {
                    leftPanelAutoHide = true;
                    showLeftPanel = false;
                    updateLayout = true;
                    updateLeftShowHidePanel.run();
                }

                if (showRightPanel && exceedsRight) {
                    rightPanelAutoHide = true;
                    showRightPanel = false;
                    updateLayout = true;
                    updateRightShowHidePanel.run();
                }
            } else { // Window is getting larger.
                if (leftPanelAutoHide && !exceedsLeft) {
                    leftPanelAutoHide = false;
                    showLeftPanel = true;
                    updateLayout = true;
                    updateLeftShowHidePanel.run();
                }

                if (rightPanelAutoHide && !exceedsRight) {
                    rightPanelAutoHide = false;
                    showRightPanel = true;
                    updateLayout = true;
                    updateRightShowHidePanel.run();
                }
            }
			
			// ...

            if (updateLayout) {
                doLayout();
                documentView.forceLayout(); // Skip the animation.
            }
        };

        windowResizeHandlerRegistration = Window.addResizeHandler(event - > updateWindowSize.run());
        updateWindowSize.run(); // Initial update.
    }

    // ...
    protected void createLeftTabPanel() {
        // ...
        updateLeftShowHidePanel = () - > {
            if (showLeftPanel) {
                rightArrow.asWidget().getElement().getStyle().setDisplay(Display.NONE);
                leftArrow.asWidget().getElement().getStyle().setDisplay(Display.BLOCK);
                Roles.getButtonRole().setAriaLabelProperty(showHideLeftPanel.getElement(), MyMessages.INSTANCE.hideLeftPanel());
            } else {
                leftArrow.asWidget().getElement().getStyle().setDisplay(Display.NONE);
                rightArrow.asWidget().getElement().getStyle().setDisplay(Display.BLOCK);
                Roles.getButtonRole().setAriaLabelProperty(showHideLeftPanel.getElement(), MyMessages.INSTANCE.showLeftPanel());
            }
        };
    }
}

MessageBoxen

Verwendungen von alert() sind generell für den Produktivbetrieb ungeeignet. Stattdessen sollte auf die MessageBox-Klasse (com.levigo.jadice.web.client.ui.MessageBox) des JWTs zurückgegriffen werden.

Tastaturbedienbarkeit von Annotationen

Hierbei handelt es sich weitestgehend um ein integrationsunspezifisches Produktfeature:

Annotationen sind grundsätzlich mit den Pfeiltasten bewegbar.

Da sie aber keine DOM-Elemente sind, müssen Sie explizit bspw. über ein AnnotationPanel selektiert werden. Damit hier eine reibungslose Übernahme des Fokus sichergestellt ist, müssen mindestens folgende drei Calls bei der Selektion stattfinden:

pageView.getToolManager().getTool(AnnotationTool.class).clearAnnotationSelection(); 
pageView.getToolManager().getTool(AnnotationTool.class).resetEditorFocusability(); 
pageView.getToolManager().getTool(AnnotationTool.class).selectAnnotations(page, true, (ShapeBasedAnnotation) a); 

Dieses Feature funktioniert mglw. nicht, wenn ein Screenreader aktiv ist. Diese überschreiben mglw. die Funktion der Pfeiltasten.

Behandlung von Letter Spacing Injections

Manche visuell oder kognitiv eingeschränkten Nutzer profitieren von erhöhtem Zeichenabstand.
Da die meisten Applikationen dies nicht von Haus aus anbieten, wird hierfür typischerweise ein Bookmarklet verwendet, das Style in die aktuelle Seite injected.

Diesen Fall zu behandeln ist grundsätzlich nicht immer möglich, es sollte aber sichergestellt sein, dass das Layout der Integration einigermaßen anpassungsfähig ist, um Überlappungen von Inhalten zu vermeiden.

Konkret bedeutet dies, auf absolute Positionierungen von Elementen soweit nur möglich zu verzichten und stattdessen auf Flexboxen zurückzugreifen.
Ein manuelles Layouting mit den Display-Regeln display: flex; bzw. in manchen Fällen display: grid; erzielt hier die besten Erfolge (im Vergleich zu der Verwendung von GWT-Standardpanels).

- Bookmarklet: https://cdpn.io/pen/debug/YLMqbo
- Flexboxen: https://css-tricks.com/snippets/css/a-guide-to-flexbox/