summaryrefslogtreecommitdiff
path: root/program/js/treelist.js
diff options
context:
space:
mode:
Diffstat (limited to 'program/js/treelist.js')
-rw-r--r--program/js/treelist.js1090
1 files changed, 544 insertions, 546 deletions
diff --git a/program/js/treelist.js b/program/js/treelist.js
index fec2d7f21..5913f44b4 100644
--- a/program/js/treelist.js
+++ b/program/js/treelist.js
@@ -23,552 +23,550 @@
*/
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') + '">&nbsp;</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(),
- 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;
- }
+ // apply some defaults to p
+ p = $.extend({
+ id_prefix: '',
+ autoexpand: 1000,
+ selectable: false,
+ check_droptarget: function(node){ return !node.virtual }
+ }, p || {});
+
+ var container = $(node),
+ data = p.data || [],
+ indexbyid = {},
+ selection = null,
+ drag_active = false,
+ box_coords = {},
+ item_coords = [],
+ autoexpand_timer,
+ autoexpand_item,
+ body_scroll_top = 0,
+ list_scroll_top = 0,
+ 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') + '">&nbsp;</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(),
+ 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