| | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | Roundcube Treelist widget | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 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> | |
| | | +-----------------------------------------------------------------------+ |
| | | | Requires: common.js | |
| | | +-----------------------------------------------------------------------+ |
| | | */ |
| | | /** |
| | | * Roundcube Treelist 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) 2013-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> |
| | | * @requires jquery.js, common.js |
| | | */ |
| | | |
| | | |
| | | /** |
| | |
| | | indexbyid = {}, |
| | | selection = null, |
| | | drag_active = false, |
| | | has_focus = false, |
| | | box_coords = {}, |
| | | item_coords = [], |
| | | autoexpand_timer, |
| | |
| | | } |
| | | }); |
| | | |
| | | container.on('focusin', function(e){ |
| | | // TODO: only accept focus on virtual nodes from keyboard events |
| | | has_focus = true; |
| | | }) |
| | | .on('focusout', function(e){ |
| | | has_focus = false; |
| | | }); |
| | | |
| | | container.attr('role', 'tree'); |
| | | |
| | | $(document.body) |
| | | .bind('keydown', keypress); |
| | | |
| | | |
| | | /////// private methods |
| | | |
| | |
| | | function select(id) |
| | | { |
| | | if (selection) { |
| | | id2dom(selection).removeClass('selected'); |
| | | id2dom(selection).removeClass('selected').removeAttr('aria-selected'); |
| | | selection = null; |
| | | } |
| | | |
| | | var li = id2dom(id); |
| | | if (li.length) { |
| | | li.addClass('selected'); |
| | | li.addClass('selected').attr('aria-selected', 'true'); |
| | | selection = id; |
| | | // TODO: expand all parent nodes if collapsed |
| | | scroll_to_node(li); |
| | |
| | | */ |
| | | function update_data() |
| | | { |
| | | data = walk_list(container); |
| | | data = walk_list(container, 0); |
| | | } |
| | | |
| | | /** |
| | |
| | | function update_dom(node) |
| | | { |
| | | var li = id2dom(node.id); |
| | | li.attr('aria-expanded', node.collapsed ? 'false' : 'true'); |
| | | li.children('ul').first()[(node.collapsed ? 'hide' : 'show')](); |
| | | li.children('div.treetoggle').removeClass('collapsed expanded').addClass(node.collapsed ? 'collapsed' : 'expanded'); |
| | | me.triggerEvent('toggle', node); |
| | |
| | | |
| | | var li = $('<li>') |
| | | .attr('id', p.id_prefix + (p.id_encode ? p.id_encode(node.id) : node.id)) |
| | | .attr('role', 'treeitem') |
| | | .addClass((node.classes || []).join(' ')); |
| | | |
| | | if (replace) |
| | |
| | | |
| | | // add child list and toggle icon |
| | | if (node.children && node.children.length) { |
| | | li.attr('aria-expanded', node.collapsed ? 'false' : 'true'); |
| | | $('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '"> </div>').appendTo(li); |
| | | var ul = $('<ul>').appendTo(li).attr('class', node.childlistclass); |
| | | var ul = $('<ul>').appendTo(li).attr('class', node.childlistclass).attr('role', 'group'); |
| | | if (node.collapsed) |
| | | ul.hide(); |
| | | |
| | |
| | | * Recursively walk the DOM tree and build an internal data structure |
| | | * representing the skeleton of this tree list. |
| | | */ |
| | | function walk_list(ul) |
| | | function walk_list(ul, level) |
| | | { |
| | | var result = []; |
| | | ul.children('li').each(function(i,e){ |
| | |
| | | classes: li.attr('class').split(' '), |
| | | virtual: li.hasClass('virtual'), |
| | | html: li.children().first().get(0).outerHTML, |
| | | children: walk_list(sublist) |
| | | children: walk_list(sublist, level+1) |
| | | } |
| | | |
| | | if (sublist.length) { |
| | |
| | | } |
| | | if (node.children.length) { |
| | | node.collapsed = sublist.css('display') == 'none'; |
| | | li.attr('aria-expanded', node.collapsed ? 'false' : 'true'); |
| | | } |
| | | if (li.hasClass('selected')) { |
| | | li.attr('aria-selected', 'true'); |
| | | selection = node.id; |
| | | } |
| | | |
| | | // declare list item as treeitem |
| | | li.attr('role', 'treeitem'); |
| | | |
| | | // allow virtual nodes to receive focus |
| | | if (node.virtual) { |
| | | li.children('a:first').attr('tabindex', '0'); |
| | | } |
| | | |
| | | result.push(node); |
| | | indexbyid[node.id] = node; |
| | | }) |
| | | }); |
| | | |
| | | ul.attr('role', level == 0 ? 'tree' : 'group'); |
| | | |
| | | return result; |
| | | } |
| | |
| | | scroller.scrollTop(rel_offset + current_offset); |
| | | } |
| | | |
| | | /** |
| | | * Handler for keyboard events on treelist |
| | | */ |
| | | function keypress(e) |
| | | { |
| | | var target = e.target || {}, |
| | | keyCode = rcube_event.get_keycode(e); |
| | | |
| | | if (!has_focus || target.nodeName == 'INPUT' || target.nodeName == 'TEXTAREA' || target.nodeName == 'SELECT') |
| | | return true; |
| | | |
| | | switch (keyCode) { |
| | | case 38: |
| | | case 40: |
| | | case 63232: // 'up', in safari keypress |
| | | case 63233: // 'down', in safari keypress |
| | | var li = container.find(':focus').closest('li'); |
| | | if (li.length) { |
| | | focus_next(li, (mod = keyCode == 38 || keyCode == 63232 ? -1 : 1)); |
| | | } |
| | | break; |
| | | |
| | | case 37: // Left arrow key |
| | | case 39: // Right arrow key |
| | | var id, node, li = container.find(':focus').closest('li'); |
| | | if (li.length) { |
| | | id = dom2id(li); |
| | | node = indexbyid[id]; |
| | | if (node && node.children.length) |
| | | toggle(id, rcube_event.get_modifier(e) == SHIFT_KEY); // toggle subtree |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | function focus_next(li, dir, from_child) |
| | | { |
| | | var mod = dir < 0 ? 'prev' : 'next', |
| | | next = li[mod](), limit, parent; |
| | | |
| | | if (dir > 0 && !from_child && li.children('ul[role=tree]:visible').length) { |
| | | li.children('ul').children('li:first').children('a:first').focus(); |
| | | } |
| | | else if (dir < 0 && !from_child && next.children('ul[role=tree]:visible').length) { |
| | | next.children('ul').children('li:last').children('a:last').focus(); |
| | | } |
| | | else if (next.length && next.children('a:first')) { |
| | | next.children('a:first').focus(); |
| | | } |
| | | else { |
| | | parent = li.parent().closest('li[role=treeitem]'); |
| | | if (parent.length) |
| | | if (dir < 0) { |
| | | parent.children('a:first').focus(); |
| | | } |
| | | else { |
| | | focus_next(parent, dir, true); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | ///// drag & drop support |
| | | |
| | | /** |