From f89f03cd6ae4a1b3f98e39c2e01d9e40f8a286b4 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Fri, 12 Sep 2008 11:12:05 +0000
Subject: Refactor drag & drop functionality. Don't rely on browser events
 anymore (#1484453)

---
 program/include/main.inc           | 88 ++++++++++++++++++++++----------------
 program/js/app.js                  | 72 +++++++++++++++++--------------
 program/js/common.js               | 26 ++++++++---
 program/js/list.js                 | 15 ++++---
 program/steps/addressbook/func.inc | 48 ++++-----------------
 5 files changed, 131 insertions(+), 118 deletions(-)

(limited to 'program')

diff --git a/program/include/main.inc b/program/include/main.inc
index a6ad93d2b..87c727700 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -929,11 +929,11 @@ function rcmail_mailbox_list($attrib)
     $out = $select->show();
   }
   else {
-    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $attrib['maxlength'], $attrib['realnames']), html::$common_attrib);
-  }
-
-  if ($type=='ul') {
+    $js_mailboxlist = array();
+    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
+    
     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
+    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
   }
 
@@ -973,64 +973,68 @@ function rcmail_mailbox_select($p = array())
  * @access private
  */
 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
-  {
+{
   $pos = strpos($folder, $delm);
-  if ($pos !== false)
-    {
+  if ($pos !== false) {
     $subFolders = substr($folder, $pos+1);
     $currentFolder = substr($folder, 0, $pos);
-    }
-  else
-    {
+    $virtual = !isset($arrFolders[$currentFolder]);
+  }
+  else {
     $subFolders = false;
     $currentFolder = $folder;
-    }
+    $virtual = false;
+  }
 
   $path .= $currentFolder;
 
-  if (!isset($arrFolders[$currentFolder]))
-    {
+  if (!isset($arrFolders[$currentFolder])) {
     $arrFolders[$currentFolder] = array(
       'id' => $path,
       'name' => rcube_charset_convert($currentFolder, 'UTF-7'),
+      'virtual' => $virtual,
       'folders' => array());
-    }
+  }
+  else
+    $arrFolders[$currentFolder]['virtual'] = $virtual;
 
   if (!empty($subFolders))
     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
-  }
+}
   
 
 /**
  * Return html for a structured list &lt;ul&gt; for the mailbox tree
  * @access private
  */
-function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, $maxlength, $realnames=false, $nestLevel=0)
-  {
+function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
+{
   global $RCMAIL, $CONFIG;
+  
+  $maxlength = intval($attrib['maxlength']);
+  $realnames = (bool)$attrib['realnames'];
+  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
 
   $idx = 0;
   $out = '';
-  foreach ($arrFolders as $key => $folder)
-    {
+  foreach ($arrFolders as $key => $folder) {
     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
     $title = null;
 
-    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames)
+    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
       $foldername = rcube_label($folder_class);
-    else
-      {
+    }
+    else {
       $foldername = $folder['name'];
 
       // shorten the folder name to a given length
-      if ($maxlength && $maxlength>1)
-        {
+      if ($maxlength && $maxlength > 1) {
         $fname = abbreviate_string($foldername, $maxlength);
         if ($fname != $foldername)
           $title = $foldername;
         $foldername = $fname;
-        }
       }
+    }
 
     // make folder name safe for ids and class names
     $folder_id = asciiwords($folder['id'], true);
@@ -1054,35 +1058,45 @@ function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, $maxlength, $
       $classes[] = 'selected';
 
     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
+    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
+    
+    if ($folder['virtual'])
+      $classes[] = 'virtual';
+    else if ($unread)
+      $classes[] = 'unread';
 
     $js_name = JQ($folder['id']);
+    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
+    $link_attrib = $folder['virtual'] ? array() : array(
+      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
+      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
+      'title' => $title,
+    );
+
     $out .= html::tag('li', array(
         'id' => "rcmli".$folder_id,
         'class' => join(' ', $classes),
         'noclose' => true),
-      html::a(array(
-        'href' => rcmail_url('', array('_mbox' => $folder['id'])),
-        'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
-        'onmouseover' => sprintf("return %s.focus_folder('%s')", JS_OBJECT_NAME, $js_name),
-        'onmouseout' => sprintf("return %s.unfocus_folder('%s')", JS_OBJECT_NAME, $js_name),
-        'onmouseup' => sprintf("return %s.folder_mouse_up('%s')", JS_OBJECT_NAME, $js_name),
-        'title' => $title,
-      ), Q($foldername)) .
+      html::a($link_attrib, $html_name) .
       (!empty($folder['folders']) ? html::div(array(
         'class' => ($collapsed ? 'collapsed' : 'expanded'),
         'style' => "position:absolute",
         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
       ), '&nbsp;') : ''));
     
-    if (!empty($folder['folders']))
-      $out .= "\n<ul" . ($collapsed ? " style=\"display: none;\"" : "") . ">\n" . rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $maxlength, $realnames, $nestLevel+1) . "</ul>\n";
+    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
+    
+    if (!empty($folder['folders'])) {
+      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
+        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
+    }
 
     $out .= "</li>\n";
     $idx++;
-    }
+  }
 
   return $out;
