2 +-----------------------------------------------------------------------+
3 | RoundCube List Widget |
5 | This file is part of the RoundCube Webmail client |
6 | Copyright (C) 2006-2008, RoundCube Dev, - Switzerland |
7 | Licensed under the GNU GPL |
9 +-----------------------------------------------------------------------+
10 | Authors: Thomas Bruederli <roundcube@gmail.com> |
11 | Charles McNulty <charles@charlesmcnulty.com> |
12 +-----------------------------------------------------------------------+
13 | Requires: common.js |
14 +-----------------------------------------------------------------------+
16 $Id: list.js 344 2006-09-18 03:49:28Z thomasb $
21 * RoundCube List Widget class
24 function rcube_list_widget(list, p)
30 this.list = list ? list : null;
35 this.subject_col = -1;
36 this.shiftkey = false;
37 this.multiselect = false;
38 this.draggable = false;
39 this.keyboard = false;
40 this.toggleselect = false;
42 this.dont_select = false;
43 this.drag_active = false;
44 this.last_selected = 0;
46 this.in_selection_before = false;
48 this.drag_mouse_start = null;
49 this.dblclick_time = 600;
50 this.row_init = function(){};
51 this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragend:[] };
53 // overwrite default paramaters
54 if (p && typeof(p)=='object')
60 rcube_list_widget.prototype = {
64 * get all message rows from HTML table and init each row
68 if (this.list && this.list.tBodies[0])
70 this.rows = new Array();
73 for(var r=0; r<this.list.tBodies[0].childNodes.length; r++)
75 row = this.list.tBodies[0].childNodes[r];
76 while (row && (row.nodeType != 1 || row.style.display == 'none'))
78 row = row.nextSibling;
85 this.frame = this.list.parentNode;
89 rcube_event.add_listener({element:document, event:'keydown', object:this, method:'key_press'});
97 init_row: function(row)
99 // make references in internal array and set event handlers
100 if (row && String(row.id).match(/rcmrow([a-z0-9\-_=]+)/i))
105 this.rows[uid] = {uid:uid, id:row.id, obj:row, classname:row.className};
107 // set eventhandlers to table row
108 row.onmousedown = function(e){ return p.drag_row(e, this.uid); };
109 row.onmouseup = function(e){ return p.click_row(e, this.uid); };
112 row.onselectstart = function() { return false; };
114 this.row_init(this.rows[uid]);
124 var tbody = document.createElement('TBODY');
125 this.list.insertBefore(tbody, this.list.tBodies[0]);
126 this.list.removeChild(this.list.tBodies[1]);
127 this.rows = new Array();
129 if (sel) this.clear_selection();
134 * 'remove' message row from list (just hide it)
136 remove_row: function(uid, sel_next)
138 if (this.rows[uid].obj)
139 this.rows[uid].obj.style.display = 'none';
144 this.rows[uid] = null;
151 insert_row: function(row, attop)
153 var tbody = this.list.tBodies[0];
155 if (attop && tbody.rows.length)
156 tbody.insertBefore(row, tbody.firstChild);
158 tbody.appendChild(row);
166 * Set focur to the list
171 for (var n=0; n<this.selection.length; n++)
173 id = this.selection[n];
174 if (this.rows[id].obj)
176 this.set_classname(this.rows[id].obj, 'selected', true);
177 this.set_classname(this.rows[id].obj, 'unfocused', false);
181 if (e || (e = window.event))
182 rcube_event.cancel(e);
187 * remove focus from the list
192 this.focused = false;
193 for (var n=0; n<this.selection.length; n++)
195 id = this.selection[n];
196 if (this.rows[id] && this.rows[id].obj)
198 this.set_classname(this.rows[id].obj, 'selected', false);
199 this.set_classname(this.rows[id].obj, 'unfocused', true);
206 * onmousedown-handler of message list row
208 drag_row: function(e, id)
210 // don't do anything (another action processed before)
211 var evtarget = rcube_event.get_target(e);
212 if (this.dont_select || (evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG')))
215 this.in_selection_before = this.in_selection(id) ? id : false;
217 // selects currently unselected row
218 if (!this.in_selection_before)
220 var mod_key = rcube_event.get_modifier(e);
221 this.select_row(id, mod_key, false);
224 if (this.draggable && this.selection.length)
226 this.drag_start = true;
227 this.drag_mouse_start = rcube_event.get_mouse_pos(e);
228 rcube_event.add_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
229 rcube_event.add_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
237 * onmouseup-handler of message list row
239 click_row: function(e, id)
241 var now = new Date().getTime();
242 var mod_key = rcube_event.get_modifier(e);
243 var evtarget = rcube_event.get_target(e);
245 if ((evtarget && (evtarget.tagName == 'INPUT' || evtarget.tagName == 'IMG')))
248 // don't do anything (another action processed before)
249 if (this.dont_select)
251 this.dont_select = false;
255 var dblclicked = now - this.rows[id].clicked < this.dblclick_time;
257 // unselects currently selected row
258 if (!this.drag_active && this.in_selection_before == id && !dblclicked)
259 this.select_row(id, mod_key, false);
261 this.drag_start = false;
262 this.in_selection_before = false;
264 // row was double clicked
265 if (this.rows && dblclicked && this.in_selection(id))
266 this.trigger_event('dblclick');
268 this.trigger_event('click');
270 if (!this.drag_active)
271 rcube_event.cancel(e);
273 this.rows[id].clicked = now;
279 * get next and previous rows that are not hidden
281 get_next_row: function()
286 var last_selected_row = this.rows[this.last_selected];
287 var new_row = last_selected_row ? last_selected_row.obj.nextSibling : null;
288 while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
289 new_row = new_row.nextSibling;
294 get_prev_row: function()
299 var last_selected_row = this.rows[this.last_selected];
300 var new_row = last_selected_row ? last_selected_row.obj.previousSibling : null;
301 while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
302 new_row = new_row.previousSibling;
308 // selects or unselects the proper row depending on the modifier key pressed
309 select_row: function(id, mod_key, with_mouse)
311 var select_before = this.selection.join(',');
312 if (!this.multiselect)
315 if (!this.shift_start)
316 this.shift_start = id
320 this.shift_start = id;
321 this.highlight_row(id, false);
328 this.shift_select(id, false);
333 this.highlight_row(id, true);
336 case CONTROL_SHIFT_KEY:
337 this.shift_select(id, true);
341 this.highlight_row(id, false);
346 // trigger event if selection changed
347 if (this.selection.join(',') != select_before)
348 this.trigger_event('select');
350 if (this.last_selected != 0 && this.rows[this.last_selected])
351 this.set_classname(this.rows[this.last_selected].obj, 'focused', false);
353 // unselect if toggleselect is active and the same row was clicked again
354 if (this.toggleselect && this.last_selected == id)
356 this.clear_selection();
360 this.set_classname(this.rows[id].obj, 'focused', true);
362 if (!this.selection.length)
363 this.shift_start = null;
365 this.last_selected = id;
370 * Alias method for select_row
374 this.select_row(id, false);
380 * Select row next to the last selected one.
381 * Either below or above.
383 select_next: function()
385 var next_row = this.get_next_row();
386 var prev_row = this.get_prev_row();
387 var new_row = (next_row) ? next_row : prev_row;
389 this.select_row(new_row.uid, false, false);
394 * Perform selection when shift key is pressed
396 shift_select: function(id, control)
398 var from_rowIndex = this.rows[this.shift_start].obj.rowIndex;
399 var to_rowIndex = this.rows[id].obj.rowIndex;
401 var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
402 var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
404 // iterate through the entire message list
405 for (var n in this.rows)
407 if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j))
409 if (!this.in_selection(n))
410 this.highlight_row(n, true);
414 if (this.in_selection(n) && !control)
415 this.highlight_row(n, true);
422 * Check if given id is part of the current selection
424 in_selection: function(id)
426 for(var n in this.selection)
427 if (this.selection[n]==id)
435 * Select each row in list
437 select_all: function(filter)
439 if (!this.rows || !this.rows.length)
442 // reset but remember selection first
443 var select_before = this.selection.join(',');
444 this.clear_selection();
446 for (var n in this.rows)
448 if (!filter || this.rows[n][filter]==true)
450 this.last_selected = n;
451 this.highlight_row(n, true);
455 // trigger event if selection changed
456 if (this.selection.join(',') != select_before)
457 this.trigger_event('select');
464 * Unselect all selected rows
466 clear_selection: function()
468 var num_select = this.selection.length;
469 for (var n=0; n<this.selection.length; n++)
470 if (this.rows[this.selection[n]])
472 this.set_classname(this.rows[this.selection[n]].obj, 'selected', false);
473 this.set_classname(this.rows[this.selection[n]].obj, 'unfocused', false);
476 this.selection = new Array();
479 this.trigger_event('select');
484 * Getter for the selection array
486 get_selection: function()
488 return this.selection;
493 * Return the ID if only one row is selected
495 get_single_selection: function()
497 if (this.selection.length == 1)
498 return this.selection[0];
505 * Highlight/unhighlight a row
507 highlight_row: function(id, multiple)
509 if (this.rows[id] && !multiple)
511 if (!this.in_selection(id))
513 this.clear_selection();
514 this.selection[0] = id;
515 this.set_classname(this.rows[id].obj, 'selected', true);
518 else if (this.rows[id])
520 if (!this.in_selection(id)) // select row
522 this.selection[this.selection.length] = id;
523 this.set_classname(this.rows[id].obj, 'selected', true);
527 var p = find_in_array(id, this.selection);
528 var a_pre = this.selection.slice(0, p);
529 var a_post = this.selection.slice(p+1, this.selection.length);
530 this.selection = a_pre.concat(a_post);
531 this.set_classname(this.rows[id].obj, 'selected', false);
532 this.set_classname(this.rows[id].obj, 'unfocused', false);
539 * Handler for keyboard events
541 key_press: function(e)
543 if (this.focused != true)
546 var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
547 var mod_key = rcube_event.get_modifier(e);
552 return this.use_arrow_key(keyCode, mod_key);
556 this.shiftkey = e.shiftKey;
557 this.key_pressed = keyCode;
558 this.trigger_event('keypress');
566 * Special handling method for arrow keys
568 use_arrow_key: function(keyCode, mod_key)
571 if (keyCode == 40) // down arrow key pressed
572 new_row = this.get_next_row();
573 else if (keyCode == 38) // up arrow key pressed
574 new_row = this.get_prev_row();
578 this.select_row(new_row.uid, mod_key, true);
579 this.scrollto(new_row.uid);
587 * Try to scroll the list to make the specified row visible
589 scrollto: function(id)
591 var row = this.rows[id].obj;
592 if (row && this.frame)
594 var scroll_to = Number(row.offsetTop);
596 if (scroll_to < Number(this.frame.scrollTop))
597 this.frame.scrollTop = scroll_to;
598 else if (scroll_to + Number(row.offsetHeight) > Number(this.frame.scrollTop) + Number(this.frame.offsetHeight))
599 this.frame.scrollTop = (scroll_to + Number(row.offsetHeight)) - Number(this.frame.offsetHeight);
605 * Handler for mouse move events
607 drag_mouse_move: function(e)
611 // check mouse movement, of less than 3 pixels, don't start dragging
612 var m = rcube_event.get_mouse_pos(e);
613 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))
617 this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
619 // get subjects of selectedd messages
621 var c, i, node, subject, obj;
622 for(var n=0; n<this.selection.length; n++)
624 if (n>12) // only show 12 lines
630 if (this.rows[this.selection[n]].obj)
632 obj = this.rows[this.selection[n]].obj;
635 for(c=0, i=0; i<obj.childNodes.length; i++)
637 if (obj.childNodes[i].nodeName == 'TD')
639 if (((node = obj.childNodes[i].firstChild) && (node.nodeType==3 || node.nodeName=='A')) &&
640 (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c)))
642 subject = node.nodeType==3 ? node.data : node.innerHTML;
643 names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
652 this.draglayer.write(names);
653 this.draglayer.show(1);
655 this.drag_active = true;
656 this.trigger_event('dragstart');
659 if (this.drag_active && this.draglayer)
661 var pos = rcube_event.get_mouse_pos(e);
662 this.draglayer.move(pos.x+20, pos.y-5);
665 this.drag_start = false;
672 * Handler for mouse up events
674 drag_mouse_up: function(e)
676 document.onmousemove = null;
678 if (this.draglayer && this.draglayer.visible)
679 this.draglayer.show(0);
681 this.drag_active = false;
682 this.trigger_event('dragend');
684 rcube_event.remove_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
685 rcube_event.remove_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
687 return rcube_event.cancel(e);
693 * set/unset a specific class name
695 set_classname: function(obj, classname, set)
697 var reg = new RegExp('\s*'+classname, 'i');
698 if (!set && obj.className.match(reg))
699 obj.className = obj.className.replace(reg, '');
700 else if (set && !obj.className.match(reg))
701 obj.className += ' '+classname;
706 * Setter for object event handlers
708 * @param {String} Event name
709 * @param {Function} Handler function
710 * @return Listener ID (used to remove this handler later on)
712 addEventListener: function(evt, handler)
714 if (this.events[evt]) {
715 var handle = this.events[evt].length;
716 this.events[evt][handle] = handler;
725 * Removes a specific event listener
727 * @param {String} Event name
728 * @param {Int} Listener ID to remove
730 removeEventListener: function(evt, handle)
732 if (this.events[evt] && this.events[evt][handle])
733 this.events[evt][handle] = null;
738 * This will execute all registered event handlers
741 trigger_event: function(evt)
743 if (this.events[evt] && this.events[evt].length) {
744 for (var i=0; i<this.events[evt].length; i++)
745 if (typeof(this.events[evt][i]) == 'function')
746 this.events[evt][i](this);