protofunc()

HTML5 placeholder Attribut und Zugänglichkeit

Tags: HTML 5, accessibility, deutsch, javascript

Angestachelt durch Robert´s Post/Script zu “Adding HTML5 placeholder attribute support through progressive enhancement” und vor allen Dingen einigen Kommentaren habe ich mein eigenes HTML5 Placeholder-Script nochmals überarbeitet.

Eine Sache, die ich verändert habe, betrifft die Frage, ob das placeholder-Attribut ein Ersatz des label-Elements darstellen kann oder nicht. Laut HTML5 Spezifikation stellt das placeholder-Attribut keinen Ersatz für das label-Element dar, sondern dient nur als zusätzlicher Hint. Meiner Meinung nach ist dies ein weiterer kleiner Fail der Zugänglichkeit in der HTML5 Spezifikation. HTML5 wollte sich doch eigentlich an der Praxis orientieren und das was man mit dem placeholder-Attribut optisch erreicht, wird heutzuage millionenfach als platzsparender Ersatz eines label-Elements praktiziert (Stichwort: overLabel).

Das nun verbesserte Script kann letztendlich als stark vereinfachter Implementierungsvorschlag für Browser dienen. Wird ein placeholder-Attribut entdeckt, wird eine a11y-Namen Berechnung durchgeführt, wird diese nicht fündig, wird der Text des placeholder-Attributs zum a11y-Namen gemacht. Dies mag nicht ganz der Spezifikation entsprechen, ist jedoch deutlich näher an der Realität. Wird dagegen sowohl eine a11y-namengebende Auszeichnung als auch das placeholder-Attribut verwendet, bleibt es selbstverständlich bei dem ursprünglichen Namen und das placeholder-Attribut wird lediglich als zusätzlicher Hint angesehen.

Written June 18, 2010 by
protofunc

jme – das erste HTML6 audio/video development kit

Tags: HTML 5, accessibility, deutsch, javascript, jquery, video

Es gibt zwei Dinge mit denen mein jMediaelement Projekt allein nicht beschrieben wäre: HTML5 audio / video einerseits und Player-Script andererseits. Die von jme genutzen HTML5 Media-Elemente sind nur eine Abspieltechnologie von insgesamt dreien. Würde man den HTML5 Teil weglassen und damit lediglich Flash und VLC als Abspieltechnik dienen, hätte jme weiterhin imense Vorteile für Frontendentwickler. Das liegt unter anderem daran, daß jme eben kein reines Player-Script ist.

jme besteht aus 2 Hauptkomponeten:

  1. einem leichtgewichtigem, kugelsicheren reinen embed-Teil
  2. der “Vollversion”, welche neben dem embed-Teil, auch die DOM-API und die Markup-API (Controls) enthält

Media-Elemente einbinden schnell und einfach wie ein Bild

In vielen Fällen, in denen ein Entwickler einen Videoplayer einbinden muß, ist kein spezielles Design und kein besonderes Feature verlangt. Es geht schlicht weg darum einen Player zum Abspieln von Videos/Audios zu haben.

Der reine embed-Teil von jme ist daher in einer separaten Version als leichtgewichtiger Helper eingebunden.

Der notwendige HTML-Code ist identisch mit dem HTML5 Code:

<video src="myvideo.webm" poster="mein-poster.jpg" controls="controls">
	<!-- Fallback Content -->
</video>

Damit dies auch ohne Bugs und mit älteren Browsern funktioniert, kopiert man einfach die mm.embed.min.js aus dem packages Ordner vom jMediaelement-Download zusammen mit der player.swf und – falls man youtube-videos einbinden will – der yt.swf in einen gemeinsamen Ordner.

Danach kann man bei DOM-Ready seine HTML5 Media-Elemente mit jmeEmbed registieren:

$(document).ready(function(){
	$('audio, video').jmeEmbed({showFallback: true});
});

Der embed Teil kümmert sich um die Erkennung von Browser- und Plugin-Features, deren Einbindung/Initialisierung, der Normalisierung der HTML5 Mediaelement Attribute sowie der hiermit zusammenhängenden Bugs. Ist die Vollversion von jme eingebunden, wird hierdurch auch die DOM-API initialisiert. Die Vorteile für den Entwickler:

  • er hat einen einfachen, intuitiven und semantischen Einbindecode
  • Sofern ein Video mit richtigem Container/Codec eingebunden wird, erhöht sich die Zugänglichkeit für bestimmte Endgeräte
  • der Entwickler ist nicht gezwungen mehrere Video-Formate anzubieten. Letztendlich können auch nicht HTML5-Formate wie zum Beispiel flv-Dateien oder Youtube-Seiten als Datengrundlage genommen werden.
  • Die jeweiligen Browser-, Plugin-Player rendern die Kontrolelemente automatisch, so daß keine weiteren HTML-/CSS-Anpassungen anfallen.

Kurz: Gegenüber der Art wie man derzeit Videos einbindet, kommt man hier mit jme schneller, semantischer und manchmal auch zugänglicher ans Ziel.

HTML ist die Schnittstelle zwischen JavaScript und CSS oder “divide et impera” – Teile und herrsche

Der zweite Teil ist keine reine JavaScript API, sondern gliedert sich in DOM-API und Markup-API auf. Dieser Aufbau ist der Erkenntnis geschuldet, daß JavaScript nur wenig HTML diktieren sollte und HTML unter anderem eine Schnittstelle zwischen einerseits JavaScript und CSS und andererseits zwischen Frontend und Backend darstellt.

Die Erstellung von funktionsfähigen Kontrollelementen ist Aufgabe der Markup-API. Features welche nicht über die Markup-API realisiert sind, können mit Hilfe der DOM-API erledigt werden.

HTML6 Control-Elemente = jme Markup-API/Kontrollklassen

Anstatt, daß durch einen JavaScript-Aufruf ein Haufen unsemantischer HTML-Code um den Player gepackt wird, besteht jme aus kleinen Bausteinen, die einfach zusammengesetzt werden können und so einen individuellen Player ermöglichen. Hierbei besteht als oberstes Prinzip, daß sich die verwendeten Bausteine flexibel in die HTML-/DOM-Welt integrieren laßen müßen.

Stell Dir vor HTML5 ist Schnee von gestern und das W3C würde mit HTML6 einige neue Elemente spezifizieren, welche als Kontrollelemente für das audio- und video-element dienen. Ein Abspiel-/-Pause Button könnte wie folgt aussehen:

<button type="play-pause">play / pause</button>

Das schöne daran, der Browser kümmert sich um Funktionalität und Zugänglichkeit und der Entwickler besitzt die Flexibilität die Kontrollelemente erstens frei zu platzieren und zweitens mit weiterem HTML anzureichen, um Sie dann beispielsweise per CSS besser zu stylen oder um ein gewünschtes Userinterface-Verhalten zu erreichen.

Damit der Browser weiß, mit welchem Mediaelement das jeweilige Kontrollelement verbunden ist, werden die Kontrollelemente gemeinsam mit dem Mediaelement in einem ebenfalls neuen Element mit dem Namen player gewrappt. Das Ergebnis sieht dann wie folgt aus:

<player>
	<video src="myvideo.webm"></video>
	<button type="play-pause">play / pause</button>
</player>

Die jme Kontrollelement-Bausteine funktionieren hierbei auf ähnliche Weise. Die neuen Kontrollelemente werden durch beliebige valide Elemente mit bestimmten HTML-Klassen symbolisiert und lediglich das Wrapper-Element “player” wird durch einen JavaScript Aufruf ersetzt:

<div class="player-wrapper">
	<video src="myvideo.webm" controls="controls"></video>
	<button class="play-pause">play / pause</button>
</div>

Das jQuery-Plugin ‘jmeControl’, welches auf dem wrapper-Element aufgerufen wird, registriert hierbei sozusagen die einzelnen Kontrolelemente und ruft bei Bedarf, die embed-Methode auf. Genau wie bei der jmeEmbed-Methode muß grundsätzlich keine weitere Konfiguration vorgenommen werden.

$('div.player-wrapper').jmeControl();

Werden die nativen Kontrollelemente nicht angezeigt, wird der Flashplayer – sofern nicht ausdrücklich anders konfiguriert – automatisch in den wmode=transparent Zustand versetzt, um auch schöne Overlay-Kontrollelemente oder Overlay-Logos über dem Video erstellen zu können.

Man kann selbstverständlich auch mehrere Kontrollelemente unterbringen:

<div class="player-wrapper">
	<video src="myvideo.webm" controls="controls"></video>
	<button class="play-pause">play / pause</button>
	<span class="current-time">--:--</span>
</div>

jme kümmert sich hierbei nicht nur um die Funktionalität und rendert die jeweils aktuelle Zeit in das span-Element und erstellt ein Play-Pause-Toggle Verhalten aus dem button-element, sondern auch um die Zugänglichkeit. Insbesondere dann, wenn zum einfacheren Stylen statt dem button-Element ein span-Element genommen wird, fügt jme die gewollte Semantik per WAI-ARIA ein. Folgendes geschriebenes HTML ….

<div class="player-wrapper">
	<video src="myvideo.webm" controls="controls"></video>
	<span class="play-pause">play / pause</span>
	<span class="current-time">--:--</span>
</div>

…wird durch jme in folgendes HTML umgewandelt:

<div class="player-wrapper">
	<video src="myvideo.webm"></video>
	<span role="button" tabindex="0" class="play-pause">play / pause</span>
	<span role="timer" class="current-time">--:--</span>
</div>

Neben allgemein üblichen Video/Audio Kontroll-Elementen bietet jme über Plugins auch Verhaltensklassen für eine Playliste und Captions an.

Die Tatsache, daß wir eine Markup-API verwenden, die nicht von JavaScript diktiert wird, heißt aber nicht, daß wir nun alle möglichen Kontroll-/Verhaltensklassen auswendig lernen und wieder und wieder ins HTML schreiben müßen. jme bietet im utils-Ordner das jmeEmbedControls-Plugin an, welches nichts anderes macht, als einen Haufen Kontrollelemente in das Element, auf dem es aufgerufen wurde, einzufügen, um dann hierauf die jmeControl-Methode auszuführen. Diese Methode ist sozusagen das Schnellstarter-Kit für jme. Er ist bewußt ausgelagert, um jedem Webentwickler eine projektspezifische Anpassung zu ermöglichen.

einheitliche DOM-API statt vereinheitlichte JavaScript-API

Die vereinheitlichte JavaScript-API ist bei jme bewußt eine DOM-API, da nur so sich Mediaelemente, sei es nun HTML5, Flash oder VLC, in den normalen Workflow eines Frontendentwicklers integrieren können.

Ein Beispiel: JW-Player for HTML5 ist das erste HTML5 Player-Script, welches eine dokumentierte API vorweisen kann. Und obwohl jQuery, also ein klassischer DOM-Wrapper, als JavaScript Grundlage gewählt wurde, wurde keine DOM-API implementiert. Um einen Player zu scripten, sieht der Code wie folgt aus:

$.jwplayer().play();

Laut Dokumentation wird nun immer auf dem ersten eingebundenen Mediaelement die Methode play aufgerufen, möchte man nun jedoch auf einem anderen Mediaelement Methoden ausführen, muß man bereits zum Zeitpunkt der Initialisierung die Referenz auf dieses Objekt speichern und sicherstellen, daß alle Module die hiermit interagieren müßen an diese Referenz kommen:

//holen der Referenz auf ein bestimmtes Video bei der Initialisierung
var player = $('#stage video').jwplayer({
	flashplayer:'/static/files/player.swf',
	skin:'/static/files/five.xml'
});

//initialisieren aller anderen videos
$('video').not('#stage video').jwplayer({
	flashplayer:'/static/files/player.swf',
	skin:'/static/files/five.xml'
});

//arbeiten mit der gespeicherten Referenz
//irgendwo ganz anders im script
player.play();

