Corrections
Plusieurs améliorations sont nécessaires pour rendre ce composant accessible :
- La liste d'onglets doit posséder un attribut
role="tablist"
;
- Chaque onglet doit posséder un attribut
role="tab"
;
- Chaque panneau doit posséder un attribut
role="tabpanel"
;
-
Chaque onglet actif doit posséder un attribut
aria-selected="true"
(ou "false"
s'il est inactif) pour préciser son état ;
-
Chaque onglet doit posséder un attribut
aria-controls="id-du-panneau"
qui le lie au panneau qu'il contrôle ;
-
Chaque panneau doit posséder un attribut
aria-labelledby="id-de-l-onglet"
qui le lie à l'onglet qui le contrôle ;
-
À partir du titre d’un onglet, si le panneau n’est pas activé par défaut, la touche Espace ne permet pas d’activer le panneau ;
-
Depuis un onglet, les touches ↑ et ←
doivent permettre d'atteindre l'onglet précédent ;
-
Depuis un onglet, les touches ↓ et →
doivent permettre d'atteindre l'onglet suivant ;
Il faut dans un premier temps corriger le template par défaut. On peut facilement ajouter les attributs role
tablist et tabpanel sur le template uib/template/tabs/tabset.html
.
On peut ensuite ajouter le role
tab sur le uib/template/tabs/tab.html
.
Pour corriger aria-selected="true"
sur le template uib/template/tabs/tab.html
on peut utiliser une expression Angular pour changer la valeur en fonction du $scope
active. Ce qui donne
aria-selected="{{active ? 'true' : 'false'}}"
.
angular.module('uib/template/tabs/tabset.html', []).run(['$templateCache', function($templateCache) {
$templateCache.put(
'uib/template/tabs/tabset.html',
'<div>\n' +
'<ul role="tablist" class="nav nav-{{type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>\n' +
'<div class="tab-content">\n' +
'<div role="tabpanel" class="tab-pane"\n' +
'ng-repeat="tab in tabset.tabs"\n' +
'tabindex="0"\n' +
'ng-class="{active: tabset.active === tab.index}"\n' +
'uib-tab-content-transclude="tab">\n' +
'</div>\n' +
'</div>\n' +
'</div>\n' +
'');
}]);
angular.module('uib/template/tabs/tab.html', []).run(['$templateCache', function($templateCache) {
$templateCache.put(
'uib/template/tabs/tab.html',
'<li role="tab" ng-class="{active: active, disabled: disabled}" aria-selected="{{active ? \'true\' : \'false\'}}">\n' +
'<a tabindex="{{active ? \'0\' : \'-1\'}}" href ng-click="select()" tab-heading-transclude>{{heading}}</a>\n' +
'</li>\n' +
'');
}]);
Pour corriger aria-controls="id-du-panneau"
et aria-labelledby="id-de-l-onglet"
,
il faut procéder en 2 étapes : d'abord créer un id unique, puis placer correctement l'id dans le DOM.
Il faut créer une factory qui renvoie un id unique. Cette fonction prend en paramètre un préfixe puis cherche dans le document
un id unique aléatoirement.
a11yBootstrapModule.factory('getUID', function(){
return function(prefix){
do {
prefix += Math.floor(Math.random() * 1000000);
} while (document.getElementById(prefix));
return prefix;
};
});
Nous allons maintenant placer cet id unique dans le DOM. Nous allons donc injecter notre factory getUID
dans la directive. Nous avons besoin d'ajouter les id après le rendu du DOM, il faut pour cela ajouter le service
$timeout
. Ainsi la fonction render()
sera appliquée après le rendu du DOM.
a11yBootstrapModule.directive('uibTabset', ['getUID', '$timeout',function(getUID, $timeout){
return {
link: function($scope, iElm, iAttrs, controller) {
function render() {}
$timeout(render,0);
}
};
}]);
Dans un premier temps, il faut récupérer le tableau de tabs et de tabpanels,
puis le pacourir avec un forEach
.
Pour chaque tab nous devons générer un id unique, y affecter et ajouter l'attribut aria-labelledby
au panel correspondant. De la même manière, pour chaque panel, il faut générer un id unique, puis affecter et ajouter l'attribut
aria-controls
au tab correspondant.
a11yBootstrapModule.directive('uibTabset', ['getUID', '$timeout',function(getUID, $timeout){
return {
link: function($scope, iElm, iAttrs, controller) {
function render() {
var tablist = iElm[0].firstElementChild;
var tabs = angular.element(tablist).children();
var tabContent = iElm[0].lastElementChild;
var tabpanels = angular.element(tabContent).children();
angular.forEach(angular.element(tabs), function(value, key){
var tab = angular.element(value);
var panel = angular.element(tabpanels[key]);
var idtab = getUID('tab-');
tab.attr('id', idtab);
panel.attr('aria-labelledby', idtab);
var idpanel = getUID('panel-');
panel.attr('id', idpanel);
tab.attr('aria-controls', idpanel);
});
}
$timeout(render, 0);
}
};
}]);
Pour la correction de la gestion clavier, on peut reprendre la directive keyboardRotate
avec
le paramètre recursion
à 1 et autoClick
à 0. On a donc ajouté keyboard-rotate="{recursion: 1, autoClick: 0}"
à notre template. Si le but est d'activer automatiquement l'onglet à la navigation, il faut alors mettre le paramètre autoClick
à 1.
angular.module('uib/template/tabs/tabset.html', []).run(['$templateCache', function($templateCache) {
$templateCache.put(
'uib/template/tabs/tabset.html',
'<div>\n' +
'<ul role="tablist" keyboard-rotate="{recursion: 1, autoClick: 0}" class="nav nav-{{type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>\n' +
'<div class="tab-content">\n' +
'<div role="tabpanel" class="tab-pane"\n' +
'ng-repeat="tab in tabset.tabs"\n' +
'tabindex="0"\n' +
'ng-class="{active: tabset.active === tab.index}"\n' +
'uib-tab-content-transclude="tab">\n' +
'</div>\n' +
'</div>\n' +
'</div>\n' +
'');
}]);