-  }
+}
 
 
 /**
diff --git a/program/js/app.js b/program/js/app.js
index 3812a73de..36072e384 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -133,6 +133,7 @@ function rcube_webmail()
           this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
           this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
           this.message_list.addEventListener('dragstart', function(o){ p.drag_active = true; if (p.preview_timer) clearTimeout(p.preview_timer); });
+          this.message_list.addEventListener('dragmove', function(o, e){ p.drag_move(e); });
           this.message_list.addEventListener('dragend', function(o){ p.drag_active = false; });
 
           this.message_list.init();
@@ -252,6 +253,7 @@ function rcube_webmail()
           this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
           this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
           this.contact_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
+          this.contact_list.addEventListener('dragmove', function(o, e){ p.drag_move(e); });
           this.contact_list.addEventListener('dragend', function(o){ p.drag_active = false; });
           this.contact_list.init();
 
@@ -1158,27 +1160,43 @@ function rcube_webmail()
 
 
   this.doc_mouse_up = function(e)
-    {
-    if (this.message_list)
+  {
+    var model, li;
+    
+    if (this.message_list) {
       this.message_list.blur();
-    else if (this.contact_list)
+      model = this.env.mailboxes;
+    }
+    else if (this.contact_list) {
       this.contact_list.blur();
-    };
-
-  this.focus_folder = function(id)
-    {
-    var li;
-    if (this.drag_active && this.check_droptarget(id) && (li = this.get_folder_li(id)))
-      this.set_classname(li, 'droptarget', true);
+      model = this.env.address_sources;
+    }
+    
+    // handle mouse release when dragging
+    if (this.drag_active && model) {
+      for (var k in model) {
+        if ((li = this.get_folder_li(k)) && rcube_mouse_is_over(e, li.firstChild) && this.check_droptarget(k)) {
+          this.set_classname(li, 'droptarget', false);
+          this.command('moveto', model[k].id);
+          break;
+        }
+      }
     }
+  };
 
-  this.unfocus_folder = function(id)
-    {
+  this.drag_move = function(e)
+  {
     var li;
-    if (this.drag_active && (li = this.get_folder_li(id)))
-      this.set_classname(li, 'droptarget', false);
+    var model = this.task == 'mail' ? this.env.mailboxes : this.env.address_sources;
+    
+    if (this.gui_objects.folderlist && model) {
+      for (var k in model) {
+        if (li = this.get_folder_li(k))
+          this.set_classname(li, 'droptarget', (rcube_mouse_is_over(e, li.firstChild) && this.check_droptarget(k)));
+      }
     }
-
+  };
+  
   this.collapse_folder = function(id)
     {
     var div;
@@ -1211,16 +1229,6 @@ function rcube_webmail()
       }
     }
 
-  // onmouseup handler for folder list item
-  this.folder_mouse_up = function(id)
-    {
-    if (this.drag_active)
-      {
-      this.unfocus_folder(id);
-      this.command('moveto', id);
-      }
-    };
-
   this.click_on_list = function(e)
     {
     if (this.message_list)
@@ -1232,7 +1240,7 @@ function rcube_webmail()
     if (mbox_li = this.get_folder_li())
       this.set_classname(mbox_li, 'unfocused', true);
 
-    rcube_event.cancel(e);
+    return rcube_event.get_button(e) == 2 ? true : rcube_event.cancel(e);
     };
 
 
@@ -1303,7 +1311,7 @@ function rcube_webmail()
   this.check_droptarget = function(id)
   {
     if (this.task == 'mail')
-      return (id != this.env.mailbox);
+      return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual);
     else if (this.task == 'addressbook')
       return (id != this.env.source && this.env.address_sources[id] && !this.env.address_sources[id].readonly);
     else if (this.task == 'settings')
@@ -3009,12 +3017,12 @@ function rcube_webmail()
       row.id = id;
 
       if (before && (before = this.get_folder_row_id(before)))
-	tbody.insertBefore(row, document.getElementById(before));
+        tbody.insertBefore(row, document.getElementById(before));
       else
-        tbody.appendChild(row);	  
+        tbody.appendChild(row);
       
       if (replace)
-	tbody.removeChild(replace);
+        tbody.removeChild(replace);
       }
 
     // add to folder/row-ID map
@@ -3136,9 +3144,9 @@ function rcube_webmail()
     {
     var cell, td;
     var new_row = document.createElement('TR');
-    for(var n=0; n<row.childNodes.length; n++)
+    for(var n=0; n<row.cells.length; n++)
       {
-      cell = row.childNodes[n];
+      cell = row.cells[n];
       td = document.createElement('TD');
 
       if (cell.className)
diff --git a/program/js/common.js b/program/js/common.js
index 6e4c643fa..209ce1070 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -113,6 +113,15 @@ get_keycode: function(e)
   return e && e.keyCode ? e.keyCode : (e && e.which ? e.which : 0);
 },
 
+/**
+ * returns the event key code
+ */
+get_button: function(e)
+{
+  e = e || window.event;
+  return e && (typeof e.button != 'undefined') ? e.button : (e && e.which ? e.which : 0);
+},
+
 /**
  * returns modifier key (constants defined at top of file)
  */