Mit einer DOM-API sieht die Sache dagegen anders aus, denn die API ist Teil des DOMs und kann wie jede DOM-API mit der jQuery-Selektorengine selektiert werden.:

$('#stage video').play();

Ebenso wie man bei anderen DOM-Elementen auf Events horcht, Methoden chained etc., funktioniert dies auch bei jme. Das video bzw. das audio Element bleiben hierbei immer das zentrale API Element, gleichgültig ob tatsächlich HTML5 oder eben ein Plugin zur Anzeige der Mediainahlte genutzt wird.

$('video').bind('ended', function(){
	$(this).attr('autoplay', true).loadSrc('my-video2.webm');
});

Kurz: nur so kann Scripten Spaß machen.

Fazit

Merkwürdig aber wahr: jme stellt in vielen Punkten einige Weltrekorde auf:

  1. jme ist das erste audio/video Script mit Fallback, welches eine dokumentierte DOM-API besitzt (das werden garantiert einige nachmachen, da es sooo nahe liegt)
  2. jme ist das erste audio/video Script, welches eine Markup-API für Kontroll- und Zustandselemente verwendet
  3. jme ist – und das ist wirklich traurig – das erste Script seiner Art, welches semantischen Code erlaubt/nutzt (notfalls mit WAI-ARIA forciert)

Oder anders gesagt: jme versucht nicht aus HTML5 Flash zu machen, sondern aus Flash HTML5.

Written June 6, 2010 by
protofunc

WAI-ARIA – Epic Fail: Wenn der Screenreader nicht mehr aufhören will zu plappern

Tags: accessibility, deutsch, javascript

Aria Live Regions gehören zu den spannenderen Attributen bei Wai-Aria und haben – trotz der noch schlechten Unterstützung durch Screenreader – für einiges an Aufsehen gesorgt. Das Aria Best Practices Dokument ist ein guter Einstieg in Liveregions. Neben dem Attribut aria-live können die Attribute aria-busy, aria-relevant und aria-atomic das Vorleseverhalten im Screenreader beeinflußen. Bei Liveregions muß immer bedacht werden, daß ein ständig oder zu viel plappernder Text nervt und den User eher bei der Erfüllung seiner Aufgaben stört als weiterhilft. Dieses Problem wird allerdings vom oben genannten Best Practice Dokument mehr als ausgiebig beschrieben, so daß selten Fehlimplementierungen zu finden sind.

Wann Liveregions überhaupt Sinn machen?

Das grundsätzliche Mißverständnis von Liveregions liegt darin, daß viele Entwickler Liveregions als generelle Antwort auf das Problem von dynamischen Änderungen des Contents sehen. Sie also meinen, das ein HTML-Bereich immer als Liveregion ausgezeichnet werden sollte, wenn sich der Content, zum Beispiel aufgrund einer Ajax-Response, dynamisch ändert.

Hierbei wird allzu gerne folgender Text des Best Practices Dokuments überlesen/mißverstanden:

Live regions enable assistive technologies, such as screen readers, to be informed of updates without losing the users’ place in the content.

Damit hört sich das Feature “Liveregion” ziemlich cool an (und ist es auch). Man kann nämlich ohne den Fokus des Users ändern zu müßen, Informationen vorlesen laßen: Zum Beispiel kann ein Warenkorb darüber informieren, daß das Produkt nun erfolgreich im Warenkorb abgelegt wurde, ohne daß der User wegbewegt wird, er also weiterhin innerhalb des Produktskatalogs surfen kann. Er kann über neu eingegangene Chat-Nachrichten informiert werden, ohne daß er bei seiner eigenen Eingabe einer Nachricht gestört wird. Oder der User kann mit Hilfe der Rolle alert, welche implizit eine Liveregion ist, auf Fehler innerhalb eines Formularprozeßes aufmerksam gemacht werden, ohne ihn am weiteren Ausfüllen anderer Formularfelder zu behindern.

Leider wird die Kehrseite hieraus nicht deutlich gemacht und es kommt dadurch zur Fehlimplementierung. Diese Kehrseite könnte beispielsweise wie folgt lauten:

Liveregions ermöglichen es assistiven Technologien über geänderte Inhalte informiert zu werden, ohne daß der User mit diesen Inhalten – zumindest sofort – interagieren kann/soll/muß.

Ist der Text etwas länger, stärker strukturiert oder bietet gar Eingabemöglichkeiten wie Links, Formulare oder andere Eingabewidgets, möchte der User diesen Text nicht dumpf vorgelesen bekommen, er möchte mit diesem interagieren. Hierzu muß er jedoch den aktuellen Bereich verlaßen und zum neu geänderten Content gebracht werden. Dies ist eine Aufgabe, die von Screenreadern in Kombination mit Liveregions (noch) nicht erfüllt wird (eine gewisse Ausnahme stellt hier der Screenreader Orca dar, welcher auf Wunsch des Users auch zu einer Liveregion springt), von der Aria-Spezifikation nicht gefordert wird (obwohl es Sinn machen würde), aber letztendlich auch gar nicht erfüllt werden muß.

Für diese Art von Content-Änderung benötigt man nämlich kein Aria. Die Lösung ist kinderleicht zu impelentieren und funktioniert auch in vielen nicht Aria-fähigen Screenreader-Browser-Kombinationen. Die Lösung heißt focus.

Wenn wir beispielsweise folgendes geändertes HTML haben…

<div id="live-content">
	<h2 tabindex="-1">Überschrift von neuem Content</h2>
	<!-- weiterer Inhalt -->
</div>

können wir mit folgender Funktion …

function setFocus(elem){
	setTimeout(function(){
		try {
			elem.focus();
		} catch(e){}
	}, 0);
}

den Fokus auf folgende Weise…

setFocus(document.getElementById('live-content').getElementsByTagName('h2')[0]);

ganz einfach verschieben, so daß die Überschrift des neuen Contents vorgelesen und der User mit dem neuen Content interagieren kann. Das uncolle an dieser einfachen Art für Zugänglichkeit zu sorgen, ist einzig und alleine, daß wir im Prinzip praktisch keine neue Technik einsetzen und es potentiell die Möglichkeit gibt, daß es auch in “älteren” Screenreadern funktionieren könnte.

Wer dies in möglichst vielen Screenreadern zum Laufen bringen möchte, sollte sich den großen Screenreader Fokus Test durchlesen.

Written December 30, 2009 by
protofunc

WAI-ARIA – Epic Fail (Too much accessibility – good intentions, badly implemented)

Tags: accessibility, deutsch, javascript

Wenn Frontend-Entwickler neues Spielzeug bekommen, passiert eigentlich immer das gleiche. Sie laden sich, wenn nicht bereits geschehen, die neueste Alpha/Beta Version von Safari, Firefox und / oder Opera herunter und fangen dann an, die neuen Techniken zu testen, zu experimentieren und schließlich einzusetzen.

Bei WAI-Aria ist dies anders. Hier wird zwar bereits von einigen fleißig eingesetzt, aber kaum ein Entwickler kommt auf die Idee sich die neueste Version von Jaws herunterzuladen, zu installieren und zu testen. Hinzu kommt, daß viele Frontendentwickler eigentlich mehr so etwas wie theologisch und / oder philosophisch veranlagte Semantik-Professoren der HTML-Meta-Ebene sind, als gestandene Entwickler. Anstatt Aria so zu implemtieren, daß es funktioniert, wird lieber nach einem tieferen Sinn gesucht und die Aria Attribute und das Verhalten implementiert, welches nach der jeweiligen tiefen, manchmal religiösen, Überzeugung das semantisch näher liegende ist.

Nicht anders ist es zu erklären, daß mit Aria ausgetattete Javascript Widgets häufig größere Barrieren darstellen, als die identische Widget-Version, welche überhaupt kein Aria einsetzt.

Im Prinzip ist die Aria-Entwicklung ziemlich leicht, wenn man sich an die Fakten hält:

  • Was sagt das Aria-Best Practice Dokument (für Entwickler wichtiger als die Spezifikation)
  • Was für Accessibility-Informationen und welches Verhalten zeigen native, zugängliche Applikationen (MSAA-Inspect und/oder Accessibility Probe)
  • Mit welchen Aria-Attributen erreiche ich die selben Accessibility-Informationen im HTML
  • Und das wichtigste: Was liest mir die aktuellste Jaws-Version im aktuellsten Firefox vor (Vorsicht: Nicht wenige User verwenden häufig ältere Jaws-Versionen und den Internet Explorer 7/8)

Im Ergebnis tue ich mich mit diesem Zustand schwer. Die meisten Veröffentlichungen in diesem Blog zum Thema Barrierefreiheit gehen auf ein einziges Gefühl zurück, nämlich Wut über die Werke anderer Entwickler. In der Regel schreibe ich erst einen bitter bösen “Pranger-Artikel”, um diesen dann zu verwerfen und einfach ein Tutorial zu schreiben, wie man es hätte besser machen können, ohne den Bezug zum Original zu nennen. Ich glaube letztendlich, daß beide Formate nämlich Pranger-Artikel einerseits und Tutorial andererseits nicht weiterhelfen und wechsel hiermit nun das Format. Die WAI-Aria Epic Fail-Reihe soll nicht unbedingt zeigen, wie man es besser macht, sondern typische Fehlansätze bei der Aria Entwicklung aus der Praxis aufgreifen und darauf aufmerksam machen, ohne dabei Texte bzw. Scripte bzw. ihre Autoren/Entwickler an den Pranger zu stellen. Obwohl ich selbst inzwischen, gerade durch Beschäftigung mit Aria – merke, daß ich häufig gar kein Aria brauche, um Widgets überhaupt zugänglich zu machen, werde ich mich nur auf Aria konzentrieren, da hier zumindest die Zukunft zugänglicher Widgets liegt.

Den Anfang machen die Aria-Liveregions.

Written by
protofunc

Der große Screenreader Focus-Test

Tags: accessibility, deutsch, javascript

Das Versetzen des Fokus, ist ein recht effizientes Mittel, um dynamische Änderungen im HTML Screenreader-Nutzern bekannt zu machen oder Probleme der Linearisierung beim Scripting zu umgehen (beispielsweise beim Anzeigen eines Dialogs bzw. einer Lightbox). Die Möglichkeit praktisch jedes beliebige Element zu fokusieren ist wohl einer der wichtigsten Bausteine für Wai-Aria. Das Schöne: Grundsätzlich sollte diese Technik auch in nicht aria-fähigen Browser-/Screenreader-Kombinationen möglich sein.

Gleichzeitig gibt es jedoch Unterschiede zwischen Screenreadern in der Unterstützung der focus-Methode. Der generelle Support dieser Methode ist bereits beim wohl wichtigsten Screenreader Jaws je nach Version unterschiedlich. Hier zeigt sich, daß Jaws 10 einen großen Sprung nach vorn gemacht hat und praktisch gar keine Probleme macht, währenddem Jaws 8 bzw. Jaws 9 noch einige Probleme haben. Möchte man diese älteren Versionen unterstützen, sollte man wissen, was geht und was nicht.

Zum Testaufbau

Getestet wurde mit den Jaws Versionen 8, 9 und 10 sowie mit den Browsern IE8, IE7 sowie Firefox 3.5. Zusätzlich wurde im IE8 mit Cobra 8.1 und Webformator 2.4c sowie mit NVDA 0.6p3.2 im Firefox 3.5 getestet. Abschließend wurde nochmals mit der aktuellen Beta-Version von NVDA (NVDA 2009.1beta1) getestet. Im wesentlichen hat sich herausgestellt, daß sich IE7 nicht von IE8 und Jaws 8 nicht von Jaws 9 unterscheiden, so daß die Testergbenisse zusammengefaßt werden.

  • Der Fokus sollte aufgrund eines Klicks auf einen Link, als Kontrollelement, verschoben werden
  • Jaws sollte die ganze Zeit über im normalen Modus arbeiten (Virtueller Cursor Modus war an, kein EINFÜGEN + Z)
  • Eine zusätzliche Verwendung von Aria-Attributen, welche den Screenreader in den Applikationsmodus versetzen könnte, war verboten
  • Der Buffer von Jaws wurde nicht manuell upgedatet (kein Einfügen + ESC)

Der Test galt nur als bestanden, wenn der Screenreader den Inhalt des fokusierten Bereich vorgelesen hat und sowohl der virtuelle Cursor als auch der “physikalische” Fokus richtig verschoben war. Der User also sowohl mit den Pfeiltasten als auch mit der Tab-Taste vom neu fokusierten Element aus arbeiten kann.

Welche Elemente können fokusiert werden

Normalerweise können – laut HTML 4 Spezifikation – nur Interaktionselemente, welche nicht versteckt oder deaktiviert sind, fokusiert werden (also Links, Buttons, Eingabefelder etc.). Mit dem tabindex-Wert -1 können diese für den User unfkousierbar gemacht werden, währenddem sie weiterhin durch Javascript fokusiert werden können. Alle getestet Screenreader-/Browser-Kombinationen konnten ohne Probleme diese Elemente im Test fokusieren.

Mit Hilfe des tabindex-Attributs sollen ebenfalls alle anderen Elemente fokusierbar gemacht werden können. Hat das tabindex-Attribut den Wert 0 kann dieses Element sowohl durch den Nutzer als auch durch Javascript fokusiert werden. In allen getesteten Screenreadern konnten solche Elemente durch den User fokusiert werden. In Jaws 8/9 mit Internet Explorer konnten allerdings Elemente, die normalerweise nicht foksuierbar sind, nicht mit der Javascript-Methode focus fokusiert werden (Beispiel: span[tabindex=0], span[tabindex=-1]). Als Ausnahme dieser Regel stellten sich Überschriften heraus. Befand sich das zu fokusierende Element in einer Überschrfit bzw. war es eine Überschrift, konnte dieses auch in Jaws 8/9 fokusiert werden.

Cobra 8.1 laß anchor-Elemente ohne href-Attribut manchmal als Links vor, was irritierend sein dürfte. Befand sich direkt über dem fokusierten Element eine Überschrift und das fokusierte Element war selbst keine Überschrift, wurde diese zusätzlich in Jaws als auch in Cobra vorgelesen.

Regel: Fokusiere nur Elemente, die Interaktionselemente sind oder Überschriften mit tabindex bzw. Elemente mit tabindex, die in Überschriften plaziert wurden. Mit Rücksicht auf Cobra sollten keine anchor-Elemente ohne href-Attribut fokusiert werden.

Nachfolgend der Testcase für das einfache Fokusieren.

Fokusieren von vor kurzem noch versteckten bzw. gerade erst erschaffenen Elementen

Um zwischen den Screenreadern Waffengleichheit herzustellen, wurde der Webformator so eingestellt, daß er nur wirklich sichtbare Elemente anzeigt.

Das Fokusieren von gerade noch versteckten Elementen bzw. Elementen, die erst aufgrund einer User-Aktion ins DOM eingefügt werden (beispielsweise Click -> Ajax -> innerHTML -> focus), gehört wohl zu den problematischsten aller Möglichkeiten. Im wesentlichen haben sich aber alle Screenreader, außer Jaws 8/9, recht gut geschlagen.

Damit Screenreader solche Elemente fokusieren können, müssen sie ihre Ausgabe aktualisieren, um das zu fokusierende Element zu finden. Grundsätzlich sollte die focus-Methode hierzu mit der setTimeout-Methode verknüpft werden. Dies ist weniger ein Workaround, bei dem abgewartet wird, bis die Ausgabe durch den Screenreader aktualisiert wird, sondern ein technisches Erfordernis, welches mit der Arbeitsweise von Events im Browser zu tun hat, welche u.a. Jaws 10 zum Updaten seines Buffers heranzieht. Der Testcase umfaßte hierbei mehrere Delays zum Testen (0ms, 180ms, 400ms). Letztendlich gab es hierbei jedoch in der Regel keine Unterschiede zwischen den delays. Wichtig war nur das die focus-Methode, welche im selben Thread arbeitet wie das Sichtbarmachen/Einfügen selbst, durch die setTimeout-Methode “gequeued” wird.

Jaws 10 und Cobra haben hierbei sämtliche Tests ohne Probleme absolviert.

NVDA sowie Jaws 8/9 hatten dagegen gewisse Probleme. Befand sich das zu fokusierende Elemente über dem aktuell fokusierten Kontrollelement, wurde durch Jaws 8/9 sowie NVDA 0.6p3.2 der Fokus zwar verschoben, jedoch der fokusierte Text nie gesprochen.

Die aktuelle Beta-Version von NVDA sprach zwar alle fokusierten Texte, also auch die, welche sich vor dem Kontrollelement befanden, hatte jedoch bei allen Testfällen das Problem, daß sowohl Tab-Taste als auch Pfeiltasten, nach der Fokusierung immer noch vom Kontrolelement aus starteten. Ein also Weiterarbeiten vom fokusierten Bereich aus nicht möglich war. Hierbei handelt es sich um einen Regressionsbug, der nicht in der 0.6p3.2-Version auftritt. Ich hoffe, daß dieser Bug bis zur Final behoben sein wird.

Jaws 8/9 hatte noch das zusätzliche Problem dahingehend, daß der fokusierte Text auch wenn er nach dem Kontrollelement kam, manchmal nicht gesprochen und/oder der Cursor manchmal nicht richtig verschoben wurde. Dies hatte letztendlich mit zwei Dingen zu tun:

Zum einen ist es von der Bedienung des Screenreaders abhängig, ob er seine Ausgabe rechtzeitig updatet. Wurde der virtuelle Cursor, beispielsweise mit den Pfeiltasten, über den Link gebracht und dann mit der Enter-Taste der Link ausgelöst, klappte das Fokusieren in der Regel. Wurde dagegen der “richtige” Fokus mit der Tab-Taste auf das Kontrollelement versetzt und dann mit der Enter-Taste ausgelöst, wurde die Ausgabe in der Regel nicht richtig upgedatet und es kam mit allen Delays zu Problemen.

Zum anderen Griff im Widerspruch zu dem oben gesagten ein Delay von 0 bzw. 50 nicht immer (aber in etwa 90% aller Fälle). Sobald das Delay jedoch über 100ms betrug und das Kontrollelement mit dem virtuellen Cursor ausgewählt wurde, wurden auf allen bisher getesteten Rechnern mit unterschiedlicher CPU (1 Single Core sowie 2 Core DUO), unterschiedlicher CPU Auslastung (normal bis 100%) sowie unterschiedlicher Sprachgeschwindigkeit die fokusierten Texte immer gesprochen und richtig versetzt.

Regel: Will man ältere Jaws-Versionen unterstützen (und das sollte man), sollte man das Fokusieren von gerade noch versteckten bzw. gerade erst erstellten Elementen bleiben lassen und andere Techniken nutzen.

Nachfolgend der Testcase für das Fokusieren von gerade sichtbar gemachten Elementen.

Inhalt Umschreiben und dann Fokusieren

Als eine wirkungsvolle Alternative für das Fokusieren von gerade sichtbar gemachten Elementen, bietet sich das Umtexten von bereits vorhandenen sichtbaren Elementen und das nachfolgende fokusieren dieser an. Dirk Ginader hatte diese Technik in seinem barrierefreien Tab-Beispiel gezeigt. Diese Technik bietet sich aber nicht nur für Tabs an, sondern auch für viele andere Dinge an.

Die Testfälle absolvierten letztendlich (fast) alle Screenreader. Lediglich wieder Jaws 8 und Jaws 9 hatten mit einem unwahrscheinlichen Testfall Schwierigkeiten. Befand sich innerhalb des umgeschriebenen Elements kein initialer Text verhielt sich Jaws 8/9 ähnlich begriffsstutzig wie bei gerade erst sichtbar gemachten Elementen. Da Jaws 8 und 9 allerdings gleichzeitig nur Interaktionselemente sowie Überschrfiten fokusieren können, dürfte man sowieso selten mit einem solchen Element leeren Inhalts arbeiten wollen.

Regel: Elemente, die zum Umschreiben und Fokusieren gedacht sind, sollten nicht mit leerem Inhalt “starten”.

Nachfolgend der Testcase für das Fokusieren von gerade “umgeschribenen” Elementen.

Fokusieren von gerade sichtbar gemachten/eingefügten Elementen Teil 2

Um dennoch die Möglichkeit zu haben gerade erst erstellte/sichtbar gemachte Elemente zu fokusieren, sind Workarounds denkbar. Ich habe hierzu einen Testcase mit abgewandelter setFocus-Methode zur Verfügung gestellt, welcher nach ersten Tests recht zuverlässig in Jaws 8/9 funktioniert und in anderen Screenreadern keine Probleme auslöst.

Regel: Wenn man Inhalte nicht umschreiben kann, dann – und nur dann – das modifizierte setFocus-Script ausprobieren.

Written October 11, 2009 by
protofunc

Wai-Aria Widget-Entwicklung mit Accessibility Probe/Inspect am Beispiel einer custom Select-Drop-Down-Box

Tags: accessibility, javascript, jquery

Einleitung

Die Entwicklung von Aria-Widgets ist letztendlich keine triviale Sache. Zum einen müssen alle für das Widget wichtigen Aria-Attribute vorhanden sein, die Verschachtelung und letztendlich auch das – durch den Entwickler zu implementierende – Verhalten stimmen. Fehler in der HTML-Struktur oder dem Verhalten können häufig größeren Schaden anrichten als helfen. Bei der Entwicklung von zugänglichen Javascript-Komponenten erhält daher das Testen mit verschiedenen Screenreader-/Browser-Kombinationen eine hohe Bedeutung. Dieses Tutorial soll helfen zu zeigen, wie einfach man “bessere” Wai-Aria-Widgets schreibt bzw. wie man bereits vorhandene Widgets beurteilen kann, um den Entwickler auf Fehler aufmerksam zu machen.

Dies möchte ich am Beispiel einer normalen Ausklappliste demonstrieren, doch vorab hier eine Demo sowie ein Screencast, welches das fertige Aria-Widget mit verschiedenen Screenreader/Browser-Kombinationen zeigt.

Gute Resourcen

Die zentrale Anlaufstelle für gute Inhalte zum Thema WAI-Aria stellt Codetalks von Mozilla zur Verfügung. Dort lassen sich zahlreiche Aria-Beispiele/Test Cases, Artikel, FAQs, Tutorial, Blogs, Tools rund um das Thema Aria finden.

Einiges sei hier extra erwähnt.

  • Die entscheidenden Passagen des Aria Best Practices Dokuments sollten immer vor der Entwicklung eines Widgets gelesen werden. Das Dokument enthält sowohl allgemeine Informationen (z.B. Tastaturbenutzung/Fokus-Management) sowie konkrete Informationen zu entsprechenden Widgets (in unserem Fall der combobox).
  • Scripte sollten immer mit mehr als einem Screenreader und mehr als einem Browser getestet werden. Todd Kloots hat hierzu im YUI-Blog einige Informationen zum Installieren und Konfigurieren von Screenreadern für Entwickler zusammengestellt. Marco Zehe gibt weitere Informationen zum Testen mit NVDA.
  • Gute Tools machen uns das Entwicklen deutlich einfacher. Neben Tools wie Firebug/Dragonfly sind dies für die Aria-Entwicklung insbesondere die Firefox Accessibility Extension sowie eines der im Titel dieses Tutroials genannten Accessibility-Inspect Tools (Inspect bzw. Accessibility Probe).
  • Eine mögliche Anlaufstelle für Fragen sowie Diskussionen über eigene Lösungen/Ansätze stellt die Free ARIA Community dar.

