diff options
| author | luccioman <luccioman@users.noreply.github.com> | 2017-09-22 11:00:46 +0200 |
|---|---|---|
| committer | luccioman <luccioman@users.noreply.github.com> | 2017-09-22 11:00:46 +0200 |
| commit | 0b0980b36409168725aef3de787d97ae01e06b70 (patch) | |
| tree | ed1408a401ccda740a0e2639caf1996d4f74bc69 /htroot | |
| parent | 62c7cd9a77012600605e3fae6982e48822d3a5a7 (diff) | |
Improved accessibility of histograms widgets.
Added keyboard navigation support and missing WAI-ARIA attributes.
Tested with NVDA 2017.3 screenreader on recent major browsers.
Diffstat (limited to 'htroot')
| -rw-r--r-- | htroot/ConfigSearchPage_p.html | 42 | ||||
| -rw-r--r-- | htroot/HostBrowser.html | 43 | ||||
| -rw-r--r-- | htroot/js/accessibleHistogram.js | 134 | ||||
| -rwxr-xr-x | htroot/jslicense.html | 5 | ||||
| -rw-r--r-- | htroot/yacysearchtrailer.html | 103 |
5 files changed, 250 insertions, 77 deletions
diff --git a/htroot/ConfigSearchPage_p.html b/htroot/ConfigSearchPage_p.html index e66419f81..7bd5b4eab 100644 --- a/htroot/ConfigSearchPage_p.html +++ b/htroot/ConfigSearchPage_p.html @@ -182,6 +182,7 @@ <link rel="stylesheet" href="env/morris.css"> <script src="js/raphael.min.js"></script> <script src="js/morris.js"></script> +<script type="text/javascript" src="js/accessibleHistogram.js" charset="UTF-8"></script> <div id="graph" style="height:200px"></div> <script> var solr= $.getJSON("solr/collection1/select?q=*:*&defType=edismax&start=0&rows=0&wt=json&facet=true&facet.field=dates_in_content_dts&facet.sort=index", function(data) { @@ -192,23 +193,30 @@ var solr= $.getJSON("solr/collection1/select?q=*:*&defType=edismax&start=0&rows= var count = dates_in_content_dts[i + 1]; if (date && count) {parsed[parsed.length] = {x: date,y: count};}; }; - if (parsed.length > 0) Morris.Bar({ - element: 'graph', - data: parsed, - xkey: 'x', - ykeys: ['y'], - labels: ['number of documents about this date'], - yLabelFormat: function (y) { return y.toString() + ' docs'; }, - barColors: function (row, series, type) { - var d = new Date(row.label); - if (d.getDay() === 6) return '#4aaf46'; //saturday - if (d.getDay() === 0) return '#4aaf46'; //sunday - return '#3574c0'; - }, - hideHover: 'false' - }).on('click', function(i, row) { - console.log(i, row); - }); + if (parsed.length > 0) { + var histogram = Morris.Bar({ + element: 'graph', + data: parsed, + xkey: 'x', + ykeys: ['y'], + labels: ['number of documents about this date'], + yLabelFormat: function (y) { return y.toString() + ' docs'; }, + barColors: function (row, series, type) { + var d = new Date(row.label); + if (d.getDay() === 6) return '#4aaf46'; //saturday + if (d.getDay() === 0) return '#4aaf46'; //sunday + return '#3574c0'; + }, + hideHover: 'false' + }).on('click', function(i, row) { + console.log(i, row); + }); + + /* Add keyboard navigation support and accessible attributes */ + makeAccessibleMorrisBar(histogram, + "Number of documents per date histogram", + function(data) {return data.x + " : " + data.y + " docs"}); + } }); </script> diff --git a/htroot/HostBrowser.html b/htroot/HostBrowser.html index 65d499420..6a20be864 100644 --- a/htroot/HostBrowser.html +++ b/htroot/HostBrowser.html @@ -121,6 +121,7 @@ function updatepage(str) { <link rel="stylesheet" href="env/morris.css"> <script src="js/raphael.min.js"></script> <script src="js/morris.js"></script> +<script type="text/javascript" src="js/accessibleHistogram.js" charset="UTF-8"></script> <div id="graph" style="height:200px"></div> <script> var solr= $.getJSON("solr/collection1/select?q=*:*&defType=edismax&start=0&rows=0&wt=json&facet=true&facet.field=dates_in_content_dts&facet.sort=index", function(data) { @@ -131,23 +132,31 @@ var solr= $.getJSON("solr/collection1/select?q=*:*&defType=edismax&start=0&rows= var count = dates_in_content_dts[i + 1]; if (date && count) {parsed[parsed.length] = {x: date,y: count};}; }; - if (parsed.length > 0) Morris.Bar({ - element: 'graph', - data: parsed, - xkey: 'x', - ykeys: ['y'], - labels: ['number of documents about this date'], - yLabelFormat: function (y) { return y.toString() + ' docs'; }, - barColors: function (row, series, type) { - var d = new Date(row.label); - if (d.getDay() === 6) return '#4aaf46'; //saturday - if (d.getDay() === 0) return '#4aaf46'; //sunday - return '#3574c0'; - }, - hideHover: 'false' - }).on('click', function(i, row) { - console.log(i, row); - }); + if (parsed.length > 0) { + var histogram = Morris.Bar({ + element: 'graph', + data: parsed, + xkey: 'x', + ykeys: ['y'], + labels: ['number of documents about this date'], + yLabelFormat: function (y) { return y.toString() + ' docs'; }, + barColors: function (row, series, type) { + var d = new Date(row.label); + if (d.getDay() === 6) return '#4aaf46'; //saturday + if (d.getDay() === 0) return '#4aaf46'; //sunday + return '#3574c0'; + }, + hideHover: 'false' + }).on('click', function(i, row) { + console.log(i, row); + }); + + /* Add keyboard navigation support and accessible attributes */ + makeAccessibleMorrisBar(histogram, + "Number of documents per date histogram", + function(data) {return data.x + " : " + data.y + " docs"}); + } + }); </script> #(/hosts)# diff --git a/htroot/js/accessibleHistogram.js b/htroot/js/accessibleHistogram.js new file mode 100644 index 000000000..c1bfa4bdc --- /dev/null +++ b/htroot/js/accessibleHistogram.js @@ -0,0 +1,134 @@ +/* +* Copyright (C) 2017 by luccioman; https://github.com/luccioman +* +* This file is part of YaCy. +* +* YaCy is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* +* YaCy is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with YaCy. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * Add complementary features to a bar chart created with morris.js for improved accessibility : + * keyboard navigation support and accessible labels and widget roles. + * @param {Morris.Bar} morrisBar a bar chart created with Morris.Bar() + * @param {String} title the accessible title to add to the bar chart + * @param {Function} barLabelGenerator the eventual function providing an accessible label for each bar. The function must accept one parameter (the data item related to the bar) and return a String. + * @param {String} barRole the eventual ARIA role to assign to each bar element + * @param {Function} clickHandler an eventual click event handler function defined on the chart and to be applied when pressing "Enter" on a focused bar + */ +function makeAccessibleMorrisBar(morrisBar, title, barLabelGenerator, barRole, clickHandler) { + if(morrisBar && morrisBar.el && morrisBar.el.length > 0) { + var svgBarChart = morrisBar.el[0]; + /* Mark the chart with the appropriate ARIA roles, including fallback values for older user agents */ + svgBarChart.setAttribute("role", "graphics-document figure document"); + + /* Add a comprehensive title */ + var titleElements = svgBarChart.getElementsByTagName("title"); + var titleElement; + if(titleElements.length < 1) { + titleElement = document.createElement("title"); + } else { + titleElement = titleElements[0]; + } + titleElement.innerHTML = title; + titleElement.id = "morisBarTitle"; + svgBarChart.insertBefore(titleElement, svgBarChart.firstChild); + svgBarChart.setAttribute("aria-labelledby", "morisBarTitle"); + + /* Handle keyboard events on focusable bars to allow keyboard navigation */ + var histogramBarKeydownHandler = function(event) { + if(event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + /* Prevent collision with any eventual other keyboard shortcuts */ + return; + } + + if(event.key == "ArrowRight" || event.keyCode == 39) { + var nextFocusable = this.nextSibling; + /* Look for the next focusable bar */ + while(nextFocusable != null && (!nextFocusable.focus || !nextFocusable.hasAttribute("tabindex"))) { + nextFocusable = nextFocusable.nextSibling; + } + if(nextFocusable != null && nextFocusable.focus && nextFocusable.tabIndex != null) { + /* Set the current bar focusable but out of the tab sequence */ + this.setAttribute("tabindex", "-1") + /* Set the next bar focusable in the tab sequence */ + nextFocusable.setAttribute("tabindex", "0"); + /* Give focus to the next bar */ + nextFocusable.focus(); + } + } else if(event.key == "ArrowLeft" || event.keyCode == 37) { + var prevFocusable = this.previousSibling; + /* Look for the previous focusable bar */ + while(prevFocusable != null && (!prevFocusable.focus || !prevFocusable.hasAttribute("tabindex"))) { + prevFocusable = prevFocusable.previousSibling; + } + if(prevFocusable != null && prevFocusable.focus && prevFocusable.tabIndex != null) { + /* Set the current bar focusable but out of the tab sequence */ + this.setAttribute("tabindex", "-1"); + /* Set the next bar focusable in the tab sequence */ + prevFocusable.setAttribute("tabindex", "0"); + /* Give focus to the next bar */ + prevFocusable.focus(); + } + } else if(clickHandler && (event.key == "Enter" || event.key == "NumpadEnter" || event.keyCode == 13)) { + /* Find the data index from the bar position */ + var dataIndex = morrisBar.hitTest(this.x.animVal.value); + if(dataIndex != null && dataIndex >= 0 && dataIndex < morrisBar.options.data.length) { + /* Implement the same behavior as a link */ + clickHandler(morrisBar.options.data[dataIndex]); + } + } + }; + + /* When a bar receive focus from keyboard navigation : show the same toolip as the one used on mouse hover */ + var histogramBarFocusHandler = function() { + /* Find the data index from the bar position */ + var dataIndex = morrisBar.hitTest(this.x.animVal.value); + if(dataIndex != null && dataIndex >= 0 && morrisBar.hover != null) { + morrisBar.hover.update.apply(morrisBar.hover, morrisBar.hoverContentForRow(dataIndex)); + } + }; + + /* When a bar looses focus : hide the tooltip */ + var histogramBarBlurHandler = function() { + if (morrisBar.options.hideHover !== false) { + morrisBar.hover.hide(); + } + }; + + var bars = svgBarChart.getElementsByTagName("rect"); + var data, count, bar, firstFocusableBar = true; + for(var i = 0; i < bars.length && i < morrisBar.options.data.length; i++) { + data = morrisBar.options.data[i]; + count = data.y; + bar = bars[i]; + /* Only make non zero value bars focusable */ + if(count != "0") { + /* Add the eventual bar specific role */ + if(barRole) { + bar.setAttribute("role", barRole); + } + /* Add an accessible label as the regular hover is dynamically generated and this doesn't work well with screen readers */ + bar.setAttribute("aria-label", barLabelGenerator ? barLabelGenerator(data) : data.x); + /* make each bar keyboard focusable, adding only the first one to the main tab sequence */ + bar.setAttribute("tabindex", firstFocusableBar ? "0" : "-1"); + /* Handle keyboard navigation */ + bar.onkeydown = histogramBarKeydownHandler; + /* Show/hide each bar tooltip when each bear receive/loose focus with keyboard */ + bar.onfocus = histogramBarFocusHandler; + bar.onblur = histogramBarBlurHandler; + firstFocusableBar = false; + } + } + } +}
\ No newline at end of file diff --git a/htroot/jslicense.html b/htroot/jslicense.html index 07d1cd2fc..11e72f68a 100755 --- a/htroot/jslicense.html +++ b/htroot/jslicense.html @@ -124,6 +124,11 @@ <td><a href="js/sorttable.js">sorttable.js</a> (2.1.3)</td>
</tr>
<tr>
+ <td><a href="js/accessibleHistogram.js">accessibleHistogram.js</a></td>
+ <td><a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU-GPL-2.0-or-later</a></td>
+ <td><a href="js/accessibleHistogram.js">accessibleHistogram.js</a></td>
+ </tr>
+ <tr>
<td><a href="js/ajax.js">ajax.js</a></td>
<td><a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU-GPL-2.0-or-later</a></td>
<td><a href="js/ajax.js">ajax.js</a></td>
diff --git a/htroot/yacysearchtrailer.html b/htroot/yacysearchtrailer.html index 23250b53e..ca6f9c4cf 100644 --- a/htroot/yacysearchtrailer.html +++ b/htroot/yacysearchtrailer.html @@ -97,51 +97,68 @@ show search results for "#[query]#" on map</a> <link rel="stylesheet" href="env/morris.css"> <script src="js/raphael.min.js"></script> <script src="js/morris.js"></script> +<script type="text/javascript" src="js/accessibleHistogram.js" charset="UTF-8"></script> <script> - document.getElementById("datehistogram").style = "height:200px"; - dates_in_content_dts = [#{element}#"#[name]#","#[count]#"#(nl)#::,#(/nl)##{/element}#]; - var parsed = []; - for (var i = 0; i < dates_in_content_dts.length; i = i + 2) { - var date = dates_in_content_dts[i]; - var count = dates_in_content_dts[i + 1]; - if (date && count) {parsed[parsed.length] = {x: date,y: count};}; - }; - if (parsed.length > 0) Morris.Bar({ - element: 'datehistogram', - data: parsed, - xkey: 'x', - ykeys: ['y'], - labels: ['number of documents about this date'], - yLabelFormat: function (y) { return y.toString() + ' docs'; }, - barColors: function (row, series, type) { - var d = new Date(row.label); - if (d.getDay() === 6) return '#4aaf46'; //saturday - if (d.getDay() === 0) return '#4aaf46'; //sunday - return '#3574c0'; - }, - hideHover: 'false' - }).on('click', function(i, row) { - var query = document.getElementsByClassName('searchinput')[0].getAttribute("value"); - var onp = -1, fromp = -1, top = -1; - if ((onp = query.indexOf("on:")) >= 0) { - query = query.substring(0, onp - 1); + var histogramContainer = document.getElementById("datehistogram"); + if(histogramContainer != null) { + dates_in_content_dts = [#{element}#"#[name]#","#[count]#"#(nl)#::,#(/nl)##{/element}#]; + var parsed = []; + for (var i = 0; i < dates_in_content_dts.length; i = i + 2) { + var date = dates_in_content_dts[i]; + var count = dates_in_content_dts[i + 1]; + if (date && count) {parsed[parsed.length] = {x: date,y: count};}; + }; + if (parsed.length > 0) { + histogramContainer.style = "height:200px; cursor: pointer;"; + + var histogramClickhandler = function(row) { + var query = document.getElementsByClassName('searchinput')[0].getAttribute("value"); + var onp = -1, fromp = -1, top = -1; + if ((onp = query.indexOf("on:")) >= 0) { + query = query.substring(0, onp - 1); + } + if ((fromp = query.indexOf("from:")) < 0) { + query = query + " from:" + row.x; + document.getElementsByClassName('searchinput')[0].value = query; + document.getElementById('Enter').click(); + } else if ((top = query.indexOf("to:")) < 0) { + query = query + " to:" + row.x; + document.getElementsByClassName('searchinput')[0].value = query; + document.getElementById('Enter').click(); + } else { + query = query.substring(0, fromp) + " on:" + row.x; + document.getElementsByClassName('searchinput')[0].value = query; + document.getElementById('Enter').click(); + } + console.log(i, row, query); + } + + var histogram = Morris.Bar({ + element: 'datehistogram', + data: parsed, + xkey: 'x', + ykeys: ['y'], + labels: ['number of documents about this date'], + yLabelFormat: function (y) { return y.toString() + ' docs'; }, + barColors: function (row, series, type) { + var d = new Date(row.label); + if (d.getDay() === 6) return '#4aaf46'; //saturday + if (d.getDay() === 0) return '#4aaf46'; //sunday + return '#3574c0'; + }, + hideHover: 'false' + }).on('click', function(i, row) { + histogramClickhandler(row); + }); + + /* Add keyboard navigation support and accessible attributes */ + makeAccessibleMorrisBar(histogram, + "Number of documents per date histogram", + function(data) {return data.x + " : " + data.y + " docs"}, + "link", + histogramClickhandler); } - if ((fromp = query.indexOf("from:")) < 0) { - query = query + " from:" + row.x; - document.getElementsByClassName('searchinput')[0].value = query; - document.getElementById('Enter').click(); - } else if ((top = query.indexOf("to:")) < 0) { - query = query + " to:" + row.x; - document.getElementsByClassName('searchinput')[0].value = query; - document.getElementById('Enter').click(); - } else { - query = query.substring(0, fromp) + " on:" + row.x; - document.getElementsByClassName('searchinput')[0].value = query; - document.getElementById('Enter').click(); - } - var date = row.x; - console.log(i, row, query); - }); + } </script> #(/nav-dates)# |
