明日のためにその2

ツールバー・ボタンのWAI-ARIA対応のための要見直しメモ

現状のソース


/* JavaScript */
/*  toggleToolbar()
    ツールバー・ボタンのクリックで表示・非表示を切り替え    */
function toggleToolbar(obj) {
    'use strict';
    var _win = window, _doc = document,
        tool = [{id : 'navi-button', target : '#global-navigation'},
                {id : 'search-button', target : '#utility > #site-search'},
                {id : 'info-button', target : '#utility-list'}],
        cName = obj.getAttribute('class'),
        spch = _win.matchMedia ? _win.matchMedia('speech').matches : false,
        target, tObj, i = 0, l = tool.length;
    if(cName === 'opener') {
        for(i = 0; i < l; i++) {
            target = _doc.querySelector(tool[i].target);
            obj.id === tool[i].id && target ? (tObj = target, target.setAttribute('class', 'show'), obj.setAttribute('class', 'closer')) : (target && /show/.test(target.getAttribute('class')) && target.setAttribute('class', 'hide'), _doc.getElementById(tool[i].id).setAttribute('class', 'opener'));
        }
        spch && tObj && _win.confirm('「' + tObj.title + '」に移動しますか?') && (_win.self.location.href = '#' + tObj.id);
        obj.id === 'search-button' ? _doc.getElementById('search').focus() : tObj && tObj.focus();
    }
    else if(cName === 'closer') {
        for(i = 0; i < l; i++) {
            target = _doc.querySelector(tool[i].target);
            target && /show/.test(target.getAttribute('class')) && target.setAttribute('class', 'hide');
            _doc.getElementById(tool[i].id).setAttribute('class', 'opener');
        }
        !spch && obj.blur();
    }
}
(function(_win, _doc) {
    'use strict';
    /*  setToolbar()
        ツールバーの設定    */
    function setToolbar() {
        if(_doc.defaultView.getComputedStyle(_doc.getElementById('toolbar'), '').display !== 'none') {
            var navi = _doc.getElementById('navi-button'),
                srch = _doc.getElementById('search-button'),
                info = _doc.getElementById('info-button');
            _doc.getElementById('home-button').addEventListener('click', function() { _win.self.location.href = 'http://asamuzak.jp/'; }, false);
            navi.setAttribute('class', 'opener');
            navi.addEventListener('click', function() { toggleToolbar(this); }, false);
            _doc.querySelector('#utility > #site-search') ? (srch.removeAttribute('disabled'), srch.setAttribute('class', 'opener'), srch.addEventListener('click', function() { toggleToolbar(this); }, false)) : srch.setAttribute('disabled', 'disabled');
            _doc.getElementById('utility-list') ? (info.removeAttribute('disabled'), info.setAttribute('class', 'opener'), info.addEventListener('click', function() { toggleToolbar(this); }, false)) : info.setAttribute('disabled', 'disabled');
        }
    }
    _doc.addEventListener('DOMContentLoaded', function() {
        setToolbar();
    }, false);
})(window, document);


<!-- HTML -->
<div id="toolbar" role="menubar">
    <button id="home-button" type="button" title="ホーム" role="menuitem"><img src="http://asamuzak.jp/svg/home.svg" alt="ホーム" /></button>
    <button id="navi-button" type="button" title="ナビゲーション" role="menuitem" aria-controls="global-navigation"><img src="http://asamuzak.jp/svg/navi.svg" alt="ナビゲーション" /></button>
    <button id="search-button" type="button" title="検索" role="menuitem" aria-controls="site-search"><img src="http://asamuzak.jp/svg/search_brg.svg" alt="検索" /></button>
    <button id="info-button" type="button" title="お知らせ" role="menuitem" aria-controls="utility-list"><img src="http://asamuzak.jp/svg/info.svg" alt="お知らせ" /></button>
</div>
  • classの差し替えで制御するのではなくて、aria-selectedとaria-hiddenで制御
  • aria-controlsの設定はsetToolbar()で? さらに、targetはaria-controlsの値を取得するよう変更?
  • media="speech"への対応は…サポートしているブラウザがない現状では不要かな?
  • そもそものマークアップは妥当? a要素でマークアップしておいてスクリプトでボタンに差し替えるべき? つーか、listだよな…orz

修正後

テストサンプル


/* CSS */
.hide {
    position: absolute;
    left: -9999px;
    width: 0;
    height: 0;
    overflow: hidden;
}
.show {
    (適宜)
}


/* JavaScript */
/*
*   setToolbar(), toggleToolbar()
*   ツールバー・ボタンのクリックで表示・非表示を切り替え
*   Copyright (c) 2014 Kazz
*   http://asamuzak.jp
*   Dual licensed under MIT or GPL
*   http://asamuzak.jp/license
*/
(function(_win, _doc) {
    'use strict';
    function setToolbar() {
        var toolbarSetting = function() {
            // c : コントローラとなるボタン
            // t : 表示・非表示を切り替えるターゲット
            return [{ c : 'navi-button', t : 'global-navigation' },
                    { c : 'search-button', t : '#utility > #site-search' },
                    { c : 'info-button', t : 'utility-list'}];
        };
        var setEvent = function(o, a) {
            o.addEventListener('click', function() {
                toggleToolbar(this, a);
            }, false);
        };
        for(var _$ = toolbarSetting(), c, t, i = 0, l = _$.length; i < l; i++) {
            (c = _doc.getElementById(_$[i].c) || _doc.querySelector(_$[i].c)) && ((t = _doc.getElementById(_$[i].t) || _doc.querySelector(_$[i].t)) ? (c.removeAttribute('disabled'), c.setAttribute('class', 'opener'), c.setAttribute('aria-controls', t.id), setEvent(c, _$), t.setAttribute('aria-labelledby', c.id), t.setAttribute('class', 'hide'), t.setAttribute('tabindex', '-1')) : c.setAttribute('disabled', 'disabled'));
        }
    }
    function toggleToolbar(obj, tool) {
        var c, t, i = 0, l = tool.length;
        switch(obj.getAttribute('class')) {
            case 'opener' :
                for(i = 0; i < l; i++) {
                    c = _doc.getElementById(tool[i].c) || _doc.querySelector(tool[i].c);
                    t = _doc.getElementById(tool[i].t) || _doc.querySelector(tool[i].t);
                    c === obj && t ? (c.setAttribute('class', 'closer'), t.setAttribute('class', 'show')) : (c && c.setAttribute('class', 'opener'), t && t.setAttribute('class', 'hide'));
                }
                break;
            case 'closer' :
                for(i = 0; i < l; i++) {
                    (c = _doc.getElementById(tool[i].c) || _doc.querySelector(tool[i].c)) && c.setAttribute('class', 'opener');
                    (t = _doc.getElementById(tool[i].t) || _doc.querySelector(tool[i].t)) && t.setAttribute('class', 'hide');
                }
                break;
        }
    }
    _doc.addEventListener('DOMContentLoaded', setToolbar, false);
})(window, document);

"明日のためにその2"へのTwitter上でのコメントやRT

9件のツイートがあります。

ツイート 1

ツイート 2

ツイート 3

ツイート 4

ツイート 5

ツイート 6

ツイート 7

ツイート 8

ツイート 9