From d4d62ac414a3ba706fb65c581581c419a90d5ac9 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Tue, 27 May 2014 10:59:28 -0400
Subject: [PATCH] Set aria-* attributes for autocompletion fields and widgets

---
 program/js/list.js |    1 +
 program/js/app.js  |   42 +++++++++++++++++++++++++++++-------------
 2 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/program/js/app.js b/program/js/app.js
index 06008b2..4bd6442 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -3381,7 +3381,9 @@
     this.env.recipients_delimiter = this.env.recipients_separator + ' ';
 
     obj.keydown(function(e) { return ref.ksearch_keydown(e, this, props); })
-      .attr('autocomplete', 'off');
+      .attr('autocomplete', 'off')
+      .attr('aria-autocomplete', 'list')
+      .attr('aria-expanded', 'false');
   };
 
   this.submit_messageform = function(draft)
@@ -4471,7 +4473,7 @@
 
         var dir = key==38 ? 1 : 0;
 
-        highlight = document.getElementById('rcmksearchSelected');
+        highlight = document.getElementById('rcmkSearchItem' + this.ksearch_selected);
         if (!highlight)
           highlight = this.ksearch_pane.__ul.firstChild;
 
@@ -4519,14 +4521,14 @@
 
   this.ksearch_select = function(node)
   {
-    var current = $('#rcmksearchSelected');
-    if (current[0] && node) {
-      current.removeAttr('id').removeClass('selected');
+    if (this.ksearch_pane && node) {
+      this.ksearch_pane.find('li.selected').removeClass('selected');
     }
 
     if (node) {
-      $(node).attr('id', 'rcmksearchSelected').addClass('selected');
+      $(node).addClass('selected');
       this.ksearch_selected = node._rcm_id;
+      $(this.ksearch_input).attr('aria-activedecendant', 'rcmkSearchItem' + this.ksearch_selected);
     }
   };
 
@@ -4664,9 +4666,13 @@
     // create results pane if not present
     if (!this.ksearch_pane) {
       ul = $('<ul>');
-      this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane')
+      this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').attr('role', 'listbox')
         .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
       this.ksearch_pane.__ul = ul[0];
+
+      // register (delegate) event handlers
+      ul.on('mouseover', 'li', function(e){ ref.ksearch_select(e.target); })
+        .on('onmouseup', 'li', function(e){ ref.ksearch_click(e.target); })
     }
 
     ul = this.ksearch_pane.__ul;
@@ -4692,10 +4698,9 @@
         text = typeof results[i] === 'object' ? results[i].name : results[i];
         type = typeof results[i] === 'object' ? results[i].type : '';
         li = document.createElement('LI');
-        li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
-        li.onmouseover = function(){ ref.ksearch_select(this); };
-        li.onmouseup = function(){ ref.ksearch_click(this) };
-        li._rcm_id = this.env.contacts.length + i;
+        li._rcm_id = i + this.env.contacts.length;
+        li.id = 'rcmkSearchItem' + li._rcm_id;
+        li.innerHTML = this.quote_html(text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>');
         if (type) li.className = type;
         ul.appendChild(li);
         maxlen -= 1;
@@ -4706,9 +4711,14 @@
       this.ksearch_pane.show();
       // select the first
       if (!this.env.contacts.length) {
-        $('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected');
-        this.ksearch_selected = 0;
+        this.ksearch_select($('li:first', ul).get(0));
       }
+
+      // set the right aria-* attributes to the input field
+      $(this.ksearch_input)
+        .attr('aria-haspopup', 'true')
+        .attr('aria-expanded', 'true')
+        .attr('aria-owns', 'rcmKSearchpane')
     }
 
     if (len)
@@ -4744,6 +4754,12 @@
     if (this.ksearch_pane)
       this.ksearch_pane.hide();
 
+    $(this.ksearch_input)
+      .attr('aria-haspopup', 'false')
+      .attr('aria-expanded', 'false')
+      .removeAttr('aria-activedecendant')
+      .removeAttr('aria-owns');
+
     this.ksearch_destroy();
   };
 
diff --git a/program/js/list.js b/program/js/list.js
index f64d38b..59651b8 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -213,6 +213,7 @@
   if (!this.fixed_header) {
     this.fixed_header = $('<table>')
       .attr('class', this.list.className + ' fixedcopy')
+      .attr('role', 'presentation')
       .css({ position:'fixed' })
       .append(clone)
       .append('<tbody></tbody>');

--
Gitblit v1.9.1