From e3f84735d7bc5548ab09dfa7cab26faeec21c7ed Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 13 May 2014 17:48:46 +0200 Subject: Extend treelist.js with search function and saving collapse/expand states in localStorage --- program/js/treelist.js | 196 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 191 insertions(+), 5 deletions(-) (limited to 'program/js/treelist.js') diff --git a/program/js/treelist.js b/program/js/treelist.js index b5629a658..a861b4987 100644 --- a/program/js/treelist.js +++ b/program/js/treelist.js @@ -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) + $('
  • ') + .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 = $('
  • ') + .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) $('
     
    ').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 /** -- cgit v1.2.3