2 +-----------------------------------------------------------------------+
3 | RoundCube List Widget |
5 | This file is part of the RoundCube Webmail client |
6 | Copyright (C) 2006, 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.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 this.in_selection_before = this.in_selection(id) ? id : false;
212 // don't do anything (another action processed before)
213 if (this.dont_select)
216 // selects currently unselected row
217 if (!this.in_selection_before)
219 var mod_key = rcube_event.get_modifier(e);
220 this.select_row(id, mod_key, false);
223 if (this.draggable && this.selection.length)
225 this.drag_start = true;
226 this.drag_mouse_start = rcube_event.get_mouse_pos(e);
227 rcube_event.add_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
228 rcube_event.add_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
236 * onmouseup-handler of message list row
238 click_row: function(e, id)
240 var now = new Date().getTime();
241 var mod_key = rcube_event.get_modifier(e);
243 // don't do anything (another action processed before)
244 if (this.dont_select)
246 this.dont_select = false;
250 var dblclicked = now - this.rows[id].clicked < this.dblclick_time;
252 // unselects currently selected row
253 if (!this.drag_active && this.in_selection_before == id && !dblclicked)
254 this.select_row(id, mod_key, false);
256 this.drag_start = false;
257 this.in_selection_before = false;
259 // row was double clicked
260 if (this.rows && dblclicked && this.in_selection(id))
261 this.trigger_event('dblclick');
263 this.trigger_event('click');
265 if (!this.drag_active)
266 rcube_event.cancel(e);
268 this.rows[id].clicked = now;
274 * get next and previous rows that are not hidden
276 get_next_row: function()
281 var last_selected_row = this.rows[this.last_selected];
282 var new_row = last_selected_row ? last_selected_row.obj.nextSibling : null;
283 while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
284 new_row = new_row.nextSibling;
289 get_prev_row: function()
294 var last_selected_row = this.rows[this.last_selected];
295 var new_row = last_selected_row ? last_selected_row.obj.previousSibling : null;
296 while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
297 new_row = new_row.previousSibling;
303 // selects or unselects the proper row depending on the modifier key pressed
304 select_row: function(id, mod_key, with_mouse)
306 var select_before = this.selection.join(',');
307 if (!this.multiselect)
310 if (!this.shift_start)
311 this.shift_start = id
315 this.shift_start = id;
316 this.highlight_row(id, false);
323 this.shift_select(id, false);
328 this.highlight_row(id, true);
331 case CONTROL_SHIFT_KEY:
332 this.shift_select(id, true);
336 this.highlight_row(id, false);
341 // trigger event if selection changed
342 if (this.selection.join(',') != select_before)
343 this.trigger_event('select');
345 if (this.last_selected != 0 && this.rows[this.last_selected])
346 this.set_classname(this.rows[this.last_selected].obj, 'focused', false);
348 // unselect if toggleselect is active and the same row was clicked again
349 if (this.toggleselect && this.last_selected == id)
351 this.clear_selection();
355 this.set_classname(this.rows[id].obj, 'focused', true);
357 if (!this.selection.length)
358 this.shift_start = null;
360 this.last_selected = id;
365 * Alias method for select_row
369 this.select_row(id, false);
375 * Select row next to the last selected one.
376 * Either below or above.
378 select_next: function()
380 var next_row = this.get_next_row();
381 var prev_row = this.get_prev_row();
382 var new_row = (next_row) ? next_row : prev_row;
384 this.select_row(new_row.uid, false, false);
389 * Perform selection when shift key is pressed
391 shift_select: function(id, control)
393 var from_rowIndex = this.rows[this.shift_start].obj.rowIndex;
394 var to_rowIndex = this.rows[id].obj.rowIndex;
396 var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
397 var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
399 // iterate through the entire message list
400 for (var n in this.rows)
402 if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j))
404 if (!this.in_selection(n))
405 this.highlight_row(n, true);
409 if (this.in_selection(n) && !control)
410 this.highlight_row(n, true);
417 * Check if given id is part of the current selection
419 in_selection: function(id)
421 for(var n in this.selection)
422 if (this.selection[n]==id)
430 * Select each row in list
432 select_all: function(filter)
434 if (!this.rows || !this.rows.length)
437 // reset but remember selection first
438 var select_before = this.selection.join(',');
439 this.clear_selection();
441 for (var n in this.rows)
443 if (!filter || this.rows[n][filter]==true)
445 this.last_selected = n;
446 this.highlight_row(n, true);
450 // trigger event if selection changed
451 if (this.selection.join(',') != select_before)
452 this.trigger_event('select');
459 * Unselect all selected rows
461 clear_selection: function()
463 var num_select = this.selection.length;
464 for (var n=0; n<this.selection.length; n++)
465 if (this.rows[this.selection[n]])
467 this.set_classname(this.rows[this.selection[n]].obj, 'selected', false);
468 this.set_classname(this.rows[this.selection[n]].obj, 'unfocused', false);
471 this.selection = new Array();
474 this.trigger_event('select');
479 * Getter for the selection array
481 get_selection: function()
483 return this.selection;
488 * Return the ID if only one row is selected
490 get_single_selection: function()
492 if (this.selection.length == 1)
493 return this.selection[0];
500 * Highlight/unhighlight a row
502 highlight_row: function(id, multiple)
504 if (this.rows[id] && !multiple)
506 this.clear_selection();
507 this.selection[0] = id;
508 this.set_classname(this.rows[id].obj, 'selected', true)
510 else if (this.rows[id])
512 if (!this.in_selection(id)) // select row
514 this.selection[this.selection.length] = id;
515 this.set_classname(this.rows[id].obj, 'selected', true);
519 var p = find_in_array(id, this.selection);
520 var a_pre = this.selection.slice(0, p);
521 var a_post = this.selection.slice(p+1, this.selection.length);
522 this.selection = a_pre.concat(a_post);
523 this.set_classname(this.rows[id].obj, 'selected', false);
524 this.set_classname(this.rows[id].obj, 'unfocused', false);
531 * Handler for keyboard events
533 key_press: function(e)
535 if (this.focused != true)
538 this.shiftkey = e.shiftKey;
540 var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
541 var mod_key = rcube_event.get_modifier(e);
546 return this.use_arrow_key(keyCode, mod_key);
550 this.key_pressed = keyCode;
551 this.trigger_event('keypress');
559 * Special handling method for arrow keys
561 use_arrow_key: function(keyCode, mod_key)
564 if (keyCode == 40) // down arrow key pressed
565 new_row = this.get_next_row();
566 else if (keyCode == 38) // up arrow key pressed
567 new_row = this.get_prev_row();
571 this.select_row(new_row.uid, mod_key, true);
572 this.scrollto(new_row.uid);
580 * Try to scroll the list to make the specified row visible
582 scrollto: function(id)
584 var row = this.rows[id].obj;
585 if (row && this.frame)
587 var scroll_to = Number(row.offsetTop);
589 if (scroll_to < Number(this.frame.scrollTop))
590 this.frame.scrollTop = scroll_to;
591 else if (scroll_to + Number(row.offsetHeight) > Number(this.frame.scrollTop) + Number(this.frame.offsetHeight))
592 this.frame.scrollTop = (scroll_to + Number(row.offsetHeight)) - Number(this.frame.offsetHeight);
598 * Handler for mouse move events
600 drag_mouse_move: function(e)
604 // check mouse movement, of less than 3 pixels, don't start dragging
605 var m = rcube_event.get_mouse_pos(e);
606 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))
610 this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
612 // get subjects of selectedd messages
614 var c, node, subject, obj;
615 for(var n=0; n<this.selection.length; n++)
617 if (n>12) // only show 12 lines
623 if (this.rows[this.selection[n]].obj)
625 obj = this.rows[this.selection[n]].obj;
628 for(c=0; c<obj.childNodes.length; c++)
629 if (obj.childNodes[c].nodeName=='TD' && (node = obj.childNodes[c].firstChild) && (node.nodeType==3 || node.nodeName=='A'))
631 subject = node.nodeType==3 ? node.data : node.innerHTML;
632 names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
638 this.draglayer.write(names);
639 this.draglayer.show(1);
641 this.drag_active = true;
642 this.trigger_event('dragstart');
645 if (this.drag_active && this.draglayer)
647 var pos = rcube_event.get_mouse_pos(e);
648 this.draglayer.move(pos.x+20, pos.y-5);
651 this.drag_start = false;
658 * Handler for mouse up events
660 drag_mouse_up: function(e)
662 document.onmousemove = null;
664 if (this.draglayer && this.draglayer.visible)
665 this.draglayer.show(0);
667 this.drag_active = false;
668 this.trigger_event('dragend');
670 rcube_event.remove_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
671 rcube_event.remove_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
673 return rcube_event.cancel(e);
679 * set/unset a specific class name
681 set_classname: function(obj, classname, set)
683 var reg = new RegExp('\s*'+classname, 'i');
684 if (!set && obj.className.match(reg))
685 obj.className = obj.className.replace(reg, '');
686 else if (set && !obj.className.match(reg))
687 obj.className += ' '+classname;
692 * Setter for object event handlers
694 * @param {String} Event name
695 * @param {Function} Handler function
696 * @return Listener ID (used to remove this handler later on)
698 addEventListener: function(evt, handler)
700 if (this.events[evt]) {
701 var handle = this.events[evt].length;
702 this.events[evt][handle] = handler;
711 * Removes a specific event listener
713 * @param {String} Event name
714 * @param {Int} Listener ID to remove
716 removeEventListener: function(evt, handle)
718 if (this.events[evt] && this.events[evt][handle])
719 this.events[evt][handle] = null;
724 * This will execute all registered event handlers
727 trigger_event: function(evt)
729 if (this.events[evt] && this.events[evt].length) {
730 for (var i=0; i<this.events[evt].length; i++)
731 if (typeof(this.events[evt][i]) == 'function')
732 this.events[evt][i](this);