@@ -502,18 +511,25 @@ function rcube_get_object_pos(obj)
     var elm = obj.offsetParent;
     while(elm && elm!=null)
       {
-      iX += elm.offsetLeft;
-      iY += elm.offsetTop;
+      iX += elm.offsetLeft - (elm.parentNode && elm.parentNode.scrollLeft ? elm.parentNode.scrollLeft : 0);
+      iY += elm.offsetTop - (elm.parentNode && elm.parentNode.scrollTop ? elm.parentNode.scrollTop : 0);
       elm = elm.offsetParent;
       }
     }
 
-  //if(bw.mac && bw.ie5) iX += document.body.leftMargin;
-  //if(bw.mac && bw.ie5) iY += document.body.topMargin;
-
   return {x:iX, y:iY};
   }
 
+// determine whether the mouse is over the given object or not
+function rcube_mouse_is_over(ev, obj)
+{
+  var mouse = rcube_event.get_mouse_pos(ev);
+  var pos = rcube_get_object_pos(obj);
+  
+  return ((mouse.x >= pos.x) && (mouse.x < (pos.x + obj.offsetWidth)) &&
+    (mouse.y >= pos.y) && (mouse.y < (pos.y + obj.offsetHeight)));
+}
+
 
 /**
  * Return the currently applied value of a css property
diff --git a/program/js/list.js b/program/js/list.js
index f33c67c7b..926d98aa7 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -51,7 +51,7 @@ function rcube_list_widget(list, p)
   this.drag_mouse_start = null;
   this.dblclick_time = 600;
   this.row_init = function(){};
-  this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragend:[] };
+  this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragmove:[], dragend:[] };
   
   // overwrite default paramaters
   if (p && typeof(p)=='object')
@@ -221,7 +221,11 @@ drag_row: function(e, id)
   var evtarget = rcube_event.get_target(e);
   if (this.dont_select || (evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG')))
     return false;
-
+    
+  // accept right-clicks
+  if (rcube_event.get_button(e) == 2)
+    return true;
+  
   this.in_selection_before = this.in_selection(id) ? id : false;
 
   // selects currently unselected row
@@ -576,7 +580,7 @@ key_press: function(e)
       this.key_pressed = keyCode;
       this.trigger_event('keypress');
       
-      if (this.key_pressed == list.BACKSPACE_KEY)
+      if (this.key_pressed == this.BACKSPACE_KEY)
         return rcube_event.cancel(e);
   }
   
@@ -704,6 +708,7 @@ drag_mouse_move: function(e)
   {
     var pos = rcube_event.get_mouse_pos(e);
     this.draglayer.move(pos.x+20, pos.y-5);
+    this.trigger_event('dragmove', e);
   }
 
   this.drag_start = false;
@@ -784,12 +789,12 @@ removeEventListener: function(evt, handle)
  * This will execute all registered event handlers
  * @private
  */
-trigger_event: function(evt)
+trigger_event: function(evt, p)
 {
   if (this.events[evt] && this.events[evt].length) {
     for (var i=0; i<this.events[evt].length; i++)
       if (typeof(this.events[evt][i]) == 'function')
-        this.events[evt][i](this);
+        this.events[evt][i](this, p);
   }
 }
 
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 43d117430..98cf29f10 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -58,31 +58,15 @@ function rcmail_directory_list($attrib)
   if (!$attrib['id'])
     $attrib['id'] = 'rcmdirectorylist';
 
+  $out = '';
   $local_id = '0';
   $current = get_input_value('_source', RCUBE_INPUT_GPC);
-  $line_templ = '<li id="%s" class="%s"><a href="%s"' .
-    ' onclick="return %s.command(\'list\',\'%s\',this)"' .
-    ' onmouseover="return %s.focus_folder(\'%s\')"' .
-    ' onmouseout="return %s.unfocus_folder(\'%s\')"' .
-    ' onmouseup="return %s.folder_mouse_up(\'%s\')">%s'.
-    "</a></li>\n";
+  $line_templ = html::tag('li', array('id' => 'rcmli%s', 'class' => '%s'),
+    html::a(array('href' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
     
-  // allow the following attributes to be added to the <ul> tag
-  $out = '<ul' . create_attrib_string($attrib, array('style', 'class', 'id')) . ">\n";
   if (strtolower($CONFIG['address_book_type']) != 'ldap') {
-    $out .= sprintf($line_templ,
-      'rcmli'.$local_id,
-      !$current ? 'selected' : '',
-      Q(rcmail_url(null, array('_source' => 0))),
-      JS_OBJECT_NAME,
-      $local_id,
-      JS_OBJECT_NAME,
-      $local_id,
-      JS_OBJECT_NAME,
-      $local_id,
-      JS_OBJECT_NAME,
-      $local_id,
-      rcube_label('personaladrbook'));
+    $out .= sprintf($line_templ, $local_id, (!$current ? 'selected' : ''),
+      Q(rcmail_url(null, array('_source' => $local_id))), $local_id, rcube_label('personaladrbook'));
   } // end if
   else {
     // DB address book not used, see if a source is set, if not use the
@@ -92,30 +76,16 @@ function rcmail_directory_list($attrib)
     } // end if
   } // end else
   
-  foreach ((array)$CONFIG['ldap_public'] as $id => $prop)
-  {
+  foreach ((array)$CONFIG['ldap_public'] as $id => $prop) {
     $js_id = JQ($id);
     $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id);
-    $out .= sprintf($line_templ,
-      'rcmli'.$dom_id,
-      $current == $id ? 'selected' : '',
-      Q(rcmail_url(null, array('_source' => $id))),
-      JS_OBJECT_NAME,
-      $js_id,
-      JS_OBJECT_NAME,
-      $js_id,
-      JS_OBJECT_NAME,
-      $js_id,
-      JS_OBJECT_NAME,
-      $js_id,
-      !empty($prop['name']) ? Q($prop['name']) : Q($id));
+    $out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''),
+      Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($prop['name']) ? Q($prop['name']) : Q($id)));
   }
   
-  $out .= '</ul>';
-
   $OUTPUT->add_gui_object('folderlist', $attrib['id']);
   
-  return $out;
+  return html::tag('ul', $attrib, $out, html::$common_attrib);
 }
 
 
-- 
cgit v1.2.3