Die Accessibility-Infomationen des select-Elements (im Firefox)

Die Aria-Rolle combobox beschreibt sowohl das Widget Ausklappliste (Dropdown-Liste), bei dem der Benutzer zwischen vorgegeben Werten wählen kann, als auch das Widget “kombiniertes Eingabefeld”, bei dem der Nutzer zusätzlich freie Texteingaben machen kann. Wir werden in diesem Tutorial das Inspect-Tool von Microsoft dafür nutzen, um die richtige HTML-Struktur inklusive der Aria-Attrbiute für eine einfache Ausklappliste zu ermitteln und zu testen.

Wenn wir ein Select-Element mit folgender HTML-Struktur schreiben,

<label for="select-element" id="label">Bezeichner</label>
<select id="select-element">
	<option>Option A</option>
	<option>Option B</option>
</select>

… erhalten wir folgende Accessibility-Informationen:

Das select-Element besitzt einen Namen (das label), einen aktuellen Wert, eine Rolle (Kombinationsfeld), verschiedene default-Zustände und eine unsichtbare Liste, welche ebenfalls mit dem label-Element verbunden ist.

Sofern wir die unsichtbare Liste weglassen, erreichen wir mit folgender HTML-Struktur die Weitergabe ähnlicher Accessibility-Informationen im Firefox:

<span id="label">Bezeichner</span>
<div role="combobox" aria-valuetext="Option A" aria-labelledby="label" tabindex="0">
	Option A
</div>

Die obige HTML-Struktur berücksichtigt jedoch nicht die fatale Kleinigkeit, daß der Internet Explorer 8 die valuetext-Eigenschaft nicht unterstützt. Letztendlich kann man Microsoft zumindest bei der combobox keinen Fehler vorwerfen, denn die Aria-Spezifikation für comboboxen sieht dies selbst nicht vor. Bei der Spezifikation hatte man anscheinend vor allem das Widget kombiniertes Eingabefeld im Kopf und die Realisierung der einfachen Drop-Down-Liste vergessen (reine Behauptung/Vermutung). Das Aria Best Practices Dokument geht dann zwar auf Ausklapplisten ein (verkürzte HTML-Struktur: [role=combobox][tabindex=-1] > [role=textbox][aria-readonly=true][tabindex=0]), aber dies würde zu einer HTML-Struktur führen, die in Screenreadern nicht funktioniert.

Mit Bugs gegen Bugs

Ein Problem bei der Entwicklung von Aria-Widgets ist die Tatsache, daß semantische Überreste von Elementen übrig bleiben, wenn man die Rolle mit Aria ändert. Ein typisches Beispiel hierfür ist ein Anchor-Element mit einem href-Attribut.

<a href="#bla" role="button">Button-Text</a>

Bei dieser Grundstruktur wird zwar aus einem Link ein Schalter, aber dieser Schalter besitzt neben dem Namen “Button-Text” noch zusätzlich den Wert der href-DOM-Eigenschaft als “Zugänglichkeitswert” (nicht zu Verwechseln mit dem Wert des href-Attributs). Aus diesem Grund sollte man, wenn man einen Link mit einer anderen Rolle belegt, das href-Attribut entfernen. Dies führt gleichzeitig dazu, daß

  1. der Link ohne tabindex-Attribut nicht mehr fokusierbar ist
  2. die Pseudoklasse :focus nicht in allen Browsern funktioniert (im IE6 funktioniert auch :hover nicht mehr)
  3. ein click-Event auf dem Link kein geräteunabhängiges Event mehr darstellt

Nun ist für uns die href-DOM-Eigenschaft nicht wirklich brauchbar, da wir diesen Wert nicht in der Hand haben. Das selbe Problem besteht jedoch ebenfalls bei Texteingabefeldern und läßt sich zur Lösung unseres Problems mißbrauchen.

Folgende HTML-Struktur ergibt sowohl im Firefox als auch im Internet Explorer die gewünschten Zugänglichkeitsinformationen:

<span id="label">Bezeichner</span>
<input role="combobox" value="Option A" aria-labelledby="label" tabindex="0" />

Die Ausklappliste selbst

Zusammen mit der Ausklappliste würde die HTML-Struktur wie folgt aussehen:

<span id="label">Bezeichner</span>
<input role="combobox" value="Option A" aria-labelledby="label" tabindex="0" />
<!-- weiteres HTML dazwischen | listbox wird ans Ende des Dokuments hinzugefügt -->
<ul role="listbox" aria-labelledby="label">
	<li role="option" tabindex="-1">Option A</li>
	<li role="option" tabindex="-1">Option B</li>
</ul>

Ändert der User mit der Maus oder mit den hoch/runter-Pfeiltasten die Optionen, wird dies über das zusätzliche Wai-Aria-Attribut activedescendant deutlich gemacht:

<span id="label">Bezeichner</span>
<input role="combobox" value="Option B" aria-labelledby="label" aria-activedescendant="option-2" tabindex="0" />
<ul role="listbox" aria-labelledby="label">
	<li role="option" id="option-1" tabindex="-1">Option A</li>
	<li role="option" id="option-2" tabindex="-1">Option B</li>
</ul>

Focus-Management: activedescendant vs. tabindex + focus

Grundsätzlich sollte man, statt der activedesendant-Methode, den Fokus mit tabindex und der focus-Methode ändern. Die focus-Methode funktioniert in recht vielen Browser-/Screenreader-Kombinationen, so daß Widgets, welche den focus auf diese Weise managen, selbst in Screenreader/Browser-Kombinationen funktionieren können, die kein Wai-Aria unterstüzten.

In unserem Fall, ist dieser Weg allerdings wenig sinnvoll, da wir die Liste ans Ende unseres Dokuments hinzufügen und dadurch aus der logischen Tabreihenfolge nehmen. Dem aufmerksamen Beobachter wird außerdem auffallen, daß wir den einzelnen Optionen dennoch ein tabindex-Attribut gegeben haben, obwohl wir die activedescendant-Methode nutzen und das Aria Best Practices Dokument in diesem Fall ein tabindex-Attribut für unnötig hält. Dies hat den Hintergrund, daß das tabindex-Attribut jedes Element nicht nur fokusierbar macht, sondern auch die Eigenschaft der Fokusierbarkeit (Markierbarkeit) an die jeweilige Zugänglichkeitsschnittstelle weiterleitet (Die Rolle option tut dies, wie fast alle Rollen, nicht implizit!). Fehlt dieses Attribut wird diese nicht weitergeleitet, was in einigen Screenreadern negative Folgen haben kann.

Wird die Liste reduziert, ist dieses Attribut wieder restlos zu entfernen.

Obgleich die Aria-Spezifikation noch weitere notwenige Aria-Attribute vorsieht (aria-expanded an der listbox) und weitere sinnvoll erscheinen (aria-checked an der jeweiligen Option, aria-selected=true|false an den Optionen), ist die oben gezeigte Aria-Struktur alles was wir für eine gut funktionierende Ausklappliste brauchen.

Endgültige HTML-Struktur

Mit dem Texteingabefeld als Workaround haben wir uns ein zusätzliches Problem geschaffen. Wird dieses fokusiert erscheint in der Regel ein Curosr und der Nutzer kann Text frei eingeben. Um dieses Verhalten rückgängig zu machen, müßen wir sowohl durch HTML-Struktur als auch unser Script einige Dinge berücksichtigen. Nachfolgend eine HTML-Struktur, welches einen Teil des ungewünschten Verhaltens rückgängig macht. Den Rest erledigen wir in unserem Script.

<span id="label">Bezeichner</span>
<div class="select" tabindex="-1">
	<input role="combobox" value="Option A" aria-valuetext="Option A" readonly="readonly" aria-readonly="false" aria-labelledby="label" tabindex="0" />
</div>
<ul role="listbox" aria-labelledby="label" id="datalist">
	<li role="option" id="option-1" tabindex="-1">Option A</li>
	<li role="option" id="option-2" tabindex="-1">Option B</li>
</ul>

Außerdem wollen wir die native Selectbox

  1. gegen eine schön stylbare/animierbare Selectbox tauschen
  2. in ihren Zugänglichkeitsinformationen nicht nur funktional, sondern möglichst originalgetreu nachbauen

und da brauchen wir bestimmt noch ein bißchen mehr HTML, so daß wir bei folgender Struktur landen:

<span id="label">Bezeichner</span>
<div class="select" tabindex="-1">
	<input role="combobox" value="Option A" aria-valuetext="Option A" readonly="readonly" aria-readonly="false" aria-expanded="false" aria-labelledby="label" tabindex="0" />
</div>
<div role="listbox" aria-labelledby="label" id="datalist">
	<div role="presentation">
		<ul role="presentation">
			<li role="option" id="option-1" tabindex="-1"><span role="presentation">Option A</span></li>
			<li role="option" id="option-2" tabindex="-1"><span role="presentation">Option B</span></li>
		</ul>
	</div>
</div>

Wann ist die Liste ausgeklappt/sichtbar?

Grundsätzlich klappt eine Auswahllliste für den User sichtbar erst bei gedrückter alt-Taste auf. Bei normaler Tastaturnutzung sieht es dagegen so aus, als würde sich lediglich der Wert des Select-Elements ändern. Wie ein Test mit dem Inspect-Tool zeigt, täuscht dieser Eindruck. Sobald der Wert mit den Pfeiltasten geändert wird, befindet sich der User automatisch auf der jeweiligen Option. Damit dies klappt, muß die Liste sichtbat gemacht (display: block; visibility: visible;) und das activedescendant-Attribut gesetzt werden. Es bleibt dem Autoren überlassen, ob er die Liste zu diesem Zeitpunkt außerhalb des Viewports plaziert oder nicht (In meiner Demo habe ich hierauf verzichtet).

Das Setzen des activedescendant-Attributs

Das activedescendant-Attribut sollte, ebenso wie das Setzen des Fokus mit der Fokus-Methode, immer mit einem timeout geschehen (Ein delay von 0 ms reicht). Wird das activedescendant-Attribut ohne delay auf ein Element gesetzt, welches zuvor noch versteckt war, kann der Screenreader dieses Element nicht finden und liest nichts vor. (Dies gilt nur für (neue) DOM-Elemente, nicht für die Änderung von Attributen oder Textknoten.)

Fazit

Es gibt viele Dinge, welche die Aria-Entwicklung erschweren: Browser-Bugs, Screenreader-Bugs, Merkwürdigkeiten in den Spezifikationen und letztendlich mangelndes Know-How. Geeignete Tools können uns helfen, diese Probleme schneller in den Griff zu kriegen. Ein Testen mit Screenreadern muß die Entwicklung begleiten.

Eine abschließende Frage an alle Entwickler, die mich immer wieder beschäftigt: Mit welchen Screenreader-/Browser-Kombinationen (inkl. Version) testet ihr bzw. bei welchen sollte – Eurer Meinung nach – die Funktionsfähigkeit sichergestellt werden (und sagt jetzt bitte nicht bei allen)?

Written September 23, 2009 by
protofunc

Wie man Tabs wirklich wirklich zugänglich macht

Tags: accessibility, deutsch, javascript

Vor etwa einem halben Jahr veröffentlichte Dirk Ginader eine zugängliche Tab-Lösung. Dieser Artikel versucht auf einem ähnlichem Grundscript durch Hinzufügen von WAI-Aria- Rollen und Zuständen die Zugänglichkeit zu erhöhen. Daneben deckt der Artikel einen Firefox/Jaws-Bug inklusive eines brauchbaren Workarounds auf.

Warum ich nicht die Aria Tab-Semantik nutze

Einigen wird nachfolgend (negativ) auffallen, daß ich eben nicht die Tabsematik anwende, welche durch das Aria Best Practices Dokument vorgeschlagen wird, sondern eine neue zusammenstelle. Dies begründet sich aus folgenden Punkten:

