Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F224064
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/program/js/treelist.js b/program/js/treelist.js
index d6d50e4f7..fec2d7f21 100644
--- a/program/js/treelist.js
+++ b/program/js/treelist.js
@@ -1,573 +1,577 @@
/*
+-----------------------------------------------------------------------+
| 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 class
* @contructor
*/
function rcube_treelist_widget(node, p)
{
// apply some defaults to p
p = $.extend({
id_prefix: '',
autoexpand: 1000,
selectable: false,
check_droptarget: function(node){ return !node.virtual }
}, p || {});
var container = $(node);
var data = p.data || [];
var indexbyid = {};
var selection = null;
var drag_active = false;
var box_coords = {};
var item_coords = [];
var autoexpand_timer;
var autoexpand_item;
var body_scroll_top = 0;
var list_scroll_top = 0;
var me = this;
/////// export public members and methods
this.container = container;
this.expand = expand;
this.collapse = collapse;
this.select = select;
this.render = render;
this.drag_start = drag_start;
this.drag_end = drag_end;
this.intersects = intersects;
this.update = update_node;
this.insert = insert;
this.remove = remove;
this.get_item = get_item;
this.get_selection = get_selection;
/////// startup code (constructor)
// abort if node not found
if (!container.length)
return;
if (p.data) {
index_data({ children:data });
}
// load data from DOM
else {
update_data();
}
// register click handlers on list
container.on('click', 'div.treetoggle', function(e){
toggle(dom2id($(this).parent()));
});
container.on('click', 'li', function(e){
var node = p.selectable ? indexbyid[dom2id($(this))] : null;
if (node && !node.virtual) {
select(node.id);
e.stopPropagation();
}
});
/////// private methods
/**
* Collaps a the node with the given ID
*/
function collapse(id, recursive, set)
{
var node;
if (node = indexbyid[id]) {
node.collapsed = typeof set == 'undefined' || set;
update_dom(node);
// Work around a bug in IE6 and IE7, see #1485309
if (window.bw && (bw.ie6 || bw.ie7) && node.collapsed) {
id2dom(node.id).next().children('ul:visible').hide().show();
}
if (recursive && node.children) {
for (var i=0; i < node.children.length; i++) {
collapse(node.children[i].id, recursive, set);
}
}
me.triggerEvent(node.collapsed ? 'collapse' : 'expand', node);
}
}
/**
* Expand a the node with the given ID
*/
function expand(id, recursive)
{
collapse(id, recursive, false);
}
/**
* Toggle collapsed state of a list node
*/
function toggle(id, recursive)
{
var node;
if (node = indexbyid[id]) {
collapse(id, recursive, !node.collapsed);
}
}
/**
* Select a tree node by it's ID
*/
function select(id)
{
if (selection) {
id2dom(selection).removeClass('selected');
selection = null;
}
var li = id2dom(id);
if (li.length) {
li.addClass('selected');
selection = id;
// TODO: expand all parent nodes if collapsed
scroll_to_node(li);
}
me.triggerEvent('select', indexbyid[id]);
}
/**
* Getter for the currently selected node ID
*/
function get_selection()
{
return selection;
}
/**
* Return the DOM element of the list item with the given ID
*/
function get_item(id)
{
return id2dom(id).get(0);
}
/**
* Insert the given node
*/
function insert(node, parent_id, sort)
{
var li, parent_li,
parent_node = parent_id ? indexbyid[parent_id] : null;
// insert as child of an existing node
if (parent_node) {
if (!parent_node.children)
parent_node.children = [];
parent_node.children.push(node);
parent_li = id2dom(parent_id);
// re-render the entire subtree
if (parent_node.children.length == 1) {
render_node(parent_node, parent_li.parent(), parent_li);
li = id2dom(node.id);
}
else {
// append new node to parent's child list
li = render_node(node, parent_li.children('ul').first());
}
}
// insert at top level
else {
data.push(node);
li = render_node(node, container);
}
indexbyid[node.id] = node;
if (sort) {
resort_node(li, typeof sort == 'string' ? '[class~="' + sort + '"]' : '');
}
}
/**
* Update properties of an existing node
*/
function update_node(id, updates, sort)
{
var li, node = indexbyid[id];
if (node) {
li = id2dom(id);
if (updates.id || updates.html || updates.children || updates.classes) {
$.extend(node, updates);
render_node(node, li.parent(), li);
}
if (node.id != id) {
delete indexbyid[id];
indexbyid[node.id] = node;
}
if (sort) {
resort_node(li, typeof sort == 'string' ? '[class~="' + sort + '"]' : '');
}
}
}
/**
* Helper method to sort the list of the given item
*/
function resort_node(li, filter)
{
var first, sibling,
myid = li.get(0).id,
sortname = li.children().first().text().toUpperCase();
li.parent().children('li' + filter).each(function(i, elem) {
if (i == 0)
first = elem;
if (elem.id == myid) {
// skip
}
else if (elem.id != myid && sortname >= $(elem).children().first().text().toUpperCase()) {
sibling = elem;
}
else {
return false;
}
});
if (sibling) {
li.insertAfter(sibling);
}
else if (first.id != myid) {
li.insertBefore(first);
}
// reload data from dom
update_data();
}
/**
* Remove the item with the given ID
*/
function remove(id)
{
var node, li;
if (node = indexbyid[id]) {
li = id2dom(id);
li.remove();
node.deleted = true;
delete indexbyid[id];
return true;
}
return false;
}
/**
* (Re-)read tree data from DOM
*/
function update_data()
{
data = walk_list(container);
}
/**
* Apply the 'collapsed' status of the data node to the corresponding DOM element(s)
*/
function update_dom(node)
{
var li = id2dom(node.id);
li.children('ul').first()[(node.collapsed ? 'hide' : 'show')]();
li.children('div.treetoggle').removeClass('collapsed expanded').addClass(node.collapsed ? 'collapsed' : 'expanded');
me.triggerEvent('toggle', node);
}
/**
* Render the tree list from the internal data structure
*/
function render()
{
if (me.triggerEvent('renderBefore', data) === false)
return;
// remove all child nodes
container.html('');
// render child nodes
for (var i=0; i < data.length; i++) {
render_node(data[i], container);
}
me.triggerEvent('renderAfter', container);
}
/**
* Render a specific node into the DOM list
*/
function render_node(node, parent, replace)
{
if (node.deleted)
return;
var li = $('<li>')
.attr('id', p.id_prefix + (p.id_encode ? p.id_encode(node.id) : node.id))
.addClass((node.classes || []).join(' '));
if (replace)
replace.replaceWith(li);
else
li.appendTo(parent);
if (typeof node.html == 'string') {
li.html(node.html);
}
else if (typeof node.html == 'object') {
li.append(node.html);
}
if (node.virtual)
li.addClass('virtual');
if (node.id == selection)
li.addClass('selected');
// add child list and toggle icon
if (node.children && node.children.length) {
$('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '"> </div>').appendTo(li);
var ul = $('<ul>').appendTo(li).attr('class', node.childlistclass);
if (node.collapsed)
ul.hide();
for (var i=0; i < node.children.length; i++) {
render_node(node.children[i], ul);
}
}
return li;
}
/**
* Recursively walk the DOM tree and build an internal data structure
* representing the skeleton of this tree list.
*/
function walk_list(ul)
{
var result = [];
ul.children('li').each(function(i,e){
var li = $(e), sublist = li.children('ul');
var node = {
id: dom2id(li),
classes: li.attr('class').split(' '),
virtual: li.hasClass('virtual'),
html: li.children().first().get(0).outerHTML,
children: walk_list(sublist)
}
if (sublist.length) {
node.childlistclass = sublist.attr('class');
}
if (node.children.length) {
node.collapsed = sublist.css('display') == 'none';
}
if (li.hasClass('selected')) {
selection = node.id;
}
result.push(node);
indexbyid[node.id] = node;
})
return result;
}
/**
* Recursively walk the data tree and index nodes by their ID
*/
function index_data(node)
{
if (node.id) {
indexbyid[node.id] = node;
}
for (var c=0; node.children && c < node.children.length; c++) {
index_data(node.children[c]);
}
}
/**
* Get the (stripped) node ID from the given DOM element
*/
function dom2id(li)
{
var domid = li.attr('id').replace(new RegExp('^' + (p.id_prefix) || '%'), '');
return p.id_decode ? p.id_decode(domid) : domid;
}
/**
* Get the <li> element for the given node ID
*/
function id2dom(id)
{
var domid = p.id_encode ? p.id_encode(id) : id;
return $('#' + p.id_prefix + domid);
}
/**
* Scroll the parent container to make the given list item visible
*/
function scroll_to_node(li)
{
- var scroller = container.parent();
- scroller.scrollTop(li.offset().top - scroller.offset().top + scroller.scrollTop());
+ var scroller = container.parent(),
+ current_offset = scroller.scrollTop(),
+ rel_offset = li.offset().top - scroller.offset().top;
+
+ if (rel_offset < 0 || rel_offset + li.height() > scroller.height())
+ scroller.scrollTop(rel_offset + current_offset);
}
///// drag & drop support
/**
* When dragging starts, compute absolute bounding boxes of the list and it's items
* for faster comparisons while mouse is moving
*/
function drag_start()
{
var li, item, height,
pos = container.offset();
body_scroll_top = bw.ie ? 0 : window.pageYOffset;
list_scroll_top = container.parent().scrollTop();
drag_active = true;
box_coords = {
x1: pos.left,
y1: pos.top,
x2: pos.left + container.width(),
y2: pos.top + container.height()
};
item_coords = [];
for (var id in indexbyid) {
li = id2dom(id);
item = li.children().first().get(0);
if (height = item.offsetHeight) {
pos = $(item).offset();
item_coords[id] = {
x1: pos.left,
y1: pos.top,
x2: pos.left + item.offsetWidth,
y2: pos.top + height,
on: id == autoexpand_item
};
}
}
}
/**
* Signal that dragging has stopped
*/
function drag_end()
{
drag_active = false;
if (autoexpand_timer) {
clearTimeout(autoexpand_timer);
autoexpand_timer = null;
autoexpand_item = null;
}
$('li.droptarget', container).removeClass('droptarget');
}
/**
* Determine if the given mouse coords intersect the list and one if its items
*/
function intersects(mouse, highlight)
{
// offsets to compensate for scrolling while dragging a message
var boffset = bw.ie ? -document.documentElement.scrollTop : body_scroll_top,
moffset = list_scroll_top - container.parent().scrollTop(),
result = null;
mouse.top = mouse.y + -moffset - boffset;
// no intersection with list bounding box
if (mouse.x < box_coords.x1 || mouse.x >= box_coords.x2 || mouse.top < box_coords.y1 || mouse.top >= box_coords.y2) {
// TODO: optimize performance for this operation
$('li.droptarget', container).removeClass('droptarget');
return result;
}
// check intersection with visible list items
var pos, node;
for (var id in item_coords) {
pos = item_coords[id];
if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.top >= pos.y1 && mouse.top < pos.y2) {
node = indexbyid[id];
// if the folder is collapsed, expand it after the configured time
if (node.children && node.children.length && node.collapsed && p.autoexpand && autoexpand_item != id) {
if (autoexpand_timer)
clearTimeout(autoexpand_timer);
autoexpand_item = id;
autoexpand_timer = setTimeout(function() {
expand(autoexpand_item);
drag_start(); // re-calculate item coords
autoexpand_item = null;
}, p.autoexpand);
}
else if (autoexpand_timer && autoexpand_item != id) {
clearTimeout(autoexpand_timer);
autoexpand_item = null;
autoexpand_timer = null;
}
// check if this item is accepted as drop target
if (p.check_droptarget(node)) {
if (highlight) {
id2dom(id).addClass('droptarget');
pos.on = true;
}
result = id;
}
else {
result = null;
}
}
else if (pos.on) {
id2dom(id).removeClass('droptarget');
pos.on = false;
}
}
return result;
}
}
// use event processing functions from Roundcube's rcube_event_engine
rcube_treelist_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_treelist_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
rcube_treelist_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Mar 1, 7:37 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
166264
Default Alt Text
(14 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment