|
|
/* +-----------------------------------------------------------------------+ | RoundCube List Widget | | | | This file is part of the RoundCube Webmail client | | Copyright (C) 2006-2008, RoundCube Dev, - Switzerland | | Licensed under the GNU GPL | | | +-----------------------------------------------------------------------+ | Authors: Thomas Bruederli <roundcube@gmail.com> | | Charles McNulty <charles@charlesmcnulty.com> | +-----------------------------------------------------------------------+ | Requires: common.js | +-----------------------------------------------------------------------+
$Id: list.js 344 2006-09-18 03:49:28Z thomasb $ */
/** * RoundCube List Widget class * @contructor */ function rcube_list_widget(list, p) { // static contants
this.ENTER_KEY = 13; this.DELETE_KEY = 46; this.list = list ? list : null; this.frame = null; this.rows = []; this.selection = []; this.shiftkey = false;
this.multiselect = false; this.draggable = false; this.keyboard = false; this.toggleselect = false; this.dont_select = false; this.drag_active = false; this.last_selected = 0; this.shift_start = 0; this.in_selection_before = false; this.focused = false; this.drag_mouse_start = null; this.dblclick_time = 600; this.row_init = function(){}; this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragend:[] }; // overwrite default paramaters
if (p && typeof(p)=='object') for (var n in p) this[n] = p[n]; }
rcube_list_widget.prototype = {
/** * get all message rows from HTML table and init each row */ init: function() { if (this.list && this.list.tBodies[0]) { this.rows = new Array();
var row; for(var r=0; r<this.list.tBodies[0].childNodes.length; r++) { row = this.list.tBodies[0].childNodes[r]; while (row && (row.nodeType != 1 || row.style.display == 'none')) { row = row.nextSibling; r++; }
this.init_row(row); }
this.frame = this.list.parentNode;
// set body events
if (this.keyboard) rcube_event.add_listener({element:document, event:'keydown', object:this, method:'key_press'}); } },
/** * */ init_row: function(row) { // make references in internal array and set event handlers
if (row && String(row.id).match(/rcmrow([a-z0-9\-_=]+)/i)) { var p = this; var uid = RegExp.$1; row.uid = uid; this.rows[uid] = {uid:uid, id:row.id, obj:row, classname:row.className};
// set eventhandlers to table row
row.onmousedown = function(e){ return p.drag_row(e, this.uid); }; row.onmouseup = function(e){ return p.click_row(e, this.uid); };
if (document.all) row.onselectstart = function() { return false; };
this.row_init(this.rows[uid]); } },
/** * */ clear: function(sel) { var tbody = document.createElement('TBODY'); this.list.insertBefore(tbody, this.list.tBodies[0]); this.list.removeChild(this.list.tBodies[1]); this.rows = new Array(); if (sel) this.clear_selection(); },
/** * 'remove' message row from list (just hide it) */ remove_row: function(uid, sel_next) { if (this.rows[uid].obj) this.rows[uid].obj.style.display = 'none';
if (sel_next) this.select_next();
this.rows[uid] = null; },
/** * */ insert_row: function(row, attop) { var tbody = this.list.tBodies[0];
if (attop && tbody.rows.length) tbody.insertBefore(row, tbody.firstChild); else tbody.appendChild(row);
this.init_row(row); },
/** * Set focur to the list */ focus: function(e) { this.focused = true; for (var n=0; n<this.selection.length; n++) { id = this.selection[n]; if (this.rows[id].obj) { this.set_classname(this.rows[id].obj, 'selected', true); this.set_classname(this.rows[id].obj, 'unfocused', false); } }
if (e || (e = window.event)) rcube_event.cancel(e); },
/** * remove focus from the list */ blur: function() { var id; this.focused = false; for (var n=0; n<this.selection.length; n++) { id = this.selection[n]; if (this.rows[id] && this.rows[id].obj) { this.set_classname(this.rows[id].obj, 'selected', false); this.set_classname(this.rows[id].obj, 'unfocused', true); } } },
/** * onmousedown-handler of message list row */ drag_row: function(e, id) { this.in_selection_before = this.in_selection(id) ? id : false;
// don't do anything (another action processed before)
var evtarget = rcube_event.get_target(e); if (this.dont_select || (evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG'))) return false;
// selects currently unselected row
if (!this.in_selection_before) { var mod_key = rcube_event.get_modifier(e); this.select_row(id, mod_key, false); }
if (this.draggable && this.selection.length) { this.drag_start = true; this.drag_mouse_start = rcube_event.get_mouse_pos(e); rcube_event.add_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'}); rcube_event.add_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'}); }
return false; },
/** * onmouseup-handler of message list row */ click_row: function(e, id) { var now = new Date().getTime(); var mod_key = rcube_event.get_modifier(e); var evtarget = rcube_event.get_target(e); if ((evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG'))) return false; // don't do anything (another action processed before)
if (this.dont_select) { this.dont_select = false; return false; } var dblclicked = now - this.rows[id].clicked < this.dblclick_time;
// unselects currently selected row
if (!this.drag_active && this.in_selection_before == id && !dblclicked) this.select_row(id, mod_key, false);
this.drag_start = false; this.in_selection_before = false;
// row was double clicked
if (this.rows && dblclicked && this.in_selection(id)) this.trigger_event('dblclick'); else this.trigger_event('click');
if (!this.drag_active) rcube_event.cancel(e);
this.rows[id].clicked = now; return false; },
/** * get next and previous rows that are not hidden */ get_next_row: function() { if (!this.rows) return false;
var last_selected_row = this.rows[this.last_selected]; var new_row = last_selected_row ? last_selected_row.obj.nextSibling : null; while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) new_row = new_row.nextSibling;
return new_row; },
get_prev_row: function() { if (!this.rows) return false;
var last_selected_row = this.rows[this.last_selected]; var new_row = last_selected_row ? last_selected_row.obj.previousSibling : null; while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) new_row = new_row.previousSibling;
return new_row; },
// selects or unselects the proper row depending on the modifier key pressed
select_row: function(id, mod_key, with_mouse) { var select_before = this.selection.join(','); if (!this.multiselect) mod_key = 0; if (!this.shift_start) this.shift_start = id
if (!mod_key) { this.shift_start = id; this.highlight_row(id, false); } else { switch (mod_key) { case SHIFT_KEY: this.shift_select(id, false); break;
case CONTROL_KEY: if (!with_mouse) this.highlight_row(id, true); break;
case CONTROL_SHIFT_KEY: this.shift_select(id, true); break;
default: this.highlight_row(id, false); break; } }
// trigger event if selection changed
if (this.selection.join(',') != select_before) this.trigger_event('select');
if (this.last_selected != 0 && this.rows[this.last_selected]) this.set_classname(this.rows[this.last_selected].obj, 'focused', false);
// unselect if toggleselect is active and the same row was clicked again
if (this.toggleselect && this.last_selected == id) { this.clear_selection(); id = null; } else this.set_classname(this.rows[id].obj, 'focused', true);
if (!this.selection.length) this.shift_start = null;
this.last_selected = id; },
/** * Alias method for select_row */ select: function(id) { this.select_row(id, false); this.scrollto(id); },
/** * Select row next to the last selected one. * Either below or above. */ select_next: function() { var next_row = this.get_next_row(); var prev_row = this.get_prev_row(); var new_row = (next_row) ? next_row : prev_row; if (new_row) this.select_row(new_row.uid, false, false); },
/** * Perform selection when shift key is pressed */ shift_select: function(id, control) { var from_rowIndex = this.rows[this.shift_start].obj.rowIndex; var to_rowIndex = this.rows[id].obj.rowIndex;
var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex); var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
// iterate through the entire message list
for (var n in this.rows) { if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j)) { if (!this.in_selection(n)) this.highlight_row(n, true); } else { if (this.in_selection(n) && !control) this.highlight_row(n, true); } } },
/** * Check if given id is part of the current selection */ in_selection: function(id) { for(var n in this.selection) if (this.selection[n]==id) return true;
return false; },
/** * Select each row in list */ select_all: function(filter) { if (!this.rows || !this.rows.length) return false;
// reset but remember selection first
var select_before = this.selection.join(','); this.clear_selection();
for (var n in this.rows) { if (!filter || this.rows[n][filter]==true) { this.last_selected = n; this.highlight_row(n, true); } }
// trigger event if selection changed
if (this.selection.join(',') != select_before) this.trigger_event('select');
return true; },
/** * Unselect all selected rows */ clear_selection: function() { var num_select = this.selection.length; for (var n=0; n<this.selection.length; n++) if (this.rows[this.selection[n]]) { this.set_classname(this.rows[this.selection[n]].obj, 'selected', false); this.set_classname(this.rows[this.selection[n]].obj, 'unfocused', false); }
this.selection = new Array(); if (num_select) this.trigger_event('select'); },
/** * Getter for the selection array */ get_selection: function() { return this.selection; },
/** * Return the ID if only one row is selected */ get_single_selection: function() { if (this.selection.length == 1) return this.selection[0]; else return null; },
/** * Highlight/unhighlight a row */ highlight_row: function(id, multiple) { if (this.rows[id] && !multiple) { if (!this.in_selection(id)) { this.clear_selection(); this.selection[0] = id; this.set_classname(this.rows[id].obj, 'selected', true); } } else if (this.rows[id]) { if (!this.in_selection(id)) // select row
{ this.selection[this.selection.length] = id; this.set_classname(this.rows[id].obj, 'selected', true); } else // unselect row
{ var p = find_in_array(id, this.selection); var a_pre = this.selection.slice(0, p); var a_post = this.selection.slice(p+1, this.selection.length); this.selection = a_pre.concat(a_post); this.set_classname(this.rows[id].obj, 'selected', false); this.set_classname(this.rows[id].obj, 'unfocused', false); } } },
/** * Handler for keyboard events */ key_press: function(e) { if (this.focused != true) return true;
var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0; var mod_key = rcube_event.get_modifier(e); switch (keyCode) { case 40: case 38: return this.use_arrow_key(keyCode, mod_key); break;
default: this.shiftkey = e.shiftKey; this.key_pressed = keyCode; this.trigger_event('keypress'); } return true; },
/** * Special handling method for arrow keys */ use_arrow_key: function(keyCode, mod_key) { var new_row; if (keyCode == 40) // down arrow key pressed
new_row = this.get_next_row(); else if (keyCode == 38) // up arrow key pressed
new_row = this.get_prev_row();
if (new_row) { this.select_row(new_row.uid, mod_key, true); this.scrollto(new_row.uid); }
return false; },
/** * Try to scroll the list to make the specified row visible */ scrollto: function(id) { var row = this.rows[id].obj; if (row && this.frame) { var scroll_to = Number(row.offsetTop);
if (scroll_to < Number(this.frame.scrollTop)) this.frame.scrollTop = scroll_to; else if (scroll_to + Number(row.offsetHeight) > Number(this.frame.scrollTop) + Number(this.frame.offsetHeight)) this.frame.scrollTop = (scroll_to + Number(row.offsetHeight)) - Number(this.frame.offsetHeight); } },
/** * Handler for mouse move events */ drag_mouse_move: function(e) { if (this.drag_start) { // check mouse movement, of less than 3 pixels, don't start dragging
var m = rcube_event.get_mouse_pos(e); if (!this.drag_mouse_start || (Math.abs(m.x - this.drag_mouse_start.x) < 3 && Math.abs(m.y - this.drag_mouse_start.y) < 3)) return false; if (!this.draglayer) this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000}); // get subjects of selectedd messages
var names = ''; var c, node, subject, obj; for(var n=0; n<this.selection.length; n++) { if (n>12) // only show 12 lines
{ names += '...'; break; }
if (this.rows[this.selection[n]].obj) { obj = this.rows[this.selection[n]].obj; subject = '';
for(c=0; c<obj.childNodes.length; c++) if (obj.childNodes[c].nodeName=='TD' && (node = obj.childNodes[c].firstChild) && (node.nodeType==3 || node.nodeName=='A')) { subject = node.nodeType==3 ? node.data : node.innerHTML; names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />'; break; } } }
this.draglayer.write(names); this.draglayer.show(1);
this.drag_active = true; this.trigger_event('dragstart'); }
if (this.drag_active && this.draglayer) { var pos = rcube_event.get_mouse_pos(e); this.draglayer.move(pos.x+20, pos.y-5); }
this.drag_start = false;
return false; },
/** * Handler for mouse up events */ drag_mouse_up: function(e) { document.onmousemove = null;
if (this.draglayer && this.draglayer.visible) this.draglayer.show(0);
this.drag_active = false; this.trigger_event('dragend');
rcube_event.remove_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'}); rcube_event.remove_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
return rcube_event.cancel(e); },
/** * set/unset a specific class name */ set_classname: function(obj, classname, set) { var reg = new RegExp('\s*'+classname, 'i'); if (!set && obj.className.match(reg)) obj.className = obj.className.replace(reg, ''); else if (set && !obj.className.match(reg)) obj.className += ' '+classname; },
/** * Setter for object event handlers * * @param {String} Event name * @param {Function} Handler function * @return Listener ID (used to remove this handler later on) */ addEventListener: function(evt, handler) { if (this.events[evt]) { var handle = this.events[evt].length; this.events[evt][handle] = handler; return handle; } else return false; },
/** * Removes a specific event listener * * @param {String} Event name * @param {Int} Listener ID to remove */ removeEventListener: function(evt, handle) { if (this.events[evt] && this.events[evt][handle]) this.events[evt][handle] = null; },
/** * This will execute all registered event handlers * @private */ trigger_event: function(evt) { 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); } }
};
|