1. Abwärtskompatibilität: Tabben vs. erweiterte Tastaturnutzung

Aria im allgemeinen und insbesondere das Tab-Widget folgen der One-Tab-Stop-Technik”. Tatsächlich ist diese Technik sehr cool, um die Usability in aria-fähigen Screenreader-/Browserkombinationen drastisch zu erhöhen. Sie kann jedoch in manchen Situationen die Zugänglichkeit für nicht aria-fähige Systeme reduzieren.

Dies könnte beim Tab-Widget der Fall sein. Nur der aktive Tab soll nämlich in der Tabreihenfolge sein; alle inaktiven Tabs dagegen ausserhalb der, durch den Nutzer, erreichbaren Tabreihenfolge. Das Umschalten zwischen den Tabs erfolgt durch besondere Tastaturkürzel, die gescriptet werden müssen. Während das Herausnehmen von inaktiven Tabs aus der Tabreihenfolge in aria-unfähigen Systemen funktionieren kann, funktioniert das Scripten von Tastatureingaben im Normalmodus der meisten Screenreader ohne Aria in der Regel eben nicht.

2. Abwärtskompatibilität: Fehlende Rückmeldung

Im normalen Aria-Tabwidget findet die Rückmeldung über das Switchen von Tabs ausschließlich über Aria statt. Eine Fokusverlagerung, die in aria-unfähigen Screenreader/Browserkombinationen für eine solche Rückmeldung sorgen könnte, ist dagegen nicht vorgehsehen.

3. Konflikt mit Tastaturnutzung zwischen Webseite und Browser

Das Tabwidget sollte desweiteren eine Tastaturkombination verwenden (Ctrl+PageUp/Ctrl+PageDown), welche mit der des Browsers in Konflikt geraten kann.

4. Wir machen doch nicht nur Tabwidgets

Eine extrem häufige Aufgabe ist es einen Schalter zu platzieren, welcher die Sichtbarkeit eines anderen Elements steuert. Hierbei handelt es sich dann aber noch nicht um ein Tabwidget. Aber eben auch für diese Schalter wollen wir eine zugängliche Technik entwickeln. Ein Tabwidget ist aus dieser Sicht dann nichts anderes als die Zusammenfassung solcher Buttons mit ihren Containern zu einer Gruppe.

Auch wenn mittelfristig – durch steigende Aria-Unterstützung – das hier gezeigte Aria-Markup für Tabs weniger sinnvoll erscheinen wird, so bleibt es dennoch für Accordions oder Schalter, welche irgendwelche anderen Elemente in ihrer Sichtbarkeit toggeln, sinnvoll.

Das ariafizierte Markup

<div>
	<ul>
		<li><a role="button" aria-expanded="true" tabindex="-1" id="tab-1" aria-controls="panel-1" href="#">Tab 1</a></li>
		<li><a role="button" aria-expanded="false" tabindex="0" id="tab-2" aria-controls="panel-2" href="#">Tab 2</a></li>
	</ul>
	<div role="group" aria-expanded="true" aria-labelledby="tab-1" "id="panel-1">
		<p tabindex="-1">Text für Panel 1. Startet manchmal halt ohne Überschrift (schade ist aber so)</p>
	</div>
	<div role="group" aria-expanded="false" aria-labelledby="tab-2" "id="panel-2">
		<h3 tabindex="-1">Überschrift für Panel 2</h3>
		<p>Weiterer Text für Panel 2</p>
	</div>
</div>

Mit dem tabindex-Attribut kontrollieren wir die Tabreihenfolge. Wie sich zeigt drehen wir die Logik des W3C zum tabwidget um. Alle Tabs sind in der Tabreihenfolge nur eben das aktive Tab-Element nicht. Wir geben dem Tab die Rolle Button und die Eigenschaft expanded. Die expanded-Eigenschaft an Button-Elementen wird derzeit nur von Firefox 3.5 an Screenreader weitergeleitet. Die Eigenschaft controls baut eine logische Beziehung zwischen Button und Panel auf, wird jedoch noch von keinem Screenreader richtig unterstüzt (schadet wahrscheinlich nicht). Das Fokusieren des 2. Tab würde sich durch obige Auszeichnung wie folgt anhören: Tab 2 [Schalter reduziert].

Das Tabpanel bekommt die Rolle ‘group’ sowie die Eigenschaften expanded und labelledby. Die labelledby-Eigenschaft setzt eine logische Beziehung von dem group-Element zum Button. Anders als Controls wird sie gut unterstüzt. Auch die Eigenschaft expanded ist am group-Element besser unterstützt als am button-Element. Sobald der User auf Tab 2 klickt, fokusieren wir mit einem kurzen Delay das 1. Kindelement des Tabpanels, was sich ungefähr wie folgt anhören würde: [Tab 2 offen] Überschrift für Panel 2 [Überschrift Ebene 3].

Da wir die Ariafizierung mit Javascript vornehmen, hier 2 Scripte die uns dabei helfen:

ID-Attribute erstellen/ermitteln und labelledby-Eigenschaft setzen

(function($){

	var uId = new Date().getTime();
	$.fn.getID = function(){
		var id = '';
		if(this[0]){
			var elem = $(this[0]);
			id = elem.attr('id');
			if(!id){
				id = 'ID-' + (uId++);
				elem.attr({'id': id});
			}
		}
		return id;
	};

	$.each({
		labelWith: 'aria-labelledby',
		describeWith: 'aria-describedby',
		ownsThis: 'aria-owns',
		controlsThis: 'aria-controls'
	}, function(name, prop){
		$.fn[name] = function(elem){
			return this.attr(prop, $(elem).getID());
		};
	});
})(jQuery);

Das jQuery-Plugin getID gibt den Wert des id-Attributs eines Elements zurück. Ist keine id vorhanden wird eine eindeutige id erstellt. Daneben werden hierauf aufbauend die Methoden labelWith, describeWith, ownsThis und controlsThis erstellt.

Benutzung:

// Gibt den ID-Wert von panel zurück
$(panel).getID();

// gib dem Panel die Eigenschaft labelledby mit dem id-Wert von button
$(panel).labelWith(button);

Element fokusieren

Ein Script, welches ein beliebiges Element nach einem kurzen Delay fokusiert, könnte dagegen wie folgt aussehen:

(function($){

	function addTabindex(jElm){
		if(!jElm.is('a[href], area, input, button, select, textarea')){
			jElm.css({outline: 'none'}).attr({tabindex: '-1'});
		}
		return jElm;
	}
	var focusTimer;
    $.fn.setFocus = function(time){
		if(!this[0]){return this;}
		var jElm = $(this[0]);
		clearTimeout(focusTimer);
		focusTimer = setTimeout(function(){
			try{
				addTabindex(jElm)[0].focus();
			} catch(e){}
		}, time || 0);
		return this;
	};

})(jQuery);

Benutzung des Focus-Scripts:

// 1. sichtbares Kindelement von panel nach 0 ms fokusieren
$('> :visible:first', panel).setFocus();

Sollte man das Panel oder ein anderes wrapper-Element des zu fokusierenden Elements animieren, empfiehlt es sich mit der Fokusierung bis zum Ende der animation zu warten, da beim Internet Explorer der Fokus bei manchen Animation ab und zu “verspringt”. Eine Verzögerung von weniger als 500ms (Die Standarddaauer einer jQuery-Animation beträgt 400ms) beim Fokusieren wird nicht als störend empfunden.

Wenn das ganze funktionieren würde…

… hätte ich diesen Blogeintrag nie geschrieben. Es ist etwa einen Monat her, da wies mich Timo Wirth daraufhin, daß das Script in einem seiner Projekte nicht funktioniert. Das Problem war, daß sobald das HTML eben mit Aria erweitert wurde, insbesondere die Aria-Rolle Button vergeben wurde, Jaws mit Firefox den Buffer nicht mehr beim Sichtbarkeitswechsel updaten wollte. Da aber die vorbereiteten Beispiele funktionierten, stellte sich die Frage, welcher andere Faktor an dem Scheitern verantworlich ist. Es begann die Suche nach der Nadel im Heuhaufen.

Das Problem ist kurz gesagt dermaßen banal und es ist eine Schande, wie lange die Ursachenforschung gedauert hat:

Das Element, dessen Sichtbarkeit beeinflußt wird, darf unter bestimmten Bedingungen (beispielsweise Kontrollelement hat die Rolle Button) nicht die CSS-Eigenschaft overflow: hidden haben. Um sicherzustellen, daß das Panel diese CSS-Eigenschaft weder durch CSS noch durch eine Javascript-Animation erhält, bekommt es daher von mir eine CSS-Klasse. Ein dynamisch erzeugtes Stylesheet sorgt dafür, dass diese Klasse ein overvlow: visible erhält:

$(function(){
	var style = document.createElement('style'),
		styleS
	;
	style.setAttribute('type', 'text/css');
	style = $(style).prependTo('head');

	styleS = document.styleSheets[0];

	function add(sel, prop){
		if (styleS.cssRules || styleS.rules) {
			if (styleS.insertRule) {
				styleS.insertRule(sel +' {'+ prop +';}', styleS.cssRules.length);
			} else if (styleS.addRule) {
				styleS.addRule(sel, prop);
			}
		}
	}

	add('.a11y-visibility-change', 'overflow:visible !important');

	$.cssRule = {
		add: add
	};
});
//über die Klasse reden wir noch :-)
$(panel).addClass('a11y-visibility-change');

Ein div sie zu knechten

Nun stellt die CSS-Eigenschaft overflow: hidden eine recht wichtige Eigenschaft dar. Einerseits wird sie gerne genommen, um floats einzuschließen andererseits ist sie essentieller Bestandteil einiger JavaScript-Animationen, beispielsweise (slideUp, slideDown etc.). Unser Stylesheet-Problem läßt sich recht einfach lösen, sowohl Vorfahren- als auch Nachfahrenelemente dürfen wieder die CSS-Eigenschaft hidden aufweisen. Bei Animationen wie slideDown/slideUp kann das Elternelement mit einer Höhen-Animation und entsprechendem overflow: hidden verwendet werden. Zu beachten ist allerdings, daß in unserem Fall das group-Element selbst die display: none/block Eigenschaft bekommen sollte und eben nicht das Elternelement. Eine Alternative zu slideUp/slideDown könnten daher folgende Plugins bieten:

$.fn.slideParentDown = function(opts){
	opts = $.extend({}, $.fn.slideParentDown.defaults, opts);
	var fn = opts.complete;

	return this.each(function(){
		var jElm 		= $(this),
			parent		= jElm.parent().css({overflow: 'hidden', height: '0px'}),
			outerHeight = jElm.css({display: 'block'}).outerHeight({margin: true})
		;
		parent.animate({
			height: outerHeight
		}, $.extend({}, opts, {complete: function(){
			parent.css({height: '', overflow: ''});
			fn.apply(this, arguments);
		}}));
	});
};
$.fn.slideParentDown.defaults = {
	duration: 400,
	complete: function(){}
};
$.fn.slideParentUp = function(opts){
	opts = $.extend({}, $.fn.slideParentDown.defaults, opts);
	var fn = opts.complete;
	return this.each(function(){
		var jElm 		= $(this),
			parent		= jElm.parent().css({overflow: 'hidden', height: jElm.outerHeight({margin: true})})
		;
		parent.animate({
			height: '0px'
		}, $.extend({}, opts, {complete: function(){
			jElm.css({display: 'none'});
			parent.css({height: '', overflow: '', display: ''});
			fn.apply(this, arguments);
		}}));
	});
};
$.fn.slideParentDown.defaults = {
	duration: 400,
	complete: function(){}
};

Kurz gesagt, wenn man die overflow: hidden Eigenschaft braucht, nimmt man ein zusätzliches div-Element und alles wird gut.

Fazit

