| | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | 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 |
| | | */ |
| | | |
| | | |
| | | /** |
| | |
| | | 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; |
| | |
| | | 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:fixed; top:-1000px') |
| | | .html($(this.list).attr('summary') || 'Select List') |
| | | .insertAfter(this.list) |
| | | .on('focus', function(e){ me.focus(e); }) |
| | | .on('blur', function(e){ me.blur(e); }); |
| | | } |
| | | } |
| | | |
| | | return this; |
| | |
| | | |
| | | 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').attr('tabindex', '-1'); // remove fixed widths |
| | | } |
| | | else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0) { |
| | | this.init_fixed_header(); |
| | |
| | | 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>'); |
| | |
| | | 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(); |
| | |
| | | |
| | | if (sel) |
| | | this.clear_selection(); |
| | | else |
| | | this.last_selected = 0; |
| | | |
| | | // reset scroll position (in Opera) |
| | | if (this.frame) |
| | |
| | | */ |
| | | focus: function(e) |
| | | { |
| | | var n, id; |
| | | if (this.focused) |
| | | return; |
| | | |
| | | 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); |
| | | |
| | | // 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 (this.focus_elem) { |
| | | // We now fix this by explicitly assigning focus to a dedicated link element |
| | | this.focus_noscroll(this.focus_elem); |
| | | } |
| | | else { |
| | | // 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)) |
| | | rcube_event.cancel(e); |
| | | $(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; |
| | | 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'); |
| | | } |
| | | } |
| | | $(this.list).removeClass('focus'); |
| | | }, |
| | | |
| | | /** |
| | | * Focus the given element without scrolling the list container |
| | | */ |
| | | focus_noscroll: function(elem) |
| | | { |
| | | var y = this.frame.scrollTop || this.frame.scrollY; |
| | | elem.focus(); |
| | | this.frame.scrollTop = y; |
| | | }, |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | this.rows[id].clicked = now; |
| | | this.focus(); |
| | | |
| | | return false; |
| | | }, |
| | | |
| | |
| | | this.highlight_row(n, true, true); |
| | | } |
| | | else { |
| | | $(this.rows[n].obj).removeClass('selected').removeClass('unfocused'); |
| | | $(this.rows[n].obj).removeClass('selected').removeAttr('aria-selected'); |
| | | } |
| | | } |
| | | |
| | |
| | | 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 = []; |
| | | } |
| | | |
| | | if (num_select && !this.selection.length && !no_event) |
| | | if (num_select && !this.selection.length && !no_event) { |
| | | this.triggerEvent('select'); |
| | | this.last_selected = 0; |
| | | } |
| | | }, |
| | | |
| | | |
| | |
| | | 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); |
| | | } |
| | |
| | | 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); |
| | | } |
| | |
| | | } |
| | | |
| | | 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; |
| | | }, |