Refactor drag & drop functionality. Don't rely on browser events anymore (#1484453)

release-0.6
thomascube 16 years ago
parent 55fb73529c
commit f89f03cd6a

@ -929,11 +929,11 @@ function rcmail_mailbox_list($attrib)
$out = $select->show(); $out = $select->show();
} }
else { else {
$out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $attrib['maxlength'], $attrib['realnames']), html::$common_attrib); $js_mailboxlist = array();
} $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
if ($type=='ul') {
$RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']); $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')); $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
} }
@ -973,64 +973,68 @@ function rcmail_mailbox_select($p = array())
* @access private * @access private
*/ */
function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='') function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
{ {
$pos = strpos($folder, $delm); $pos = strpos($folder, $delm);
if ($pos !== false) if ($pos !== false) {
{
$subFolders = substr($folder, $pos+1); $subFolders = substr($folder, $pos+1);
$currentFolder = substr($folder, 0, $pos); $currentFolder = substr($folder, 0, $pos);
} $virtual = !isset($arrFolders[$currentFolder]);
else }
{ else {
$subFolders = false; $subFolders = false;
$currentFolder = $folder; $currentFolder = $folder;
} $virtual = false;
}
$path .= $currentFolder; $path .= $currentFolder;
if (!isset($arrFolders[$currentFolder])) if (!isset($arrFolders[$currentFolder])) {
{
$arrFolders[$currentFolder] = array( $arrFolders[$currentFolder] = array(
'id' => $path, 'id' => $path,
'name' => rcube_charset_convert($currentFolder, 'UTF-7'), 'name' => rcube_charset_convert($currentFolder, 'UTF-7'),
'virtual' => $virtual,
'folders' => array()); 'folders' => array());
} }
else
$arrFolders[$currentFolder]['virtual'] = $virtual;
if (!empty($subFolders)) if (!empty($subFolders))
rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm); rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
} }
/** /**
* Return html for a structured list <ul> for the mailbox tree * Return html for a structured list <ul> for the mailbox tree
* @access private * @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; global $RCMAIL, $CONFIG;
$maxlength = intval($attrib['maxlength']);
$realnames = (bool)$attrib['realnames'];
$msgcounts = $RCMAIL->imap->get_cache('messagecount');
$idx = 0; $idx = 0;
$out = ''; $out = '';
foreach ($arrFolders as $key => $folder) foreach ($arrFolders as $key => $folder) {
{
$zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd'; $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
$title = null; $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); $foldername = rcube_label($folder_class);
else }
{ else {
$foldername = $folder['name']; $foldername = $folder['name'];
// shorten the folder name to a given length // shorten the folder name to a given length
if ($maxlength && $maxlength>1) if ($maxlength && $maxlength > 1) {
{
$fname = abbreviate_string($foldername, $maxlength); $fname = abbreviate_string($foldername, $maxlength);
if ($fname != $foldername) if ($fname != $foldername)
$title = $foldername; $title = $foldername;
$foldername = $fname; $foldername = $fname;
}
} }
}
// make folder name safe for ids and class names // make folder name safe for ids and class names
$folder_id = asciiwords($folder['id'], true); $folder_id = asciiwords($folder['id'], true);
@ -1054,35 +1058,45 @@ function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, $maxlength, $
$classes[] = 'selected'; $classes[] = 'selected';
$collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders')); $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']); $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( $out .= html::tag('li', array(
'id' => "rcmli".$folder_id, 'id' => "rcmli".$folder_id,
'class' => join(' ', $classes), 'class' => join(' ', $classes),
'noclose' => true), 'noclose' => true),
html::a(array( html::a($link_attrib, $html_name) .
'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)) .
(!empty($folder['folders']) ? html::div(array( (!empty($folder['folders']) ? html::div(array(
'class' => ($collapsed ? 'collapsed' : 'expanded'), 'class' => ($collapsed ? 'collapsed' : 'expanded'),
'style' => "position:absolute", 'style' => "position:absolute",
'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name) 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
), ' ') : '')); ), ' ') : ''));
if (!empty($folder['folders'])) $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
$out .= "\n<ul" . ($collapsed ? " style=\"display: none;\"" : "") . ">\n" . rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $maxlength, $realnames, $nestLevel+1) . "</ul>\n";
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"; $out .= "</li>\n";
$idx++; $idx++;
} }
return $out; return $out;
} }
/** /**

@ -133,6 +133,7 @@ function rcube_webmail()
this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); }); 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('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('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.addEventListener('dragend', function(o){ p.drag_active = false; });
this.message_list.init(); 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('keypress', function(o){ p.contactlist_keypress(o); });
this.contact_list.addEventListener('select', function(o){ p.contactlist_select(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('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.addEventListener('dragend', function(o){ p.drag_active = false; });
this.contact_list.init(); this.contact_list.init();
@ -1158,27 +1160,43 @@ function rcube_webmail()
this.doc_mouse_up = function(e) this.doc_mouse_up = function(e)
{ {
if (this.message_list) var model, li;
if (this.message_list) {
this.message_list.blur(); this.message_list.blur();
else if (this.contact_list) model = this.env.mailboxes;
}
else if (this.contact_list) {
this.contact_list.blur(); this.contact_list.blur();
}; model = this.env.address_sources;
}
this.focus_folder = function(id)
{ // handle mouse release when dragging
var li; if (this.drag_active && model) {
if (this.drag_active && this.check_droptarget(id) && (li = this.get_folder_li(id))) for (var k in model) {
this.set_classname(li, 'droptarget', true); 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; var li;
if (this.drag_active && (li = this.get_folder_li(id))) var model = this.task == 'mail' ? this.env.mailboxes : this.env.address_sources;
this.set_classname(li, 'droptarget', false);
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) this.collapse_folder = function(id)
{ {
var div; 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) this.click_on_list = function(e)
{ {
if (this.message_list) if (this.message_list)
@ -1232,7 +1240,7 @@ function rcube_webmail()
if (mbox_li = this.get_folder_li()) if (mbox_li = this.get_folder_li())
this.set_classname(mbox_li, 'unfocused', true); 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) this.check_droptarget = function(id)
{ {
if (this.task == 'mail') 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') else if (this.task == 'addressbook')
return (id != this.env.source && this.env.address_sources[id] && !this.env.address_sources[id].readonly); return (id != this.env.source && this.env.address_sources[id] && !this.env.address_sources[id].readonly);
else if (this.task == 'settings') else if (this.task == 'settings')
@ -3009,12 +3017,12 @@ function rcube_webmail()
row.id = id; row.id = id;
if (before && (before = this.get_folder_row_id(before))) if (before && (before = this.get_folder_row_id(before)))
tbody.insertBefore(row, document.getElementById(before)); tbody.insertBefore(row, document.getElementById(before));
else else
tbody.appendChild(row); tbody.appendChild(row);
if (replace) if (replace)
tbody.removeChild(replace); tbody.removeChild(replace);
} }
// add to folder/row-ID map // add to folder/row-ID map
@ -3136,9 +3144,9 @@ function rcube_webmail()
{ {
var cell, td; var cell, td;
var new_row = document.createElement('TR'); 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'); td = document.createElement('TD');
if (cell.className) if (cell.className)

@ -113,6 +113,15 @@ get_keycode: function(e)
return e && e.keyCode ? e.keyCode : (e && e.which ? e.which : 0); 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) * returns modifier key (constants defined at top of file)
*/ */
@ -502,18 +511,25 @@ function rcube_get_object_pos(obj)
var elm = obj.offsetParent; var elm = obj.offsetParent;
while(elm && elm!=null) while(elm && elm!=null)
{ {
iX += elm.offsetLeft; iX += elm.offsetLeft - (elm.parentNode && elm.parentNode.scrollLeft ? elm.parentNode.scrollLeft : 0);
iY += elm.offsetTop; iY += elm.offsetTop - (elm.parentNode && elm.parentNode.scrollTop ? elm.parentNode.scrollTop : 0);
elm = elm.offsetParent; 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}; 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 * Return the currently applied value of a css property

@ -51,7 +51,7 @@ function rcube_list_widget(list, p)
this.drag_mouse_start = null; this.drag_mouse_start = null;
this.dblclick_time = 600; this.dblclick_time = 600;
this.row_init = function(){}; this.row_init = function(){};
this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragend:[] }; this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragmove:[], dragend:[] };
// overwrite default paramaters // overwrite default paramaters
if (p && typeof(p)=='object') if (p && typeof(p)=='object')
@ -221,7 +221,11 @@ drag_row: function(e, id)
var evtarget = rcube_event.get_target(e); var evtarget = rcube_event.get_target(e);
if (this.dont_select || (evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG'))) if (this.dont_select || (evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG')))
return false; return false;
// accept right-clicks
if (rcube_event.get_button(e) == 2)
return true;
this.in_selection_before = this.in_selection(id) ? id : false; this.in_selection_before = this.in_selection(id) ? id : false;
// selects currently unselected row // selects currently unselected row
@ -576,7 +580,7 @@ key_press: function(e)
this.key_pressed = keyCode; this.key_pressed = keyCode;
this.trigger_event('keypress'); this.trigger_event('keypress');
if (this.key_pressed == list.BACKSPACE_KEY) if (this.key_pressed == this.BACKSPACE_KEY)
return rcube_event.cancel(e); return rcube_event.cancel(e);
} }
@ -704,6 +708,7 @@ drag_mouse_move: function(e)
{ {
var pos = rcube_event.get_mouse_pos(e); var pos = rcube_event.get_mouse_pos(e);
this.draglayer.move(pos.x+20, pos.y-5); this.draglayer.move(pos.x+20, pos.y-5);
this.trigger_event('dragmove', e);
} }
this.drag_start = false; this.drag_start = false;
@ -784,12 +789,12 @@ removeEventListener: function(evt, handle)
* This will execute all registered event handlers * This will execute all registered event handlers
* @private * @private
*/ */
trigger_event: function(evt) trigger_event: function(evt, p)
{ {
if (this.events[evt] && this.events[evt].length) { if (this.events[evt] && this.events[evt].length) {
for (var i=0; i<this.events[evt].length; i++) for (var i=0; i<this.events[evt].length; i++)
if (typeof(this.events[evt][i]) == 'function') if (typeof(this.events[evt][i]) == 'function')
this.events[evt][i](this); this.events[evt][i](this, p);
} }
} }

