From eaf09c7f76ff7eedb69c1b6f9354c603b8e53135 Mon Sep 17 00:00:00 2001 From: Tim Robertson Date: Fri, 18 Oct 2013 23:18:22 -0400 Subject: [PATCH] Accessibility updates based on jQuery UI Autocomplete --- select2.css | 11 +++++++++ select2.js | 46 ++++++++++++++++++++++++++++++----- select2_locale_en.js.template | 1 + 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/select2.css b/select2.css index 626ff8af..9b50f24a 100644 --- a/select2.css +++ b/select2.css @@ -297,6 +297,17 @@ Version: @@ver@@ Timestamp: @@timestamp@@ background-position: -18px 1px; } +.select2-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + /* results */ .select2-results { max-height: 200px; diff --git a/select2.js b/select2.js index 43d1d3f5..14f6c620 100644 --- a/select2.js +++ b/select2.js @@ -677,6 +677,13 @@ the specific language governing permissions and limitations under the Apache Lic this.container = this.createContainer(); + this.liveRegion = $("", { + role: "status", + "aria-live": "polite" + }) + .addClass("select2-hidden-accessible") + .appendTo(document.body); + this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()).replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); this.containerSelector="#"+this.containerId; this.container.attr("id", this.containerId); @@ -763,7 +770,7 @@ the specific language governing permissions and limitations under the Apache Lic this.dropdown.on("click mouseup mousedown", function (e) { e.stopPropagation(); }); this.nextSearchTerm = undefined; - + if ($.isFunction(this.opts.initSelection)) { // initialize selection based on the current value of the source element this.initSelection(); @@ -791,7 +798,7 @@ the specific language governing permissions and limitations under the Apache Lic this.autofocus = opts.element.prop("autofocus"); opts.element.prop("autofocus", false); if (this.autofocus) this.focus(); - + }, // abstract @@ -804,6 +811,7 @@ the specific language governing permissions and limitations under the Apache Lic if (select2 !== undefined) { select2.container.remove(); + select2.liveRegion.remove(); select2.dropdown.remove(); element .removeClass("select2-offscreen") @@ -861,7 +869,7 @@ the specific language governing permissions and limitations under the Apache Lic opts = $.extend({}, { populateResults: function(container, results, query) { - var populate, id=this.opts.id; + var populate, id=this.opts.id, liveRegion=this.liveRegion; populate=function(results, container, depth) { @@ -910,6 +918,8 @@ the specific language governing permissions and limitations under the Apache Lic node.data("select2-data", result); container.append(node); } + + liveRegion.text(opts.formatMatches(results.length)); }; populate(results, container, 0); @@ -1484,6 +1494,8 @@ the specific language governing permissions and limitations under the Apache Lic this.ensureHighlightVisible(); + this.liveRegion.text(choice.text()); + data = choice.data("select2-data"); if (data) { this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data }); @@ -1591,6 +1603,12 @@ the specific language governing permissions and limitations under the Apache Lic function postRender() { search.removeClass("select2-active"); self.positionDropdown(); + if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) { + self.liveRegion.text(results.text()); + } + else { + self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length)); + } } function render(html) { @@ -1828,9 +1846,11 @@ the specific language governing permissions and limitations under the Apache Lic "  ", " ", "", + "", "", "
", " ", @@ -1960,7 +1980,10 @@ the specific language governing permissions and limitations under the Apache Lic // rewrite labels from original element to focusser this.focusser.attr("id", "s2id_autogen"+idSuffix); - elementLabel = $("label[for='" + this.opts.element.attr("id") + "']") + elementLabel = $("label[for='" + this.opts.element.attr("id") + "']"); + + this.focusser.prev() + .text(elementLabel.text()) .attr('for', this.focusser.attr('id')); // Ensure the original element retains an accessible name @@ -1969,6 +1992,13 @@ the specific language governing permissions and limitations under the Apache Lic this.focusser.attr("tabindex", this.elementTabIndex); + // write label for search field using the label from the focusser element + this.search.attr("id", this.focusser.attr('id') + '_search'); + + this.search.prev() + .text($("label[for='" + this.focusser.attr('id') + "']").text()) + .attr('for', this.search.attr('id')); + this.search.on("keydown", this.bind(function (e) { if (!this.isInterfaceEnabled()) return; @@ -2148,7 +2178,7 @@ the specific language governing permissions and limitations under the Apache Lic self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val()); } }); - } + } }, isPlaceholderOptionSelected: function() { @@ -2410,6 +2440,7 @@ the specific language governing permissions and limitations under the Apache Lic }).html([ "
    ", "
  • ", + " ", " ", "
  • ", "
", @@ -2521,7 +2552,9 @@ the specific language governing permissions and limitations under the Apache Lic // rewrite labels from original element to focusser this.search.attr("id", "s2id_autogen"+nextUid()); - $("label[for='" + this.opts.element.attr("id") + "']") + + this.search.prev() + .text($("label[for='" + this.opts.element.attr("id") + "']").text()) .attr('for', this.search.attr('id')); this.search.on("input paste", this.bind(function() { @@ -3257,6 +3290,7 @@ the specific language governing permissions and limitations under the Apache Lic }, formatResultCssClass: function(data) {return data.css;}, formatSelectionCssClass: function(data, container) {return undefined;}, + formatMatches: function (matches) { return matches + " results are available, use up and down arrow keys to navigate."; }, formatNoMatches: function () { return "No matches found"; }, formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1? "" : "s"); }, formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); }, diff --git a/select2_locale_en.js.template b/select2_locale_en.js.template index 3f7b39b5..f66bcc84 100644 --- a/select2_locale_en.js.template +++ b/select2_locale_en.js.template @@ -7,6 +7,7 @@ "use strict"; $.extend($.fn.select2.defaults, { + formatMatches: function (matches) { return matches + " results are available, use up and down arrow keys to navigate."; }, formatNoMatches: function () { return "No matches found"; }, formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1 ? "" : "s"); }, formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },