From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Wed, 04 Jun 2014 11:20:33 -0400 Subject: [PATCH] Fix attachment menu structure and aria-attributes --- program/js/list.js | 131 +++++++++++++++++++++++++++---------------- 1 files changed, 81 insertions(+), 50 deletions(-) diff --git a/program/js/list.js b/program/js/list.js index fa37353..cf6c3f0 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -51,7 +51,7 @@ this.rowcount = 0; this.colcount = 0; - this.subject_col = -1; + this.subject_col = 0; this.modkey = 0; this.multiselect = false; this.multiexpand = false; @@ -60,6 +60,7 @@ this.column_movable = false; this.keyboard = false; this.toggleselect = false; + this.aria_listbox = false; this.drag_active = false; this.col_drag_active = false; @@ -75,6 +76,9 @@ if (p && typeof p === 'object') for (var n in p) this[n] = p[n]; + + // register this instance + rcube_list_widget._instances.push(this); }; @@ -94,6 +98,12 @@ this.tbody = this.list; } + if ($(this.list).attr('role') == 'listbox') { + this.aria_listbox = true; + if (this.multiselect) + $(this.list).attr('aria-multiselectable', 'true'); + } + if (this.tbody) { this.rows = {}; this.rowcount = 0; @@ -111,15 +121,9 @@ if (this.keyboard) { rcube_event.add_listener({event:'keydown', object:this, method:'key_press'}); - // install a link element to receive focus. - // this helps to maintain the natural tab order when moving focus with keyboard - this.focus_elem = $('<a>') - .attr('tabindex', '0') - .attr('style', 'display:block; width:1px; height:1px; line-height:1px; overflow:hidden; position:fixed; top:-1000px') - .html('Select List') - .insertAfter(this.list) - .on('focus', function(e){ me.focus(e); }) - .on('blur', function(e){ me.blur(e); }); + // allow the table element to receive focus. + $(this.list).attr('tabindex', '0') + .on('focus', function(e){ me.focus(e); }); } } @@ -165,6 +169,15 @@ }, false); } + // label the list row with the subject col as descriptive label + if (this.aria_listbox) { + var lbl_id = 'l:' + row.id; + $(row) + .attr('role', 'option') + .attr('aria-labelledby', lbl_id) + .find(this.col_tagname()).eq(this.subject_col).attr('id', lbl_id); + } + if (document.all) row.onselectstart = function() { return false; }; @@ -186,7 +199,7 @@ if (this.fixed_header) { // copy (modified) fixed header back to the actual table $(this.list.tHead).replaceWith($(this.fixed_header).find('thead').clone()); - $(this.list.tHead).find('tr td').attr('style', '').find('a').attr('tabindex', '-1'); // remove fixed widths + $(this.list.tHead).find('th,td').attr('style', '').find('a').attr('tabindex', '-1'); // remove fixed widths } else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0) { this.init_fixed_header(); @@ -213,6 +226,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>'); @@ -279,11 +293,10 @@ this.rows = {}; this.rowcount = 0; + this.last_selected = 0; if (sel) this.clear_selection(); - else - this.last_selected = 0; // reset scroll position (in Opera) if (this.frame) @@ -392,23 +405,21 @@ if (this.focused) return; - var n, id; this.focused = true; - - for (n in this.selection) { - id = this.selection[n]; - if (this.rows[id] && this.rows[id].obj) { - $(this.rows[id].obj).addClass('selected').removeClass('unfocused'); - } - } if (e) rcube_event.cancel(e); + var focus_elem = null; + + if (this.last_selected && this.rows[this.last_selected]) { + focus_elem = $(this.rows[this.last_selected].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0'); + } + // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) - if (this.focus_elem) { + if (focus_elem && focus_elem.length) { // We now fix this by explicitly assigning focus to a dedicated link element - this.focus_noscroll(this.focus_elem); + this.focus_noscroll(focus_elem); } else { // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) @@ -416,7 +427,7 @@ window.focus(); } - $(this.list).addClass('focus'); + $(this.list).addClass('focus').removeAttr('tabindex'); // set internal focus pointer to first row if (!this.last_selected) @@ -429,16 +440,18 @@ */ blur: function(e) { - var n, id; this.focused = false; - for (n in this.selection) { - id = this.selection[n]; - if (this.rows[id] && this.rows[id].obj) { - $(this.rows[id].obj).removeClass('selected focused').addClass('unfocused'); - } + + // avoid the table getting focus right again + var me = this; + setTimeout(function(){ + $(me.list).removeClass('focus').attr('tabindex', '0'); + }, 20); + + if (this.last_selected && this.rows[this.last_selected]) { + $(this.rows[this.last_selected].obj) + .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex'); } - - $(this.list).removeClass('focus'); }, /** @@ -842,9 +855,9 @@ get_first_row: function() { if (this.rowcount) { - var i, len, uid, rows = this.tbody.childNodes; + var i, uid, rows = this.tbody.childNodes; - for (i=0, len=rows.length-1; i<len; i++) + for (i=0; i<rows.length; i++) if (rows[i].id && (uid = this.get_row_uid(rows[i]))) return uid; } @@ -887,9 +900,10 @@ */ select_row: function(id, mod_key, with_mouse) { - var select_before = this.selection.join(','); + var select_before = this.selection.join(','), + in_selection_before = this.in_selection(id); - if (!this.multiselect) + if (!this.multiselect && with_mouse) mod_key = 0; if (!this.shift_start) @@ -925,20 +939,26 @@ this.multi_selecting = true; } - // trigger event if selection changed - if (this.selection.join(',') != select_before) - this.triggerEvent('select'); - - if (this.last_selected != 0 && this.rows[this.last_selected]) - $(this.rows[this.last_selected].obj).removeClass('focused'); + if (this.last_selected != 0 && this.rows[this.last_selected]) { + $(this.rows[this.last_selected].obj).removeClass('focused') + .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex'); + } // unselect if toggleselect is active and the same row was clicked again - if (this.toggleselect && this.last_selected == id) { + if (this.toggleselect && in_selection_before) { this.clear_selection(); - id = null; } - else + // trigger event if selection changed + else if (this.selection.join(',') != select_before) { + this.triggerEvent('select'); + } + + if (this.rows[id]) { $(this.rows[id].obj).addClass('focused'); + // set cursor focus to link inside selected row + if (this.focused) + this.focus_noscroll($(this.rows[id].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0')); + } if (!this.selection.length) this.shift_start = null; @@ -1086,7 +1106,7 @@ this.highlight_row(n, true, true); } else { - $(this.rows[n].obj).removeClass('selected').removeClass('unfocused'); + $(this.rows[n].obj).removeClass('selected').removeAttr('aria-selected'); } } @@ -1143,7 +1163,7 @@ else { for (n in this.selection) if (this.rows[this.selection[n]]) { - $(this.rows[this.selection[n]].obj).removeClass('selected').removeClass('unfocused'); + $(this.rows[this.selection[n]].obj).removeClass('selected').removeAttr('aria-selected'); } this.selection = []; @@ -1206,13 +1226,13 @@ if (this.selection.length > 1 || !this.in_selection(id)) { this.clear_selection(null, true); this.selection[0] = id; - $(this.rows[id].obj).addClass('selected'); + $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true'); } } else { if (!this.in_selection(id)) { // select row this.selection.push(id); - $(this.rows[id].obj).addClass('selected'); + $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true'); if (!norecur && !this.rows[id].expanded) this.highlight_children(id, true); } @@ -1222,7 +1242,7 @@ a_post = this.selection.slice(p+1, this.selection.length); this.selection = a_pre.concat(a_post); - $(this.rows[id].obj).removeClass('selected').removeClass('unfocused'); + $(this.rows[id].obj).removeClass('selected').removeAttr('aria-selected'); if (!norecur && !this.rows[id].expanded) this.highlight_children(id, false); } @@ -1301,6 +1321,14 @@ } return rcube_event.cancel(e); + + case 9: // Tab + this.blur(); + break; + + case 13: // Enter + if (!this.selection.length) + this.select_row(this.last_selected, mod_key, false); default: this.key_pressed = keyCode; @@ -1766,3 +1794,6 @@ rcube_list_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; rcube_list_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; rcube_list_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent; + +// static +rcube_list_widget._instances = []; -- Gitblit v1.9.1