|
|
|
@ -44,6 +44,7 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
scroll_delay: 500,
|
|
|
|
|
scroll_step: 5,
|
|
|
|
|
scroll_speed: 20,
|
|
|
|
|
save_state: false,
|
|
|
|
|
check_droptarget: function(node){ return !node.virtual }
|
|
|
|
|
}, p || {});
|
|
|
|
|
|
|
|
|
@ -52,6 +53,8 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
indexbyid = {},
|
|
|
|
|
selection = null,
|
|
|
|
|
drag_active = false,
|
|
|
|
|
search_active = false,
|
|
|
|
|
last_search = '',
|
|
|
|
|
box_coords = {},
|
|
|
|
|
item_coords = [],
|
|
|
|
|
autoexpand_timer,
|
|
|
|
@ -59,6 +62,9 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
body_scroll_top = 0,
|
|
|
|
|
list_scroll_top = 0,
|
|
|
|
|
scroll_timer,
|
|
|
|
|
searchfield,
|
|
|
|
|
tree_state,
|
|
|
|
|
list_id = (container.attr('id') || id_prefix || '0'),
|
|
|
|
|
me = this;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -105,6 +111,43 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// activate search function
|
|
|
|
|
if (p.searchbox) {
|
|
|
|
|
searchfield = $(p.searchbox).on('keyup', function(e) {
|
|
|
|
|
var key = rcube_event.get_keycode(e),
|
|
|
|
|
mod = rcube_event.get_modifier(e);
|
|
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
|
case 9: // tab
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 13: // enter
|
|
|
|
|
search(this.value, true);
|
|
|
|
|
return rcube_event.cancel(e);
|
|
|
|
|
|
|
|
|
|
case 27: // escape
|
|
|
|
|
reset_search();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 38: // arrow up
|
|
|
|
|
case 37: // left
|
|
|
|
|
case 39: // right
|
|
|
|
|
case 40: // arrow down
|
|
|
|
|
return; // ignore arrow keys
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
search(this.value, false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}).attr('autocomplete', 'off');
|
|
|
|
|
|
|
|
|
|
// find the reset button for this search field
|
|
|
|
|
searchfield.parent().find('a.reset').click(function(e) {
|
|
|
|
|
reset_search();
|
|
|
|
|
return false;
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////// private methods
|
|
|
|
|
|
|
|
|
@ -126,6 +169,7 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
me.triggerEvent(node.collapsed ? 'collapse' : 'expand', node);
|
|
|
|
|
save_state(id, node.collapsed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -191,13 +235,21 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
function insert(node, parent_id, sort)
|
|
|
|
|
{
|
|
|
|
|
var li, parent_li,
|
|
|
|
|
parent_node = parent_id ? indexbyid[parent_id] : null;
|
|
|
|
|
parent_node = parent_id ? indexbyid[parent_id] : null
|
|
|
|
|
search_ = search_active;
|
|
|
|
|
|
|
|
|
|
// apply saved state
|
|
|
|
|
state = get_state(node.id, node.collapsed);
|
|
|
|
|
if (state !== undefined) {
|
|
|
|
|
node.collapsed = state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// insert as child of an existing node
|
|
|
|
|
if (parent_node) {
|
|
|
|
|
if (!parent_node.children)
|
|
|
|
|
parent_node.children = [];
|
|
|
|
|
|
|
|
|
|
search_active = false;
|
|
|
|
|
parent_node.children.push(node);
|
|
|
|
|
parent_li = id2dom(parent_id);
|
|
|
|
|
|
|
|
|
@ -210,6 +262,19 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
// append new node to parent's child list
|
|
|
|
|
li = render_node(node, parent_li.children('ul').first());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// list is in search mode
|
|
|
|
|
if (search_) {
|
|
|
|
|
search_active = search_;
|
|
|
|
|
|
|
|
|
|
// add clone to current search results (top level)
|
|
|
|
|
$('<li>')
|
|
|
|
|
.attr('id', li.attr('id') + '--xsR')
|
|
|
|
|
.attr('class', li.attr('class'))
|
|
|
|
|
.addClass('searchresult__')
|
|
|
|
|
.append(li.children().first().clone(true, true))
|
|
|
|
|
.appendTo(container);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// insert at top level
|
|
|
|
|
else {
|
|
|
|
@ -335,6 +400,80 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
drag_active = false;
|
|
|
|
|
|
|
|
|
|
container.html('');
|
|
|
|
|
|
|
|
|
|
reset_search();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
function search(q, enter)
|
|
|
|
|
{
|
|
|
|
|
q = String(q).toLowerCase();
|
|
|
|
|
|
|
|
|
|
if (!q.length)
|
|
|
|
|
return reset_search();
|
|
|
|
|
else if (q == last_search && !enter)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
var hits = [];
|
|
|
|
|
var search_tree = function(items) {
|
|
|
|
|
$.each(items, function(i, node) {
|
|
|
|
|
var li, sli;
|
|
|
|
|
if (!node.virtual && !node.deleted && String(node.text).toLowerCase().indexOf(q) >= 0) {
|
|
|
|
|
li = id2dom(node.id);
|
|
|
|
|
sli = $('<li>')
|
|
|
|
|
.attr('id', li.attr('id') + '--xsR')
|
|
|
|
|
.attr('class', li.attr('class'))
|
|
|
|
|
.addClass('searchresult__')
|
|
|
|
|
.append(li.children().first().clone(true, true))
|
|
|
|
|
.appendTo(container);
|
|
|
|
|
hits.push(node.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.children && node.children.length) {
|
|
|
|
|
search_tree(node.children);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// reset old search results
|
|
|
|
|
if (search_active) {
|
|
|
|
|
$(container).children('li.searchresult__').remove();
|
|
|
|
|
search_active = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// hide all list items
|
|
|
|
|
$(container).children('li').hide().removeClass('selected');
|
|
|
|
|
|
|
|
|
|
// search recursively in tree (to keep sorting order)
|
|
|
|
|
search_tree(data);
|
|
|
|
|
search_active = true;
|
|
|
|
|
last_search = q;
|
|
|
|
|
|
|
|
|
|
me.triggerEvent('search', { query: q, last: last_search, count: hits.length, ids: hits, execute: enter||false });
|
|
|
|
|
|
|
|
|
|
return hits.count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
function reset_search()
|
|
|
|
|
{
|
|
|
|
|
if (searchfield)
|
|
|
|
|
searchfield.val('');
|
|
|
|
|
|
|
|
|
|
$(container).children('li.searchresult__').remove();
|
|
|
|
|
$(container).children('li').show();
|
|
|
|
|
|
|
|
|
|
search_active = false;
|
|
|
|
|
|
|
|
|
|
me.triggerEvent('search', { query: false, last: last_search });
|
|
|
|
|
last_search = '';
|
|
|
|
|
|
|
|
|
|
if (selection)
|
|
|
|
|
select(selection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -378,6 +517,9 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
else if (typeof node.html == 'object')
|
|
|
|
|
li.append(node.html);
|
|
|
|
|
|
|
|
|
|
if (!node.text)
|
|
|
|
|
node.text = li.children().first().text();
|
|
|
|
|
|
|
|
|
|
if (node.virtual)
|
|
|
|
|
li.addClass('virtual');
|
|
|
|
|
if (node.id == selection)
|
|
|
|
@ -406,12 +548,13 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
{
|
|
|
|
|
var result = [];
|
|
|
|
|
ul.children('li').each(function(i,e){
|
|
|
|
|
var li = $(e), sublist = li.children('ul');
|
|
|
|
|
var state, li = $(e), sublist = li.children('ul');
|
|
|
|
|
var node = {
|
|
|
|
|
id: dom2id(li),
|
|
|
|
|
classes: String(li.attr('class')).split(' '),
|
|
|
|
|
virtual: li.hasClass('virtual'),
|
|
|
|
|
html: li.children().first().get(0).outerHTML,
|
|
|
|
|
text: li.children().first().text(),
|
|
|
|
|
children: walk_list(sublist)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -420,6 +563,14 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
}
|
|
|
|
|
if (node.children.length) {
|
|
|
|
|
node.collapsed = sublist.css('display') == 'none';
|
|
|
|
|
|
|
|
|
|
// apply saved state
|
|
|
|
|
state = get_state(node.id, node.collapsed);
|
|
|
|
|
if (state !== undefined) {
|
|
|
|
|
node.collapsed = state;
|
|
|
|
|
sublist[(state?'hide':'show')]();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!li.children('div.treetoggle').length)
|
|
|
|
|
$('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '"> </div>').appendTo(li);
|
|
|
|
|
}
|
|
|
|
@ -452,7 +603,7 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
*/
|
|
|
|
|
function dom2id(li)
|
|
|
|
|
{
|
|
|
|
|
var domid = li.attr('id').replace(new RegExp('^' + (p.id_prefix) || '%'), '');
|
|
|
|
|
var domid = li.attr('id').replace(new RegExp('^' + (p.id_prefix) || '%'), '').replace(/--xsR$/, '');
|
|
|
|
|
return p.id_decode ? p.id_decode(domid) : domid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -461,8 +612,9 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
*/
|
|
|
|
|
function id2dom(id)
|
|
|
|
|
{
|
|
|
|
|
var domid = p.id_encode ? p.id_encode(id) : id;
|
|
|
|
|
return $('#' + p.id_prefix + domid);
|
|
|
|
|
var domid = p.id_encode ? p.id_encode(id) : id,
|
|
|
|
|
suffix = search_active ? '--xsR' : '';
|
|
|
|
|
return $('#' + p.id_prefix + domid + suffix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -478,6 +630,40 @@ function rcube_treelist_widget(node, p)
|
|
|
|
|
scroller.scrollTop(rel_offset + current_offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Save node collapse state to localStorage
|
|
|
|
|
*/
|
|
|
|
|
function save_state(id, collapsed)
|
|
|
|
|
{
|
|
|
|
|
if (p.save_state && window.rcmail) {
|
|
|
|
|
var key = 'treelist-' + list_id;
|
|
|
|
|
if (!tree_state) {
|
|
|
|
|
tree_state = rcmail.local_storage_get_item(key, {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tree_state[id] != collapsed) {
|
|
|
|
|
tree_state[id] = collapsed;
|
|
|
|
|
rcmail.local_storage_set_item(key, tree_state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Read node collapse state from localStorage
|
|
|
|
|
*/
|
|
|
|
|
function get_state(id)
|
|
|
|
|
{
|
|
|
|
|
if (p.save_state && window.rcmail) {
|
|
|
|
|
if (!tree_state) {
|
|
|
|
|
tree_state = rcmail.local_storage_get_item('treelist-' + list_id, {});
|
|
|
|
|
}
|
|
|
|
|
return tree_state[id];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///// drag & drop support
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|