From ea0866a1adc9239b8b115ab2490e1dd88f3c64ec Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Wed, 07 May 2014 14:04:13 -0400 Subject: [PATCH] Improve keyboard navigation on compose screen: define tabindex groups + enable keyboard controls of contacts list widget --- program/js/list.js | 126 +++++++++++++++++++++++++++++++---------- 1 files changed, 95 insertions(+), 31 deletions(-) diff --git a/program/js/list.js b/program/js/list.js index 9b7779c..6a0c5b3 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -1,21 +1,35 @@ -/* - +-----------------------------------------------------------------------+ - | Roundcube List Widget | - | | - | This file is part of the Roundcube Webmail client | - | Copyright (C) 2006-2013, The Roundcube Dev Team | - | | - | Licensed under the GNU General Public License version 3 or | - | any later version with exceptions for skins & plugins. | - | See the README file for a full license statement. | - | | - +-----------------------------------------------------------------------+ - | Authors: Thomas Bruederli <roundcube@gmail.com> | - | Charles McNulty <charles@charlesmcnulty.com> | - +-----------------------------------------------------------------------+ - | Requires: common.js | - +-----------------------------------------------------------------------+ -*/ +/** + * Roundcube List Widget + * + * This file is part of the Roundcube Webmail client + * + * @licstart The following is the entire license notice for the + * JavaScript code in this file. + * + * Copyright (c) 2005-2014, The Roundcube Dev Team + * + * The JavaScript code in this page is free software: you can + * redistribute it and/or modify it under the terms of the GNU + * General Public License (GNU GPL) as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. The code is distributed WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + * + * As additional permission under GNU GPL version 3 section 7, you + * may distribute non-source (e.g., minimized or compacted) forms of + * that code without the copy of the GNU GPL normally required by + * section 4, provided you include this license notice and a URL + * through which recipients can access the Corresponding Source. + * + * @licend The above is the entire license notice + * for the JavaScript code in this file. + * + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Charles McNulty <charles@charlesmcnulty.com> + * + * @requires jquery.js, common.js + */ /** @@ -84,7 +98,7 @@ this.rows = {}; this.rowcount = 0; - var r, len, rows = this.tbody.childNodes; + var r, len, rows = this.tbody.childNodes, me = this; for (r=0, len=rows.length; r<len; r++) { this.rowcount += this.init_row(rows[r]) ? 1 : 0; @@ -94,8 +108,19 @@ this.frame = this.list.parentNode; // set body events - if (this.keyboard) + 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:absolute; top:-1000px') + .html('Select List') + .insertAfter(this.list) + .on('focus', function(e){ me.focus(e); }) + .on('blur', function(e){ me.blur(e); }); + } } return this; @@ -161,9 +186,9 @@ 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', ''); // remove fixed widths + $(this.list.tHead).find('tr td').attr('style', '').find('a.sortcol').attr('tabindex', '-1'); // remove fixed widths } - else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0) { + else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0 && 0) { this.init_fixed_header(); } @@ -205,6 +230,12 @@ else { $(this.fixed_header).find('thead').replaceWith(clone); } + + // avoid scrolling header links being focused + $(this.list.tHead).find('a.sortcol').attr('tabindex', '-1'); + + // set tabindex to fixed header sort links + clone.find('a.sortcol').attr('tabindex', '0'); this.thead = clone.get(0); this.resize(); @@ -251,6 +282,8 @@ if (sel) this.clear_selection(); + else + this.last_selected = 0; // reset scroll position (in Opera) if (this.frame) @@ -301,11 +334,13 @@ if (row.style) $.extend(domrow.style, row.style); if (row.uid) $(domrow).data('uid', row.uid); - for (var domcell, col, i=0; row.cols && i < row.cols.length; i++) { + for (var e, domcell, col, i=0; row.cols && i < row.cols.length; i++) { col = row.cols[i]; domcell = document.createElement(this.col_tagname()); if (col.className) domcell.className = col.className; if (col.innerHTML) domcell.innerHTML = col.innerHTML; + for (e in col.events) + domcell['on' + e] = col.events[e]; domrow.appendChild(domcell); } @@ -354,6 +389,9 @@ */ focus: function(e) { + if (this.focused) + return; + var n, id; this.focused = true; @@ -364,20 +402,32 @@ } } - // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) - // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) - $('iframe,:focus:not(body)').blur(); - window.focus(); - - if (e || (e = window.event)) + if (e) rcube_event.cancel(e); + + // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) + if (this.focus_elem) { + // We now fix this by explicitly assigning focus to a dedicated link element + this.focus_elem.focus(); + } + else { + // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) + $('iframe,:focus:not(body)').blur(); + window.focus(); + } + + $(this.list).addClass('focus'); + + // set internal focus pointer to first row + if (!this.last_selected) + this.select_first(CONTROL_KEY); }, /** * remove focus from the list */ -blur: function() +blur: function(e) { var n, id; this.focused = false; @@ -387,6 +437,8 @@ $(this.rows[id].obj).removeClass('selected focused').addClass('unfocused'); } } + + $(this.list).removeClass('focus'); }, @@ -506,6 +558,8 @@ } this.rows[id].clicked = now; + this.focus(); + return false; }, @@ -1085,8 +1139,10 @@ this.selection = []; } - if (num_select && !this.selection.length && !no_event) + if (num_select && !this.selection.length && !no_event) { this.triggerEvent('select'); + this.last_selected = 0; + } }, @@ -1295,9 +1351,17 @@ } if (new_row) { + // simulate ctr-key if no rows are selected + if (!mod_key && !this.selection.length) + mod_key = CONTROL_KEY; + this.select_row(new_row.uid, mod_key, false); this.scrollto(new_row.uid); } + else if (!new_row && !selected_row) { + // select the first row if none selected yet + this.select_first(CONTROL_KEY); + } return false; }, -- Gitblit v1.9.1