Thomas Bruederli
2014-05-13 e3f84735d7bc5548ab09dfa7cab26faeec21c7ed
Extend treelist.js with search function and saving collapse/expand states in localStorage
1 files modified
196 ■■■■■ changed files
program/js/treelist.js 196 ●●●●● patch | view | raw | blame | history
program/js/treelist.js
@@ -44,6 +44,7 @@
    scroll_delay: 500,
    scroll_step: 5,
    scroll_speed: 20,
    save_state: false,
    check_droptarget: function(node){ return !node.virtual }
  }, p || {});
@@ -52,6 +53,8 @@
    indexbyid = {},
    selection = null,
    drag_active = false,
    search_active = false,
    last_search = '',
    box_coords = {},
    item_coords = [],
    autoexpand_timer,
@@ -59,6 +62,9 @@
    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 @@
    }
  });
  // 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 @@
      }
      me.triggerEvent(node.collapsed ? 'collapse' : 'expand', node);
      save_state(id, node.collapsed);
    }
  }
@@ -191,13 +235,21 @@
  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);
@@ -209,6 +261,19 @@
      else {
        // 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
@@ -335,6 +400,80 @@
    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 @@
    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 @@
  {
    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 @@
      }
      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') + '">&nbsp;</div>').appendTo(li);
      }
@@ -452,7 +603,7 @@
   */
  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 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 @@
      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
  /**