@ -58,31 +58,15 @@ function rcmail_directory_list($attrib)
if (!$attrib['id']) if (!$attrib['id'])
$attrib['id'] = 'rcmdirectorylist'; $attrib['id'] = 'rcmdirectorylist';
$out = '';
$local_id = '0'; $local_id = '0';
$current = get_input_value('_source', RCUBE_INPUT_GPC); $current = get_input_value('_source', RCUBE_INPUT_GPC);
$line_templ = '<li id="%s" class="%s"><a href="%s"' . $line_templ = html::tag('li', array('id' => 'rcmli%s', 'class' => '%s'),
' onclick="return %s.command(\'list\',\'%s\',this)"' . html::a(array('href' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
' onmouseover="return %s.focus_folder(\'%s\')"' .
' onmouseout="return %s.unfocus_folder(\'%s\')"' .
' onmouseup="return %s.folder_mouse_up(\'%s\')">%s'.
"</a></li>\n";
// 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') { if (strtolower($CONFIG['address_book_type']) != 'ldap') {
$out .= sprintf($line_templ, $out .= sprintf($line_templ, $local_id, (!$current ? 'selected' : ''),
'rcmli'.$local_id, Q(rcmail_url(null, array('_source' => $local_id))), $local_id, rcube_label('personaladrbook'));
!$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'));
} // end if } // end if
else { else {
// DB address book not used, see if a source is set, if not use the // 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 if
} // end else } // end else
foreach ((array)$CONFIG['ldap_public'] as $id => $prop) foreach ((array)$CONFIG['ldap_public'] as $id => $prop) {
{
$js_id = JQ($id); $js_id = JQ($id);
$dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id); $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id);
$out .= sprintf($line_templ, $out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''),
'rcmli'.$dom_id, Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($prop['name']) ? Q($prop['name']) : Q($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 .= '</ul>';
$OUTPUT->add_gui_object('folderlist', $attrib['id']); $OUTPUT->add_gui_object('folderlist', $attrib['id']);
return $out; return html::tag('ul', $attrib, $out, html::$common_attrib);
} }

@ -45,12 +45,13 @@
#directorylist #directorylist
{ {
left: 20px; left: 20px;
width: 150px; width: 170px;
overflow: hidden;
} }
#addresslist #addresslist
{ {
left: 185px; left: 200px;
width: 340px; width: 340px;
} }
@ -79,6 +80,7 @@
{ {
font-size: 11px; font-size: 11px;
border-bottom: 1px solid #EBEBEB; border-bottom: 1px solid #EBEBEB;
white-space: nowrap;
} }
#directorylist ul li a #directorylist ul li a
@ -88,6 +90,7 @@
padding-top: 2px; padding-top: 2px;
padding-bottom: 2px; padding-bottom: 2px;
text-decoration: none; text-decoration: none;
white-space: nowrap;
} }
#directorylist li.selected #directorylist li.selected
@ -127,7 +130,7 @@
{ {
position: absolute; position: absolute;
top: 85px; top: 85px;
left: 540px; left: 555px;
right: 20px; right: 20px;
bottom: 40px; bottom: 40px;
border: 1px solid #999999; border: 1px solid #999999;

@ -354,6 +354,11 @@ td.formlinks a:visited
font-weight: bold; font-weight: bold;
} }
#mailboxlist li.virtual > a
{
color: #666;
}
#mailboxlist li.selected, #mailboxlist li.selected,
#mailboxlist li.droptarget li.selected #mailboxlist li.droptarget li.selected
{ {

Loading…
Cancel
Save