Testen, testen, testen!!!

Das Ergebnis ist mit Sicherheit noch nicht des Weisheits letzter Schluß, aber hört sich schon ganz nett an. Für Leute die dies einmal testen möchten hier nun abschließend eine kleine Demonstration des zusammengebauten Tab-Scripts.

Die vorgenannte Demo wurde bisher mit folgenden Browser/Screenreader-Kombinationen erfolgreich getestet:

IE8: Jaws 10, Cobra (Webformator 2.4) | Firefox. 3.5: Jaws 10, NVDA 0.6p2 | IE 7: Jaws 9/Jaws 8

Update 06.10.2009: Das Script enthielt zuvor einen kleinen Fehler, so daß es nicht im IE7 mit Jaws 8/9 funktionierten konnte. Dieser Bug ist nun gefixt.

Written August 2, 2009 by
protofunc

leichtgewichtiges, barrierearmes, generisches Hover-Plugin für jQuery

Tags: accessibility, deutsch, javascript, jquery, tutorial

Die hover-Methode von jQuery gehört wohl zu einer der beliebtesten Methoden von jQuery. Leider besitzt sie das Problem nicht barrierefrei zu sein, da die jeweils zugrunde liegenden Events (mouseenter/mouseleave) (eingabe-)geräteabhängig sind.

Die mit dem mouseenter/mouseleave häufig (aber nicht immer) korrespondierenden Events focus/blur werden dagegen selten eingesetzt. Andererseits gibt es einige fertige Scripte, die sich des Problems für bestimmte Teilbereiche angenommen haben. Sie setzen allerdings mehr oder weniger feste HTML-Strukturen voraus (In der Regel Flyout Menue Scripte mit verschachtelten Listen und darin befindlichen Links).

Ein Script, welches dieses – eigentlich einfache Problem – etwas generischer versucht zu lösen, ist mir nicht bekannt. Ich habe versucht dies vor einiger Zeit zu lösen. Hier eine Demo dieses Lösungsansatzes. Bei der Implementierung stößt man in der Regel auf mehrere Probleme:

  1. Das “Mausinteraktionselement” kann muß aber nicht immer das “Tastaturinteraktionselement”
  2. In einem “Mausinteraktionselement können mehrere “Tastaturinteraktionselemente” vorkommen
  3. Die Art der “Tastaturinteraktionselemente” ist nicht bekannt (jedes Element kann tastaturbenutzbar gemacht werden
  4. Wurde die Interaktion bereits durch die Maus getriggert, darf sie nicht nochmal durch ein focus getriggert werden und umgekehrt.

Kern der Lösung stellen hierbei die focusin/focusout Events dar, welche bubbelnde focus/blur Events darstellen und von Microsoft erfunden wurden. In standardkonformen Browsern läßt sich dieses Event dadurch nachstellen, daß man gefeuerte Events bereits in der Capturing-Phase abfängt.

Jörn Zaefferer hat für sein simples delegate-Plugin eine entsprechende focusin/focusout-Implementierung geschrieben, welche er auch in seinem bekannten Validation-Plugin nutzt. Jörn´s focusin/focusout Code sieht wie folgt aus:

/*
*	focusin/focusout von  jörn zaefferer´s delegate plugin
*	 Copyright (c) 2007 Jörn Zaefferer
*/
$.each({
	focus: 'focusin',
	blur: 'focusout'
}, function( original, fix ){
	$.event.special[fix] = {
		setup:function() {
			if ( $.browser.msie ) return false;
			this.addEventListener( original, $.event.special[fix].handler, true );
		},
		teardown:function() {
			if ( $.browser.msie ) return false;
			this.removeEventListener( original,
			$.event.special[fix].handler, true );
		},
		handler: function(e) {
			arguments[0] = $.event.fix(e);
			arguments[0].type = fix;
			return $.event.handle.apply(this, arguments);
		}
	};
});

Unser barrierearmes hover-Plugin soll identisch zum hover-Plugin aufgerufen werden können. Als kleinen Bonus kann man als dritten Parameter Optionen angeben:

$('#nav').find('li').inOut(enterHandler, leaveHandler);
//oder mit anderen Optionen
$('#nav').find('li').inOut(enterHandler, leaveHandler, {bothOut: true});

Hierauf aufbauend nun die Grundkonstruktion unseres jQuery-Plugins:

(function($){
	var handler = {},
		uID = new Date().getTime()
	;

	$.fn.inOut = function(enter, out, opts){

	};

	$.fn.inOut.defaults = {
		mouseDelay: 0,
		keyDelay: 1,
		bothOut: false,
		useEventTypes: 'both' // both || mouse || focus
	};
})(jQuery);

Wir definieren als erstes ein handler-Objekt hier werden wir unsere handler-Methoden einfügen, die dann entscheiden, ob und wann die übergebenen Callback-Funktionen aufgerufen werden. Danach erstellen wir eine ID. Die ID werden wir später benötigen, da wir stark mit der data-Methode von jQuery arbeiten, um die einzelnen Zustände an den Elementen zu speichern. Für den seltenen Fall, daß man das Plugin mehrfach auf das selbe DOM-Element anwenden will, werden wir diese ID mit jedem Durchlauf des Plugins verändern. Hiernach kommt das eigentliche Plugin-Grundgerüst sowie die Default-Einstellungen.

Mit mouseDelay kann entschieden werden nach welchem Delay die jeweilige mouseenter/mouseleave Funktion auferufen werden sollen. Wir können dies sehr gut für benutzerfreundliche Flyout-Menues verwenden. Mit keyDelay können wir einen entsprechenden Delay für Tastaturuser einbauen. Allerdings macht dies hier weniger Sinn. Mit dem booleschen Wert bothOut können wir bestimmen, ob die leave-Methode nur dann aufgerufen werden soll, wenn weder das jeweilige Interaktionselement fokusiert noch die Maus drüber liegt. Mit der Option ‘useEventTypes’ können wir entscheiden, ob hover tatsächlich mit focus/blur gleichgesetzt werden soll (both). Anderenfalls können wir das Handling nur auf focus/blur (focus) oder nur auf mouseenter/mouseleave (mouse) beschränken.

Nun zum Inhalt unseres jQuery Plugins:

//hochzählen unserer ID
uID++;
//Defaults mit Optionen auffüllen
opts = $.extend({}, $.fn.inOut.defaults, opts);
//Option 'useEventTypes' verarbeiten
var inEvents = 'mouseenter focusin',
	outEvents = 'mouseleave focusout';
if(opts.useEventTypes === 'mouse'){
	inEvents = 'mouseenter';
	outEvents = 'mouseleave';
} else if(opts.useEventTypes === 'focus'){
	inEvents = 'focusin';
	outEvents = 'focusout';
}
//Anzahl der enter Events auf 0 setzen
//Eventhandler mit Optionen, der ID und eigentlicher Callback-Funktion binden
return this
	.data('inEvents'+ uID, 0)
	.bind(inEvents, [enter, opts, uID], handler.enter)
	.bind(outEvents, [out, opts, uID], handler.out);

Nun müssen wir noch die enter-/out-Methoden definieren. Als 1. die enter-Methode (wenn die Maus drüberwandert oder ein Element fokusiert wird):

handler.enter = function(e){
// Extrahieren der callback-Funktion, der Einstellungen und der ID aus der data-Array
	var fn = e.data[0],
		o = e.data[1],
		ID = e.data[2],
//Zwischenspeichern des Kontextes (zeigt auf DOM-Element)
		elem = this,
//Hochzählen unseres 'inEvents'
		inEvents = $.data(elem, 'inEvents'+ ID) + 1
	;
//Möglichen timer clearen, um ausführen eines "delayten" out-Callbacks zu verhindern
	clearTimeout($.data(this, 'inOutTimer'+ ID));
//setzen unserer aktuellen inEvents
	$.data(elem, 'inEvents'+ ID, inEvents);
//starten unseres Timeouts und speichern unserer timeout-Referenz
	$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
//enterCallback nur ausführen, wenn sie noch nicht ausgeführt wurde
		if(!$.data(elem, 'inOutState'+ ID)){
//setzen unserers inOutStates
			$.data(elem, 'inOutState'+ ID, true);
//modifizieren unseres Events
			e.type = 'in';
//eigentliche Callback-Funktion ausführen:
			fn.call(elem, e);
		}
//länge des Delay ermitteln
	}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
};

Nun müssen wir noch die leave-Methode erstellen, welche wie folgt aussieht:

handler.out = function (e){
	var fn = e.data[0],
		o = e.data[1],
		ID = e.data[2],
		elem = this,
		//sicherstellen das inEvents-Wert mind. 0 beträgt
		inEvents = Math.max($.data(elem, 'inEvents'+ ID) - 1, 0)
	;

	clearTimeout($.data(this, 'inOutTimer'+ ID));
	$.data(elem, 'inEvents'+ ID, inEvents);
	$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
		if($.data(elem, 'inOutState'+ ID) &&
			//Verarbeiten der bothOut-Option
				(!o.bothOut || !inEvents)){
			$.data(elem, 'inOutState'+ ID, false);
			e.type = 'out';
			fn.call(elem, e);
		}
	}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
};

Wie sich zeigt sind die enter-/leave-Methoden praktischen identisch. Die Unterschiede im Überblick: Dort wo die enter-Methode einen Wert hochzählt (inEvents), zählt die leave-Methode eins runter und dort wo die enter-Methode den Wert auf true setzt, setzt die leave-Methode den Wert false (inOutState). Daneben wird ausschließlich in der leave-Methode die Option bothOut verarbeitet.

Aus diesen geringen Unterschieden läßt sich eine kleine Funktion bauen, welche uns beide Handler-Methoden dynamisch erstellt.

$.each({enter: [1, 'in', true], out: [-1, 'out', false]}, function(handle, params){
	handler[handle] = function(e){
		var fn = e.data[0],
			o = e.data[1],
			ID = e.data[2],
			elem = this,
			inEvents = Math.max($.data(elem, 'inEvents'+ ID) + params[0], 0)
		;

		clearTimeout($.data(this, 'inOutTimer'+ ID));
		$.data(elem, 'inEvents'+ ID, inEvents);
		$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
			if((params[2] != $.data(elem, 'inOutState'+ ID)) &&
					(params[2] || !o.bothOut || !inEvents)){
				$.data(elem, 'inOutState'+ ID, params[2]);
				e.type = params[1];
				fn.call(elem, e);
			}
		}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
	};
});

Abschließend nochmals das gesamte Script sowie die Demo:

(function($){
	var handler = {},
		uID = new Date().getTime()
	;
	$.each({enter: [1, 'in', true], out: [-1, 'out', false]}, function(handle, params){
		handler[handle] = function(e){
			var fn = e.data[0],
				o = e.data[1],
				ID = e.data[2],
				elem = this,
				inEvents = Math.max($.data(elem, 'inEvents'+ ID) + params[0], 0)
			;

			clearTimeout($.data(this, 'inOutTimer'+ ID));
			$.data(elem, 'inEvents'+ ID, inEvents);
			$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
				if((params[2] != $.data(elem, 'inOutState'+ ID)) &&
						(params[2] || !o.bothOut || !inEvents)){
					$.data(elem, 'inOutState'+ ID, params[2]);
					e.type = params[1];
					fn.call(elem, e);
				}
			}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
		};
	});

	$.fn.inOut = function(enter, out, opts){
		uID++;
		opts = $.extend({}, $.fn.inOut.defaults, opts);

		var inEvents = 'mouseenter focusin',
			outEvents = 'mouseleave focusout'
		;
		if(opts.useEventTypes === 'mouse'){
			inEvents = 'mouseenter';
			outEvents = 'mouseleave';
		} else if(opts.useEventTypes === 'focus'){
			inEvents = 'focusin';
			outEvents = 'focusout';
		}

		return this
				.data('inEvents'+ uID, 0)
				.bind(inEvents, [enter, opts, uID], handler.enter)
				.bind(outEvents, [out, opts, uID], handler.out);

	};

	$.fn.inOut.defaults = {
		mouseDelay: 0,
		keyDelay: 1,
		bothOut: false,
		useEventTypes: 'both' // both || mouse || focus
	};

})(jQuery);

