diff options
-rw-r--r-- | program/js/app.js | 107 | ||||
-rw-r--r-- | program/js/treelist.js | 162 |
2 files changed, 178 insertions, 91 deletions
diff --git a/program/js/app.js b/program/js/app.js index dbb65eea4..7c6dba8c0 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -472,6 +472,7 @@ function rcube_webmail() }); this.treelist.addEventListener('collapse', function(node){ ref.folder_collapsed(node) }); this.treelist.addEventListener('expand', function(node){ ref.folder_collapsed(node) }); + this.treelist.addEventListener('select', function(node){ ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) }); } } @@ -4416,11 +4417,8 @@ function rcube_webmail() // callback from server upon group-delete command this.remove_group_item = function(prop) { - var li, key = 'G'+prop.source+prop.id; - if ((li = this.get_folder_li(key,'',true))) { - this.triggerEvent('group_delete', { source:prop.source, id:prop.id, li:li }); - - li.parentNode.removeChild(li); + var key = 'G'+prop.source+prop.id; + if (this.treelist.remove(key)) { delete this.env.contactfolders[key]; delete this.env.contactgroups[key]; } @@ -4524,14 +4522,12 @@ function rcube_webmail() link = $('<a>').attr('href', '#') .attr('rel', prop.source+':'+prop.id) .click(function() { return rcmail.command('listgroup', prop, this); }) - .html(prop.name), - li = $('<li>').attr({id: 'rcmli'+this.html_identifier(key), 'class': 'contactgroup'}) - .append(link); + .html(prop.name); this.env.contactfolders[key] = this.env.contactgroups[key] = prop; - this.add_contact_group_row(prop, li); + this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, true); - this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:li[0] }); + this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key) }); }; // callback for renaming a contact group @@ -4540,15 +4536,13 @@ function rcube_webmail() this.reset_add_input(); var key = 'G'+prop.source+prop.id, - li = this.get_folder_li(key,'',true), - link; + newnode = {}; // group ID has changed, replace link node and identifiers - if (li && prop.newid) { + if (prop.newid) { var newkey = 'G'+prop.source+prop.newid, - newprop = $.extend({}, prop);; + newprop = $.extend({}, prop); - li.id = 'rcmli' + this.html_identifier(newkey); this.env.contactfolders[newkey] = this.env.contactfolders[key]; this.env.contactfolders[newkey].id = prop.newid; this.env.group = prop.newid; @@ -4559,45 +4553,22 @@ function rcube_webmail() newprop.id = prop.newid; newprop.type = 'group'; - link = $('<a>').attr('href', '#') + newnode.id = newkey; + newnode.html = $('<a>').attr('href', '#') .attr('rel', prop.source+':'+prop.newid) .click(function() { return rcmail.command('listgroup', newprop, this); }) .html(prop.name); - $(li).children().replaceWith(link); } // update displayed group name - else if (li && (link = li.firstChild) && link.tagName.toLowerCase() == 'a') - link.innerHTML = prop.name; - - this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name; - this.add_contact_group_row(prop, $(li), true); - - this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid }); - }; - - // add contact group row to the list, with sorting - this.add_contact_group_row = function(prop, li, reloc) - { - var row, name = prop.name.toUpperCase(), - sibling = this.get_folder_li(prop.source,'',true), - prefix = 'rcmli' + this.html_identifier('G'+prop.source, true); - - // When renaming groups, we need to remove it from DOM and insert it in the proper place - if (reloc) { - row = li.clone(true); - li.remove(); + else { + $(this.treelist.get_item(key)).children().first().html(prop.name); + this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name; } - else - row = li; - $('li[id^="'+prefix+'"]', this.gui_objects.folderlist).each(function(i, elem) { - if (name >= $(this).text().toUpperCase()) - sibling = elem; - else - return false; - }); + // update list node and re-sort it + this.treelist.update(key, newnode, true); - row.insertAfter(sibling); + this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key), newid:prop.newid }); }; this.update_group_commands = function() @@ -4829,11 +4800,9 @@ function rcube_webmail() .attr('rel', id) .click(function() { return rcmail.command('listsearch', id, this); }) .html(name), - li = $('<li>').attr({ id:'rcmli' + this.html_identifier(key,true), 'class':'contactsearch' }) - .append(link), - prop = {name:name, id:id, li:li[0]}; + prop = { name:name, id:id }; - this.add_saved_search_row(prop, li); + this.treelist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch'); this.select_folder(key,'',true); this.enable_command('search-delete', true); this.env.search_id = id; @@ -4841,35 +4810,6 @@ function rcube_webmail() this.triggerEvent('abook_search_insert', prop); }; - // add saved search row to the list, with sorting - this.add_saved_search_row = function(prop, li, reloc) - { - var row, sibling, name = prop.name.toUpperCase(); - - // When renaming groups, we need to remove it from DOM and insert it in the proper place - if (reloc) { - row = li.clone(true); - li.remove(); - } - else - row = li; - - $('li[class~="contactsearch"]', this.gui_objects.folderlist).each(function(i, elem) { - if (!sibling) - sibling = this.previousSibling; - - if (name >= $(this).text().toUpperCase()) - sibling = elem; - else - return false; - }); - - if (sibling) - row.insertAfter(sibling); - else - row.appendTo(this.gui_objects.folderlist); - }; - // creates an input for saved search name this.search_create = function() { @@ -4888,10 +4828,8 @@ function rcube_webmail() this.remove_search_item = function(id) { var li, key = 'S'+id; - if ((li = this.get_folder_li(key,'',true))) { + if (this.treelist.remove(key)) { this.triggerEvent('search_delete', { id:id, li:li }); - - li.parentNode.removeChild(li); } this.env.search_id = null; @@ -5716,7 +5654,10 @@ function rcube_webmail() // mark a mailbox as selected and set environment variable this.select_folder = function(name, prefix, encode) { - if (this.gui_objects.folderlist) { + if (this.treelist) { + this.treelist.select(name); + } + else if (this.gui_objects.folderlist) { var current_li, target_li; if ((current_li = $('li.selected', this.gui_objects.folderlist))) { diff --git a/program/js/treelist.js b/program/js/treelist.js index 47ac0c1a0..4beaadab9 100644 --- a/program/js/treelist.js +++ b/program/js/treelist.js @@ -55,6 +55,11 @@ function rcube_treelist_widget(node, p) 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) @@ -68,8 +73,7 @@ function rcube_treelist_widget(node, p) } // load data from DOM else { - data = walk_list(container); - // console.log(data); + update_data(); } // register click handlers on list @@ -170,6 +174,131 @@ function rcube_treelist_widget(node, p) } /** + * 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 { + 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) @@ -202,12 +331,26 @@ function rcube_treelist_widget(node, p) /** * Render a specific node into the DOM list */ - function render_node(node, parent) + function render_node(node, parent, replace) { - var li = $('<li>' + node.html + '</li>') - .attr('id', p.id_prefix + node.id) - .addClass((node.classes || []).join(' ')) - .appendTo(parent); + 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'); @@ -217,7 +360,7 @@ function rcube_treelist_widget(node, p) // 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); + var ul = $('<ul>').appendTo(li).attr('class', node.childlistclass); if (node.collapsed) ul.hide(); @@ -225,6 +368,8 @@ function rcube_treelist_widget(node, p) render_node(node.children[i], ul); } } + + return li; } /** @@ -245,6 +390,7 @@ function rcube_treelist_widget(node, p) } if (node.children.length) { + node.childlistclass = li.children('ul').attr('class'); node.collapsed = li.children('ul').css('display') == 'none'; } if (li.hasClass('selected')) { |