Angesichts der Tatsache, daß dieses Script nicht nur die meisten 08/15 Menue-Scripte überflüßig macht, sondern eine wirklich universell einsetzbare Lösung für unser hover-Problem darstellt, ist es wirklich klein und leichtgewichtig geraten :-) .

Update: Hier findet ihr eine aktuelle Version des inOut/enterLeave-Scripts

Written March 29, 2009 by
protofunc

Tutorial: Barrierearme Checkbox und Radio Button Ersetzung mit jQuery UI und der UI-Reflecting- Technik

Tags: accessibility, deutsch, javascript, jquery, tutorial

Demo und Script-Download

Problem und Lösungsansatz oder was ist UI-State-Reflecting

Eine gerne durchgeführte Aufgabe ist es, die in manchen Browsern recht hässlichen Checkboxen und Radiobuttons durch schönere, dem Design angepasste Eingabeelemente zu ersetzen. (Auf mögliche Verschlimmbesserungen durch schlechtes UI-Design möchte ich hier nicht eingehen. Dies gut zu machen, ist eine Herausforderung für den Designer!)
Möchte man hierbei möglichst barrierearm arbeiten, müssen die Zustände und Rollen der Eingabeelemente nicht nur optisch, sondern auch semantisch deutlich gemacht werden (hierzu eignet sich beispielsweise WAI-Aria).
Zusätzlich muss die Tastaturnutzung der Rolle und seines Zustandes entsprechend implementiert werden (für Radio Buttons für Checkboxen).

Insbesondere für die Implementierung der Tastaturnutzung für Radiobuttons ist ein etwas höherer Scripting-Aufwand nötig und leider sind die meisten JavaScript Entwickler in diesem Punkt entweder faul oder ignorant.

Reflektieren statt Simulieren

Dabei ist dies mit dem UI-Reflecting Ansatz mehr als einfach zu implementieren. Ausgangspunkt von UI-Reflecting ist die Erkenntnis, dass bereits der „Fallback“ entweder die Rollen und Zusände vollständig/teilweise abdeckt oder zumindest einen Teil der Funktionalität. In unserem Fall wird sowohl Funktionalität als auch Rolle und Zustand vollständig durch das Fallback abgedeckt und es findet lediglich eine optische Ersetzung statt. Beim UI-Reflecting lässt man also das Fallback die ganze Arbeit machen und der Entwickler bemüht sich lediglich darum dies auf dem neuen User Interface abzubilden.

UI-Widget-Factory

Die UI-Widget-Factory ist Teil von jQuery UI (in der ui.core.js). Um die Funktionsweise der Factory zu verstehen, empfehle ich folgendes UI Widget Tutorial.
Der Grundaufbau unseres unseres Scripts, wird wie folgt aussehen:

$.widget('ui.checkBox', {

});
$.ui.checkBox.defaults = {
    focusClass: 'focus',
    checkedClass: 'checked',
    disabledClass: 'disabled',
    hideInput: true,
    radioElements: false
};

In das noch leere Object-Literal werden wir die im folgenden beschriebenen Methoden integrieren:

Die Init-Methode:

Als 1. sammeln wir alle label-Elemente, welche mit dem jeweiligen input-Element durch das for-Attribut in Beziehung stehen. Diese(s) label-Element(e) werden durch entsprechende Hintergundbilder später die Rolle und den Zustand des input-Elements deutlich machen. Solltet ihr hierfür aufgrund von Designgründen nicht, die bereits vorhanden label-Elemente nutzen, sondern ein eigenes Element für die Optik des input-Elements nutzen wollen, könnt ihr vor dem instanziieren des Checkbox-Objects einfach ein weiteres label-Element einfügen. Beispiel:

$('input:radio').each(function(){
	var jElm = $(this);
		jElm
			.after('<label class="visualRadio" for="'+jElm.attr(id)+'">')
			.checkBox();
});

In der Regel wird dies jedoch nicht nötig sein.

Sofern es sich um ein Radiobutton handelt, holen wir uns alle Radiobuttons der Gruppe über das name-Attribut. Hintergrund ist, dass Radiobuttons sofern sie ungecheckt werden kein Cross-Browser-taugliches Event werfen und wir dies manuell nachholen müssen.

Im Anschluss verschieben wir das Original-input-Element aus dem Viewport. Dies machen wir allerdings davon abhängig, ob der User sich im Usermode (auch Kontrastmodus genannt) befindet, wofür wir ein weiteres kleines Script einsetzen. Sollte das Script nicht verwendet werden, gehen wird davon aus, dass sich der User nicht im Usermodus befindet.

Hiernach rufen wir unsere – noch zu definierende – reflectUI-Methode auf welche die Zusätnde des input-Elements durch CSS-Klassen auf unser(e) label-Element(e) überträgt.

Im Anschluss fügen wir noch die entsprechenden Event-Handler hinzu, um die Zustandsänderungen dynamisch ändern zu können. Damit das this weiterhin auf unser instanziiertes Object zeigt, setzen wir die bind-Function-Methode ein.

Hiermit ist unsere init-Methode fertig:

init: function(){
	var that = this,
		opts = this.options;
	this.labels = $('label[for='+this.element.attr('id')+']');
	this.checkedStatus = false;

        this.radio = opts.radioElements ||
		(this.element.is(':radio')) ?
		$(document.getElementsByName(this.element.attr('name'))) :
		false;

	var usermode = ($.userMode && $.userMode.get) ?
		$.userMode.get() :
		false;

	if(!usermode && opts.hideInput){
		var css = ($('html').attr('dir') == 'rtl') ?
			{position: 'absolute', right: '-999em', width: '1px'} :
			{position: 'absolute', left: '-999em', width: '1px'};
		this.element.css(css)
			.bind('usermode', function(e, o){
				if(o.usermode){
					that.destroy.call(that, true);
				}
			});
	}
	this.reflectUI({type: 'initialReflect'});
	this.element
		.bind('click.checkBox', $.bind(this, this.reflectUI))
		.bind('focus.checkBox blur.checkBox', function(){
			that.labels.toggleClass(opts.focusClass);
		});
}

Die reflectUI-Methode

Diese Methode ist sozusagen der Kern unserers Scripts. Sie stellt lediglich sicher, dass die Zustände (disabled und vor allem checked) auf unser User-Interface übertragen werden.

Wird eine Veränderung zum vorherigen Zustand erkannt, ruft die Methode die propagate-Methode auf, welche in vielen jQuery-UI-Widgets vorkommt und sehr interessant ist:

reflectUI: function(elm, e){
	var checked = this.element.is(':checked'),
		oldChecked = this.checkedStatus,
		o = this.options;
	e = e ||
		elm;
	this.labels[(this.element.is(':disabled'))?
		'addClass' :
		'removeClass'](o.disabledClass);

        this.checkedStatus = checked;

        if (checked !== oldChecked) {
            this.labels.toggleClass(o.checkedClass);
            this.propagate('change', e);
        }
}

Die propagate-Methode

Die propagate-Methode triggert an dem jeweiligen input-Element ein custom-Event, welche durch entsprechende Event-Objekte sowie Übergabe der eigenen Objektreferenz seine Zustände nach aussen trägt. Dies ist eine sehr gute Möglichkeit, um andere Widgets mit dem checkBox-Widget zu verbinden, ohne lästige starke Abhängigkeiten zu schaffen oder in den Original-Code einzugreifen. Es lässt sich aber auch für profaneres Gebrauchen. So kann man beispielsweise die Veränderung des checked-Status dynamisch durch eine Animation kenntlich machen.
Sofern es sich bei dem input-Element, um einen Radiobutton handelt, wird für alle Radiobuttons zusätzlich die reflectUI-Methode aufgerufen.

propagate: function(n, e){
	if(this.radio){
		this.radio.checkBox('reflectUI', e);
	}
    this.element.triggerHandler("checkbox" + n, [e, {
        instance: this,
        options: this.options,
        checked: this.checkedStatus,
		labels: this.labels
    }]);
}

Die destroy-Methode

Viele jQuery-UI-Widgets enthalten eine eigene destroy-Methode, welche dazu dient, das Widget mehr oder weniger auszuschalten und den Original-Zustand herzustellen. Bei unserer destroy-Methode kann gewählt werden, ob nur das Original-input-Element wieder sichtbar sein soll. Die Events und Zustandsanzeigen am label-Element bleiben erhalten oder ob zusätzlich alle Event-Handler entfernt werden sollen.

destroy: function(onlyCss){
	this.element.css({position: '', left: '', right: '', width: ''});
		if(!onlyCss){
			var o = this.options;
			this.element
				.unbind('.checkBox');
			this.labels
				.removeClass(o.disabledClass+' '+o.checkedClass+' '+o.focusClass);
		}
}

Kleine Verfeinerungen

Hiermit ist das Script bereits fertig und einsatzbereit. Als kleine Verfeinerung unseres Scripts fügen wir noch ein paar weitere öffentliche Methoden hinzu, die uns in anderen Situation noch hilfreich sein könnten:

disable: function(){
	this.element[0].disabled = true;
	this.reflectUI({type: 'manuallyDisabled'});
},
enable: function(){
	this.element[0].disabled = false;
	this.reflectUI({type: 'manuallyenabled'});
},
toggle: function(){
	this.changeCheckStatus((this.element.is(':checked'))?false:true);
},
changeCheckStatus: function(status){
	this.element.attr({'checked': status});
	this.reflectUI({type: 'changeCheckStatus'});
}

Fazit:

Wie sich gezeigt hat, ist der Scripting-Aufwand im Vergleich zum Ergebnis (komplette Tastaturnutzung wie Original-Elemente, richtige Rollen und Zustände bei Screenreadern etc.) relativ gering gewesen. Der Grund: Wir haben an keiner Stelle eine Tastaturnutzung oder ähnliches Scripten müssen. All das besorgt der Browser für uns.
Demo und Script-Download

Written September 6, 2008 by
protofunc

Wai-Aria Grundlagen

Tags: accessibility, deutsch, javascript

WAI Aria Rollen und Eigenschaften bieten zusätzliche Semantik in Attribut-Form. Ihr erklärtes Ziel ist die Verbesserung der Zugänglichkeit von Webseiten – insbesondere für Blinde, aber auch für Tastaturnutzer sowie andere Behinderungen. Daneben sind theoretisch auch die – für eine Verbesserung der Semantik – üblichen positiven Nebeneffekte für Suchmaschinen, Browser-Features und Gebrauchstauglichkeit denkbar.

ARIA bietet zusätzliche Semantik für

  • Rollen von typischen JS-Widgets (z.B. tree, menubar)
  • jeweilige Zustände/Eigenschaften (z.B.: aria-hidden bzw. aria-haspopup, aria-required)
  • Rollen für strukturelle Elemente und Beziehungen zwischen ihnen (z.B. seealso, navigation etc.)
  • Rollen für logische Bereiche (z.B. allgemein: section, region, bei Ajax: liveregion, konkrete: main)

Aufgrund ihrer bereits breiten Unterstützung, der Lösung der wohl drängendsten Zugänglichkeitsprobleme sowie der Tatsache, dass Aria noch nicht validieren, scheinen derzeit die Rollen und Eigenschaften zur Beschreibung von Widgets am interessantesten.

Das Rollen-Konzept von WAI Aria

Das Rollen Konzept ist für Web Entwickler grundsätzlich nichts neues. Über semantisches HTML wird über die Zugänglichkeitsschnittstelle letztendlich die Rolle des Elements dem Screenreader mitgeteilt, welcher sich dann der Rolle entsprechend verhalten kann (z.B.: in Form von Information sowie Interaktionsmöglichkeiten).

Die Aria-Widget-Rollen sind an die Rollen der jeweiligen Zugänglichkeitsschnittstellen angelehnt, welche diese bereits nutzen, um Rollen von Interaktionselementen innerhalb des Betriebssystems bzw. einzelner Applikationen zugänglich zu machen. Aufgabe der Browser ist es die Aria-Rollen auf die genaue Rolle der jeweiligen Zugänglichkeitsschnittstelle zu „mappen”. (Beispiele des Aria-Rollen-Mappings für Gecko-Browser je nach vorhandener Schnittstelle).

Einhaltung von Interaktionskonventionen

Aus dem obigen Konzept folgt semantische Gleichheit/Ähnlichkeit zwischen einem Aria-Widget und seinem Pendant innerhalb von „richtigen” Applikationen.

Das nachfolgende Beispiel enthält die selben semantischen Informationen wie die Menüleiste des Firefox-Browser (nicht alle).

Beispiel für eine Menüleiste :

<div role=”menubar”>
<span role=”menuitem” aira-haspopup=”true” tabindex=”0″> Datei</span>
<div role=”menu”>
<span role=”menuitem” tabindex=”-1″> Neues Fenster</span>
<span role=”menuitem” tabindex=”-1″> Neuer Tab</span>
</div>
</div>

Jaws würde dies beim ersten fokussieren ungefähr wie folgt vorlesen: Menüleiste – Menupunkt – Datei – Untermenü – Sie können mit den Pfeiltasten links / rechts navigieren.

Das Tastaturverhalten wird weder von Browser noch vom Screenreader geliefert, sondern ist alleinige Sache des Autors.

Hierbei sollte er sich beim Scripten der Interaktionsmöglichkeiten, am Original-Verhalten des typischen Widgets innerhalb des Betriebssystems orientieren.

Die WAI-ARIA Best Practices sowie das AOL Developer Network enthalten hierzu, zu einigen Widgets entsprechende Empfehlungen für Maus- und Tastatur-Interaktionen.

Die Bedeutung des Focus

Damit der Screenreader die Rolle sowie Eigenschaften des jeweils aktiven Widget-(Unter-)Elements begreifen kann, muss dieses – insbesondere wenn es Interaktionsmöglichkeiten bietet – in der Regel fokusiert werden. Um die Fokusierbarkeit bei allen Elementen zu ermöglichen, wurde das tabindex-Attribut zum Universalattribut deklariert. Bekommt das tabindex-Attribut den Wert „0″, ist es in der normalen Tabreihenfolge durch den User fokussierbar. Hat es dagegen den Wert „-1″, liegt es außerhalb der Tabreihenfolge, kann jedoch vom Autoren mit JavaScript fokussiert werden (Bei Aria-Widgets für „Unterelemente” der Normalfall.). (Näheres zum Setzen/Auslesen des tabindex per JavaScript.)

Fokus setzen

Für das eigentliche Fokussieren durch den Autoren stehen zwei Möglichkeiten bereit:

  1. Die focus-Methode des jeweiligen Element-Objekts
  2. Das Attribut activedescendant

Um den Fokus mit der focus-Methode zu setzen, sollte diese mit einem Timeout aufgerufen werden, um Fehlverhalten in den Browsern zu umgehen. Eine Funktion die dies übernimmt, könnte beispielsweise so aussehen:

function setFocus(elem) {
setTimeout(function(){
elem.focus();
}, 1);
}

Bei meinen Tests mit einem Accordion-Widget hat sich gezeigt (hier gibt es keine spezifischen Aria-Rollen), dass ein Timeout von mehr als 180ms notwendig ist, damit Jaws (9.0) beim Vorlesen den neu angezeigten Bereich nicht überspringt.

Bei der activedesendant-Methode bekommt das tatsächlich fokussierte Element das Attribut activedescendant mit der ID des „virtuell” fokussierten Elements.

Stylen des Fokus

Daneben muss das fokussierte Element entsprechend gestylt werden. Damit dies richtig funktioniert, sollte nicht auf die Pseudo-Selectoren :focus/:active zugegriffen, sondern beim Event „focus” eine entsprechende CSS-Klasse gesetzt und beim Event „blur” wieder entfernt werden.

Alles im Blick

Bei vielen Widgets wird der Autor die Tastaturfunktionalität über die Pfeiltasten realisieren und hierbei das default–Verhalten des Browser unterbinden müssen. Daher sollte darauf geachtet werden, dass insbesondere die wichtigen Bereiche des Widgets immer im Viewport bleiben. Grundsätzlich wird dies bereits durch das Fokusieren des aktiven Elements erledigt. In bestimmten Fällen kann man sich hierauf alleine jedoch nicht verlassen.

Gründe hierfür können sein:

  • der interessante Bereich ist größer z.B. kleiner Menüpunkt öffnet großes Untermenü
  • man fokusiert nicht „physisch” sondern mit Hilfe des activedescendant-Attributs
  • Das fokusierte Element spannt sich nicht vollständig auf

In diesen Fällen kann mit der Methode „scrollIntoView”, CSS-Anpassung oder ganz kompliziert durch Abfragen des Viewports, des Scrollbereichs sowie der Position des DOM-Elements der neue scrollLeft/scrollRight-Wert berechnet und das DOM-Element in den Viewport geschoben werden.

fliegender Wechsel zwischen Maus und Tastaturnutzung

Bei vielen Widgets sollte der Autor darauf achten, dass ein Wechsel zwischen Maus und Tastaturnutzung möglich ist. D. h. Hat der User beispielsweise einen Menüpunkt aktiviert und drückt der User auf die Pfeiltaste rechts, muss das Untermenü – sofern vorhanden – geöffnet und der 1. Menüpunkt aktiviert werden. Ähnlich sieht es beispielsweise bei einem Slider-Widget aus. Hat der User es mit der Maus aktiviert und fängt an zu ziehen, sollte mit den Pfeiltasten link/rechts ebenfalls in Kontakt treten können, ohne vorher mit Tab den Fokus selbst setzen zu müssen.

1.0 Semantik überschreiben

Das Role Attrbibut überschreibt die Sematik herkömmlicher HTML-Elemente. Hierbei sollte darauf geachtet werden, dass keine „falsche” Semantik zurückbleibt. Diese kann mit der Rolle „presentation” überschrieben werden.

Beispiel (Menuleiste):

<ul role=”menubar”>
<li role=”presentation”>
<a href=”#” role=”menuitem” aira-haspopup=”true”> Datei</a>
<div role=”menu”>
<ul role=”presentation”>
<li role=”presentation”><a role=”menuitem” tabindex=”-1″> Neues Fenster</a></li>
<li role=”presentation”><a role=”menuitem” tabindex=”-1″> Neuer Tab</a></li>
</ul>
</div>
</li>
</ul>

Grds. sollte es im obigen Beispiel bereits ausreichen das 2. ul Element mit der Presentation-Rolle zu belegen, die dazugehörigen li-Elemente sollten dann ebenfalls nicht als Listenelemente an die Zugänglichkeitsschnittstelle übertragen werden.

Wer ownt, der ownt

Aria schreibt ähnlich wie HTML vor, dass einige Elemente immer Kindelement einer bestimmten Rolle sein müssen. Dies gilt beispielsweise für die Rolle option, welches ein Kind von select oder einer ähnlichen Rolle sein muss. In manchen Fällen ist dies technisch nicht möglich (Ein input-Element kann keine Kinder haben.) oder widerspricht dem Aufbau der UI-Komponente. In diesen Fällen kann das owns-Attribut verwendet werden, welches anzeigt, dass ein Element das Eltern-Element eines anderen ist.

Beispiel (Combobox):

<div role=”combobox” owns=”list”>
<input aria-haspopup=”true” aria-activedescendant=”i2″ type=”text” name=”combox-input” role=”textfield” />
</div>

<!– ganz viel code dazwischen –>

<ul id=”list”>
<li tabindex=”-1″ id=”i1″>Aria</li>
<li tabindex=”-1″ id=”i2″ class=”active”>Aria Best Practices</li>
</ul>

inhaltliche Bedeutung nicht vergessen

Hat man sein Widget mit Aria semantisiert bzw. verwendet eine Bibliothek, welche einem diese Arbeit abnimmt, sollte man ganz besonders darauf achten, ob die inhaltliche Bedeutung des Widgets klar wird. Nutzt man beispielsweise ein Slider-Widget hilft die ganze semantische Auszeichnung nichts, wenn nicht klar wird, was die Eingabe mit Hilfe des Sliders bewirken soll.

Hierfür stehen häufig verschiedene Möglichkeiten zur Verfügung:

  1. der Text-Knoten des jeweiligen Elements
  2. das title-Attribut des jeweiligen Elements
  3. ein mit dem Element verknüpftes label-Element ([for=widget-id])
  4. Ein Textknoten eines anderen Elements auf das das Widget mit labeldby verweist

Umfangreichere Informationen zum Widget können mit dem Attribut describedby zugänglich gemacht werden.

Fallstricke & Bugs

Grundsätzlich ist Wai-Aria sehr leicht zu implementieren, allerdings gab es bei meinen Versuchen folgende Probleme/offene Fragen:

  • Barrierefreiheit 1.0 vs. 2.0

    In einigen Fallen weicht das Aria-Konzept von dem ab, was normalerweise beim konventionellen, barrierearmen JavaScript gemacht wird. Dies betrifft insbesondere 2 Punkte.

    1. Aria implementiert eine verbesserte Tastaturnutzung insbesondere mit Pfeiltasten. Tabben soll in der Regel innerhalb des Widgets nicht verwendet werden können. Vielmehr soll der User hierdruch das Widget verlassen. Screenreader-/Browserkombinationen, die jedoch kein Aria verstehen, könnten einerseits die Nutzung der Pfeiltasten für sich beanspruchen und nicht an den Browser weiterleiten andererseits übernehmen sie häufig Fokusänderungen durch den JavaScript-Autoren nicht.
    2. Viele Aria-Widgets sollen explizit die display Eigenschaft ‘none’ nutzen, welche mit dem Aria Attribut ‘hidden’ korrespondiert. Währendem nach konventionellem barrierearmen Javascript man in einigen dieser Fälle eher den Inhalt lediglich aus dem Viewport schieben würde.
  • Nicht vorhandene, aber evtl. allgemein doch nutzbare Rollen?
    Im Web existieren häufig Widgets, welche in Applikationen nicht vorkommen und auch in WAI-ARIA nicht spezifiziert wurden.
    Ein Beispiel hierfür wäre beispielsweise ein typisches Accordion. Die Zugänglichkeit eines solchen Widgets kann sicherlich ebenfalls mit Aria erhöht werden, es stellt sich allerdings die Frage welche Rollen und sonstigen Eigenschaften dem Accordion-Widget am nächsten kommen, ohne dass gleichzeitig eine „falsche” Bedienmöglichkeit suggeriert wird.
  • Bugs im Screenreader
    Daneben zeigen sich insbesondere in Jaws noch einige Bugs, die anscheinend darauf zurückzuführen sind, dass die – normalerweise von „richtigen” Programmen benutzen – Rollen noch nicht ausreichend an die Erfordernisse im Web angepasst wurden.

Fazit

Trotz einiger Bugs und anderer Schwierigkeiten ist das Potential von Aria nicht zu unterschätzen. Mich hat schwer beeindruckt, wie sich die Gebrauchstauglichkeit in Screenradern von vielen alltäglichen JavaScript-Widgets mit recht einfachen Mitteln auf hohem Niveau verbessern lässt.

Gute Ressourcen

Wer Lust auf mehr bekommen hat, kann auf eine wachsende Anzahl von Tutorials, Artikeln und Beispielen zurückgreifen:

Written June 23, 2008 by
protofunc
« older posts