thomascube
2010-04-01 1d773d71414316e0b9836a15c35576593427ee21
commit | author | age
6b47de 1 /*
T 2  +-----------------------------------------------------------------------+
3  | RoundCube List Widget                                                 |
4  |                                                                       |
5  | This file is part of the RoundCube Webmail client                     |
cbbef3 6  | Copyright (C) 2006-2009, RoundCube Dev, - Switzerland                 |
6b47de 7  | Licensed under the GNU GPL                                            |
T 8  |                                                                       |
9  +-----------------------------------------------------------------------+
10  | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
11  |          Charles McNulty <charles@charlesmcnulty.com>                 |
12  +-----------------------------------------------------------------------+
13  | Requires: common.js                                                   |
14  +-----------------------------------------------------------------------+
15
57ff3b 16   $Id$
6b47de 17 */
T 18
19
20 /**
21  * RoundCube List Widget class
22  * @contructor
23  */
24 function rcube_list_widget(list, p)
25   {
26   // static contants
27   this.ENTER_KEY = 13;
28   this.DELETE_KEY = 46;
6e6e89 29   this.BACKSPACE_KEY = 8;
6b47de 30   
T 31   this.list = list ? list : null;
32   this.frame = null;
33   this.rows = [];
34   this.selection = [];
0dbac3 35   this.rowcount = 0;
6b47de 36   
d24d20 37   this.subject_col = -1;
31c171 38   this.shiftkey = false;
6b47de 39   this.multiselect = false;
f52c93 40   this.multiexpand = false;
21168d 41   this.multi_selecting = false;
6b47de 42   this.draggable = false;
T 43   this.keyboard = false;
68b6a9 44   this.toggleselect = false;
6b47de 45   
T 46   this.dont_select = false;
47   this.drag_active = false;
48   this.last_selected = 0;
b2fb95 49   this.shift_start = 0;
6b47de 50   this.in_selection_before = false;
T 51   this.focused = false;
52   this.drag_mouse_start = null;
53   this.dblclick_time = 600;
54   this.row_init = function(){};
55   
56   // overwrite default paramaters
57   if (p && typeof(p)=='object')
58     for (var n in p)
59       this[n] = p[n];
60   }
61
62
63 rcube_list_widget.prototype = {
64
65
66 /**
67  * get all message rows from HTML table and init each row
68  */
69 init: function()
70 {
71   if (this.list && this.list.tBodies[0])
72   {
73     this.rows = new Array();
0dbac3 74     this.rowcount = 0;
6b47de 75
T 76     var row;
77     for(var r=0; r<this.list.tBodies[0].childNodes.length; r++)
78     {
79       row = this.list.tBodies[0].childNodes[r];
f52c93 80       while (row && row.nodeType != 1)
6b47de 81       {
T 82         row = row.nextSibling;
83         r++;
84       }
85
86       this.init_row(row);
0dbac3 87       this.rowcount++;
6b47de 88     }
T 89
90     this.frame = this.list.parentNode;
91
92     // set body events
26f5b0 93     if (this.keyboard) {
1691a8 94       rcube_event.add_listener({element:document, event:bw.opera?'keypress':'keydown', object:this, method:'key_press'});
9e7a1b 95       rcube_event.add_listener({element:document, event:'keydown', object:this, method:'key_down'});
26f5b0 96     }
6b47de 97   }
T 98 },
99
100
101 /**
c4b819 102  * Init list row and set mouse events on it
6b47de 103  */
T 104 init_row: function(row)
105 {
106   // make references in internal array and set event handlers
c85707 107   if (row && String(row.id).match(/rcmrow([a-z0-9\-_=\+\/]+)/i))
6b47de 108   {
T 109     var p = this;
110     var uid = RegExp.$1;
111     row.uid = uid;
f52c93 112     this.rows[uid] = {uid:uid, id:row.id, obj:row};
6b47de 113
T 114     // set eventhandlers to table row
115     row.onmousedown = function(e){ return p.drag_row(e, this.uid); };
116     row.onmouseup = function(e){ return p.click_row(e, this.uid); };
117
118     if (document.all)
119       row.onselectstart = function() { return false; };
120
121     this.row_init(this.rows[uid]);
122   }
123 },
124
125
126 /**
c4b819 127  * Remove all list rows
6b47de 128  */
f11541 129 clear: function(sel)
6b47de 130 {
91a35e 131   var tbody = document.createElement('tbody');
6b47de 132   this.list.insertBefore(tbody, this.list.tBodies[0]);
T 133   this.list.removeChild(this.list.tBodies[1]);
f11541 134   this.rows = new Array();
0dbac3 135   this.rowcount = 0;
f11541 136   
T 137   if (sel) this.clear_selection();
6b47de 138 },
T 139
140
141 /**
142  * 'remove' message row from list (just hide it)
143  */
b62656 144 remove_row: function(uid, sel_next)
6b47de 145 {
T 146   if (this.rows[uid].obj)
147     this.rows[uid].obj.style.display = 'none';
148
b62656 149   if (sel_next)
T 150     this.select_next();
151
6b47de 152   this.rows[uid] = null;
0dbac3 153   this.rowcount--;
6b47de 154 },
T 155
156
157 /**
c4b819 158  * Add row to the list and initialize it
6b47de 159  */
T 160 insert_row: function(row, attop)
161 {
c4b819 162   if (this.background)
A 163     var tbody = this.background;
164   else
165     var tbody = this.list.tBodies[0];
6b47de 166
T 167   if (attop && tbody.rows.length)
c4b819 168     tbody.insertBefore(row, tbody.firstChild);
6b47de 169   else
c4b819 170     tbody.appendChild(row);
6b47de 171
c4b819 172   this.init_row(row);
0dbac3 173   this.rowcount++;
6b47de 174 },
T 175
176
177
178 /**
25c35c 179  * Set focus to the list
6b47de 180  */
T 181 focus: function(e)
182 {
183   this.focused = true;
184   for (var n=0; n<this.selection.length; n++)
185   {
186     id = this.selection[n];
cc97ea 187     if (this.rows[id] && this.rows[id].obj) {
T 188       $(this.rows[id].obj).addClass('selected').removeClass('unfocused');
6b47de 189     }
T 190   }
191
192   if (e || (e = window.event))
193     rcube_event.cancel(e);
194 },
195
196
197 /**
198  * remove focus from the list
199  */
200 blur: function()
201 {
202   var id;
203   this.focused = false;
204   for (var n=0; n<this.selection.length; n++)
205   {
206     id = this.selection[n];
cc97ea 207     if (this.rows[id] && this.rows[id].obj) {
T 208       $(this.rows[id].obj).removeClass('selected').addClass('unfocused');
6b47de 209     }
T 210   }
211 },
212
213
214 /**
215  * onmousedown-handler of message list row
216  */
217 drag_row: function(e, id)
218 {
219   // don't do anything (another action processed before)
a0ce2f 220   var evtarget = rcube_event.get_target(e);
91a35e 221   var tagname = evtarget.tagName.toLowerCase();
A 222   if (this.dont_select || (evtarget && (tagname == 'input' || tagname == 'img')))
40d7c2 223     return true;
f89f03 224     
T 225   // accept right-clicks
226   if (rcube_event.get_button(e) == 2)
227     return true;
228   
bf36a9 229   this.in_selection_before = this.in_selection(id) ? id : false;
T 230
6b47de 231   // selects currently unselected row
T 232   if (!this.in_selection_before)
233   {
234     var mod_key = rcube_event.get_modifier(e);
235     this.select_row(id, mod_key, false);
236   }
237
238   if (this.draggable && this.selection.length)
239   {
240     this.drag_start = true;
b2fb95 241     this.drag_mouse_start = rcube_event.get_mouse_pos(e);
6b47de 242     rcube_event.add_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
T 243     rcube_event.add_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
cf6bc5 244
A 245     // add listener for iframes
91a35e 246     var iframes = document.getElementsByTagName('iframe');
cf6bc5 247     this.iframe_events = Object();
A 248     for (var n in iframes)
249     {
250       var iframedoc = null;
251       if (iframes[n].contentDocument)
252         iframedoc = iframes[n].contentDocument;
253       else if (iframes[n].contentWindow)
cc97ea 254         iframedoc = iframes[n].contentWindow.document;
cf6bc5 255       else if (iframes[n].document)
A 256         iframedoc = iframes[n].document;
257
258       if (iframedoc)
259       {
cc97ea 260         var list = this;
T 261         var pos = $('#'+iframes[n].id).offset();
262         this.iframe_events[n] = function(e) { e._offset = pos; return list.drag_mouse_move(e); }
263
264         if (iframedoc.addEventListener)
265           iframedoc.addEventListener('mousemove', this.iframe_events[n], false);
266         else if (iframes[n].attachEvent)
267           iframedoc.attachEvent('onmousemove', this.iframe_events[n]);
268         else
269           iframedoc['onmousemove'] = this.iframe_events[n];
cf6bc5 270
A 271         rcube_event.add_listener({element:iframedoc, event:'mouseup', object:this, method:'drag_mouse_up'});
272       }
cc97ea 273     }
6b47de 274   }
T 275
276   return false;
277 },
278
279
280 /**
281  * onmouseup-handler of message list row
282  */
283 click_row: function(e, id)
284 {
285   var now = new Date().getTime();
286   var mod_key = rcube_event.get_modifier(e);
a0ce2f 287   var evtarget = rcube_event.get_target(e);
91a35e 288   var tagname = evtarget.tagName.toLowerCase();
40d7c2 289
91a35e 290   if ((evtarget && (tagname == 'input' || tagname == 'img')))
40d7c2 291     return true;
A 292
6b47de 293   // don't do anything (another action processed before)
T 294   if (this.dont_select)
295     {
296     this.dont_select = false;
297     return false;
298     }
299     
300   var dblclicked = now - this.rows[id].clicked < this.dblclick_time;
301
302   // unselects currently selected row
303   if (!this.drag_active && this.in_selection_before == id && !dblclicked)
304     this.select_row(id, mod_key, false);
305
306   this.drag_start = false;
307   this.in_selection_before = false;
308
309   // row was double clicked
310   if (this.rows && dblclicked && this.in_selection(id))
cc97ea 311     this.triggerEvent('dblclick');
6b47de 312   else
cc97ea 313     this.triggerEvent('click');
6b47de 314
T 315   if (!this.drag_active)
316     rcube_event.cancel(e);
317
318   this.rows[id].clicked = now;
319   return false;
320 },
321
322
bc2acc 323 /*
A 324  * Returns thread root ID for specified row ID
325  */
326 find_root: function(uid)
327 {
328    var r = this.rows[uid];
329
330    if (r && r.parent_uid)
331      return this.find_root(r.parent_uid);
332    else
333      return uid;
334 },
335
336
f52c93 337 expand_row: function(e, id)
T 338 {
339   var row = this.rows[id];
340   var evtarget = rcube_event.get_target(e);
341   var mod_key = rcube_event.get_modifier(e);
342
343   // Don't select this message
344   this.dont_select = true;
345   // Don't treat double click on the expando as double click on the message.
346   row.clicked = 0;
347
348   if (row.expanded) {
349     evtarget.className = "collapsed";
350     if (mod_key == CONTROL_KEY || this.multiexpand)
351       this.collapse_all(row);
352     else
353       this.collapse(row);
354   }
355   else {
356     evtarget.className = "expanded";
357     if (mod_key == CONTROL_KEY || this.multiexpand)
358       this.expand_all(row);
359     else
360      this.expand(row);
361   }
362 },
363
364 collapse: function(row)
365 {
366   row.expanded = false;
367   this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
368   var depth = row.depth;
369   var new_row = row ? row.obj.nextSibling : null;
370   var r;
371
372   while (new_row) {
373     if (new_row.nodeType == 1) {
374       var r = this.rows[new_row.uid];
375       if (r && r.depth <= depth)
376         break;
377       $(new_row).hide();
378       r.expanded = false;
379       this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
380     }
381     new_row = new_row.nextSibling;
382   }
383
384   return false;
385 },
386
387 expand: function(row)
388 {
389   var depth, new_row;
390   var last_expanded_parent_depth;
391
392   if (row) {
393     row.expanded = true;
394     depth = row.depth;
395     new_row = row.obj.nextSibling;
bc2acc 396     this.update_expando(row.uid, true);
f52c93 397     this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
T 398   }
399   else {
400     var tbody = this.list.tBodies[0];
401     new_row = tbody.firstChild;
402     depth = 0;
403     last_expanded_parent_depth = 0;
404   }
405
406   while (new_row) {
407     if (new_row.nodeType == 1) {
408       var r = this.rows[new_row.uid];
409       if (r) {
410         if (row && (!r.depth || r.depth <= depth))
411           break;
412
413         if (r.parent_uid) {
414           var p = this.rows[r.parent_uid];
415           if (p && p.expanded) {
416             if ((row && p == row) || last_expanded_parent_depth >= p.depth - 1) {
417               last_expanded_parent_depth = p.depth;
418               $(new_row).show();
419               r.expanded = true;
420               this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
421             }
422           }
423           else
424             if (row && (! p || p.depth <= depth))
425               break;
426         }
427       }
428     }
429     new_row = new_row.nextSibling;
430   }
431
432   return false;
433 },
434
435
436 collapse_all: function(row)
437 {
438   var depth, new_row;
439   var r;
440
441   if (row) {
442     row.expanded = false;
443     depth = row.depth;
444     new_row = row.obj.nextSibling;
bc2acc 445     this.update_expando(row.uid);
f52c93 446     this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
T 447     
448     // don't collapse sub-root tree in multiexpand mode 
449     if (depth && this.multiexpand)
450       return false; 
451   }
452   else {
453     var tbody = this.list.tBodies[0];
454     new_row = tbody.firstChild;
455     depth = 0;
456   }
457
458   while (new_row) {
459     if (new_row.nodeType == 1) {
460       var r = this.rows[new_row.uid];
461       if (r) {
462         if (row && (!r.depth || r.depth <= depth))
463           break;
464
465         if (row || r.depth)
466           $(new_row).hide();
467         if (r.has_children) {
468           r.expanded = false;
bc2acc 469           this.update_expando(r.uid);
f52c93 470           this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
T 471         }
472       }
473     }
474     new_row = new_row.nextSibling;
475   }
476
477   return false;
478 },
479
480 expand_all: function(row)
481 {
482   var depth, new_row;
483   var r;
484
485   if (row) {
486     row.expanded = true;
487     depth = row.depth;
488     new_row = row.obj.nextSibling;
bc2acc 489     this.update_expando(row.uid, true);
f52c93 490     this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
T 491   }
492   else {
493     var tbody = this.list.tBodies[0];
494     new_row = tbody.firstChild;
495     depth = 0;
496   }
497
498   while (new_row) {
499     if (new_row.nodeType == 1) {
500       var r = this.rows[new_row.uid];
501       if (r) {
502         if (row && r.depth <= depth)
503           break;
504
505         $(new_row).show();
506         if (r.has_children) {
507           r.expanded = true;
bc2acc 508           this.update_expando(r.uid, true);
f52c93 509           this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
T 510         }
511       }
512     }
513     new_row = new_row.nextSibling;
514   }
515   return false;
516 },
bc2acc 517
A 518 update_expando: function(uid, expanded)
519 {
520   var expando = document.getElementById('rcmexpando' + uid);
521   if (expando)
522     expando.className = expanded ? 'expanded' : 'collapsed';
523 },
524
f52c93 525
6b47de 526 /**
49771b 527  * get first/next/previous/last rows that are not hidden
6b47de 528  */
T 529 get_next_row: function()
530 {
531   if (!this.rows)
532     return false;
533
534   var last_selected_row = this.rows[this.last_selected];
a7d5c6 535   var new_row = last_selected_row ? last_selected_row.obj.nextSibling : null;
6b47de 536   while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
T 537     new_row = new_row.nextSibling;
538
539   return new_row;
540 },
541
542 get_prev_row: function()
543 {
544   if (!this.rows)
545     return false;
546
547   var last_selected_row = this.rows[this.last_selected];
a7d5c6 548   var new_row = last_selected_row ? last_selected_row.obj.previousSibling : null;
6b47de 549   while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none'))
T 550     new_row = new_row.previousSibling;
551
552   return new_row;
49771b 553 },
A 554
555 get_first_row: function()
556 {
557   if (this.rowcount)
558     {
559     var rows = this.list.tBodies[0].rows;
560
561     for(var i=0; i<rows.length-1; i++)
562       if(rows[i].id && String(rows[i].id).match(/rcmrow([a-z0-9\-_=\+\/]+)/i) && this.rows[RegExp.$1] != null)
563     return RegExp.$1;
564     }
565
566   return null;
6b47de 567 },
T 568
095d05 569 get_last_row: function()
A 570 {
571   if (this.rowcount)
572     {
573     var rows = this.list.tBodies[0].rows;
6b47de 574
095d05 575     for(var i=rows.length-1; i>=0; i--)
c85707 576       if(rows[i].id && String(rows[i].id).match(/rcmrow([a-z0-9\-_=\+\/]+)/i) && this.rows[RegExp.$1] != null)
095d05 577     return RegExp.$1;
A 578     }
579
580   return null;
581 },
582
583
584 /**
585  * selects or unselects the proper row depending on the modifier key pressed
586  */
6b47de 587 select_row: function(id, mod_key, with_mouse)
T 588 {
589   var select_before = this.selection.join(',');
590   if (!this.multiselect)
591     mod_key = 0;
b2fb95 592     
T 593   if (!this.shift_start)
594     this.shift_start = id
6b47de 595
T 596   if (!mod_key)
597   {
598     this.shift_start = id;
599     this.highlight_row(id, false);
21168d 600     this.multi_selecting = false;
6b47de 601   }
T 602   else
603   {
604     switch (mod_key)
605     {
606       case SHIFT_KEY:
b2fb95 607         this.shift_select(id, false);
6b47de 608         break;
T 609
610       case CONTROL_KEY:
611         if (!with_mouse)
b2fb95 612           this.highlight_row(id, true);
6b47de 613         break; 
T 614
615       case CONTROL_SHIFT_KEY:
616         this.shift_select(id, true);
617         break;
618
619       default:
b2fb95 620         this.highlight_row(id, false);
6b47de 621         break;
T 622     }
21168d 623     this.multi_selecting = true;
6b47de 624   }
T 625
626   // trigger event if selection changed
627   if (this.selection.join(',') != select_before)
cc97ea 628     this.triggerEvent('select');
6b47de 629
T 630   if (this.last_selected != 0 && this.rows[this.last_selected])
cc97ea 631     $(this.rows[this.last_selected].obj).removeClass('focused');
a9dda5 632
T 633   // unselect if toggleselect is active and the same row was clicked again
634   if (this.toggleselect && this.last_selected == id)
635   {
636     this.clear_selection();
637     id = null;
638   }
639   else
cc97ea 640     $(this.rows[id].obj).addClass('focused');
a9dda5 641
b2fb95 642   if (!this.selection.length)
T 643     this.shift_start = null;
6b47de 644
T 645   this.last_selected = id;
646 },
647
648
649 /**
650  * Alias method for select_row
651  */
652 select: function(id)
653 {
654   this.select_row(id, false);
655   this.scrollto(id);
656 },
657
658
659 /**
660  * Select row next to the last selected one.
661  * Either below or above.
662  */
663 select_next: function()
664 {
665   var next_row = this.get_next_row();
666   var prev_row = this.get_prev_row();
667   var new_row = (next_row) ? next_row : prev_row;
668   if (new_row)
669     this.select_row(new_row.uid, false, false);  
670 },
671
bc2acc 672
49771b 673 /**
A 674  * Select first row 
675  */
bc2acc 676 select_first: function(mod_key)
49771b 677 {
bc2acc 678   var row = this.get_first_row();
A 679   if (row && mod_key) {
680     this.shift_select(row, mod_key);
681     this.triggerEvent('select');
682     this.scrollto(row);
683   }
684   else if (row)
685     this.select(row);
49771b 686 },
bc2acc 687
A 688
689 /**
690  * Select last row 
691  */
692 select_last: function(mod_key)
693 {
694   var row = this.get_last_row();
695   if (row && mod_key) {
696     this.shift_select(row, mod_key);
697     this.triggerEvent('select');
698     this.scrollto(row);    
699   }
700   else if (row)
701     this.select(row);
702 },
703
49771b 704
84a331 705 /**
T 706  * Add all childs of the given row to selection
707  */
708 select_childs: function(uid)
709 {
710   if (!this.rows[uid] || !this.rows[uid].has_children)
711     return;
712   
713   var depth = this.rows[uid].depth;
714   var row = this.rows[uid].obj.nextSibling;
715   while (row) {
716     if (row.nodeType == 1) {
717       if ((r = this.rows[row.uid])) {
718         if (!r.depth || r.depth <= depth)
719           break;
720         if (!this.in_selection(r.uid))
721           this.select_row(r.uid, CONTROL_KEY);
722       }
723     }
724     row = row.nextSibling;
725   }
726 },
727
6b47de 728
T 729 /**
730  * Perform selection when shift key is pressed
731  */
732 shift_select: function(id, control)
733 {
b00bd0 734   if (!this.rows[this.shift_start] || !this.selection.length)
f2892d 735     this.shift_start = id;
A 736
6b47de 737   var from_rowIndex = this.rows[this.shift_start].obj.rowIndex;
T 738   var to_rowIndex = this.rows[id].obj.rowIndex;
739
740   var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
741   var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
742
743   // iterate through the entire message list
744   for (var n in this.rows)
745   {
746     if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j))
747     {
f52c93 748       if (!this.in_selection(n)) {
6b47de 749         this.highlight_row(n, true);
f52c93 750       }
6b47de 751     }
T 752     else
753     {
f52c93 754       if  (this.in_selection(n) && !control) {
6b47de 755         this.highlight_row(n, true);
f52c93 756       }
6b47de 757     }
T 758   }
759 },
760
761
762 /**
763  * Check if given id is part of the current selection
764  */
765 in_selection: function(id)
766 {
767   for(var n in this.selection)
768     if (this.selection[n]==id)
769       return true;
770
f52c93 771   return false;
6b47de 772 },
T 773
774
775 /**
776  * Select each row in list
777  */
778 select_all: function(filter)
779 {
780   if (!this.rows || !this.rows.length)
781     return false;
782
1094cd 783   // reset but remember selection first
T 784   var select_before = this.selection.join(',');
874717 785   this.selection = new Array();
A 786   
6b47de 787   for (var n in this.rows)
T 788   {
98bf5f 789     if (!filter || (this.rows[n] && this.rows[n][filter] == true))
6b47de 790     {
T 791       this.last_selected = n;
792       this.highlight_row(n, true);
793     }
874717 794     else if (this.rows[n])
A 795     {
eaacbe 796       $(this.rows[n].obj).removeClass('selected').removeClass('unfocused');
874717 797     }
6b47de 798   }
T 799
1094cd 800   // trigger event if selection changed
T 801   if (this.selection.join(',') != select_before)
cc97ea 802     this.triggerEvent('select');
1094cd 803
d7c226 804   this.focus();
A 805
1094cd 806   return true;
6b47de 807 },
T 808
809
810 /**
528185 811  * Invert selection
A 812  */
813 invert_selection: function()
814 {
815   if (!this.rows || !this.rows.length)
816     return false;
817
818   // remember old selection
819   var select_before = this.selection.join(',');
820   
821   for (var n in this.rows)
f52c93 822     this.highlight_row(n, true);
528185 823
A 824   // trigger event if selection changed
825   if (this.selection.join(',') != select_before)
826     this.triggerEvent('select');
827
828   this.focus();
829
830   return true;
831 },
832
833
834 /**
095d05 835  * Unselect selected row(s)
6b47de 836  */
095d05 837 clear_selection: function(id)
6b47de 838 {
1094cd 839   var num_select = this.selection.length;
095d05 840
A 841   // one row
842   if (id)
6b47de 843     {
095d05 844     for (var n=0; n<this.selection.length; n++)
cc97ea 845       if (this.selection[n] == id) {
T 846         this.selection.splice(n,1);
847         break;
848       }
095d05 849     }
A 850   // all rows
851   else
852     {
853     for (var n=0; n<this.selection.length; n++)
cc97ea 854       if (this.rows[this.selection[n]]) {
T 855         $(this.rows[this.selection[n]].obj).removeClass('selected').removeClass('unfocused');
095d05 856         }
A 857     
858     this.selection = new Array();
6b47de 859     }
T 860
095d05 861   if (num_select && !this.selection.length)
cc97ea 862     this.triggerEvent('select');
6b47de 863 },
T 864
865
866 /**
867  * Getter for the selection array
868  */
869 get_selection: function()
870 {
871   return this.selection;
872 },
873
874
875 /**
876  * Return the ID if only one row is selected
877  */
878 get_single_selection: function()
879 {
880   if (this.selection.length == 1)
881     return this.selection[0];
882   else
883     return null;
884 },
885
886
887 /**
888  * Highlight/unhighlight a row
889  */
890 highlight_row: function(id, multiple)
891 {
892   if (this.rows[id] && !multiple)
893   {
df015d 894     if (this.selection.length > 1 || !this.in_selection(id))
T 895     {
896       this.clear_selection();
897       this.selection[0] = id;
cc97ea 898       $(this.rows[id].obj).addClass('selected');
df015d 899     }
6b47de 900   }
T 901   else if (this.rows[id])
902   {
903     if (!this.in_selection(id))  // select row
904     {
905       this.selection[this.selection.length] = id;
cc97ea 906       $(this.rows[id].obj).addClass('selected');
6b47de 907     }
T 908     else  // unselect row
909     {
81ab85 910       var p = jQuery.inArray(id, this.selection);
6b47de 911       var a_pre = this.selection.slice(0, p);
T 912       var a_post = this.selection.slice(p+1, this.selection.length);
913       this.selection = a_pre.concat(a_post);
cc97ea 914       $(this.rows[id].obj).removeClass('selected').removeClass('unfocused');
6b47de 915     }
T 916   }
917 },
918
919
920 /**
921  * Handler for keyboard events
922  */
923 key_press: function(e)
924 {
26f5b0 925   if (this.focused != true)
6b47de 926     return true;
T 927
26f5b0 928   var keyCode = rcube_event.get_keycode(e);
6b47de 929   var mod_key = rcube_event.get_modifier(e);
91d1a1 930
6b47de 931   switch (keyCode)
T 932   {
933     case 40:
934     case 38: 
26f5b0 935     case 63233: // "down", in safari keypress
T 936     case 63232: // "up", in safari keypress
937       // Stop propagation so that the browser doesn't scroll
938       rcube_event.cancel(e);
6b47de 939       return this.use_arrow_key(keyCode, mod_key);
f52c93 940     case 61:
T 941     case 107: // Plus sign on a numeric keypad (fc11 + firefox 3.5.2)
942     case 109:
943     case 32:
944       // Stop propagation
945       rcube_event.cancel(e);
946       var ret = this.use_plusminus_key(keyCode, mod_key);
947       this.key_pressed = keyCode;
948       this.triggerEvent('keypress');
949       return ret;
bc2acc 950     case 36: // Home
A 951       this.select_first(mod_key);
952       return rcube_event.cancel(e);
953     case 35: // End
954       this.select_last(mod_key);
955       return rcube_event.cancel(e);
6b47de 956     default:
110748 957       this.shiftkey = e.shiftKey;
6b47de 958       this.key_pressed = keyCode;
cc97ea 959       this.triggerEvent('keypress');
6e6e89 960       
f89f03 961       if (this.key_pressed == this.BACKSPACE_KEY)
6e6e89 962         return rcube_event.cancel(e);
6b47de 963   }
T 964   
965   return true;
966 },
967
9e7a1b 968 /**
T 969  * Handler for keydown events
970  */
971 key_down: function(e)
972 {
973   switch (rcube_event.get_keycode(e))
974   {
91d1a1 975     case 27:
A 976       if (this.drag_active)
1691a8 977     return this.drag_mouse_up(e);
A 978     
9e7a1b 979     case 40:
T 980     case 38: 
981     case 63233:
982     case 63232:
f52c93 983     case 61:
T 984     case 107:
985     case 109:
986     case 32:
9e7a1b 987       if (!rcube_event.get_modifier(e) && this.focused)
T 988         return rcube_event.cancel(e);
989         
990     default:
991   }
992   
993   return true;
994 },
995
6b47de 996
T 997 /**
998  * Special handling method for arrow keys
999  */
1000 use_arrow_key: function(keyCode, mod_key)
1001 {
1002   var new_row;
e9b57b 1003   // Safari uses the nonstandard keycodes 63232/63233 for up/down, if we're
A 1004   // using the keypress event (but not the keydown or keyup event).
1005   if (keyCode == 40 || keyCode == 63233) // down arrow key pressed
6b47de 1006     new_row = this.get_next_row();
e9b57b 1007   else if (keyCode == 38 || keyCode == 63232) // up arrow key pressed
6b47de 1008     new_row = this.get_prev_row();
T 1009
1010   if (new_row)
1011   {
1012     this.select_row(new_row.uid, mod_key, true);
1013     this.scrollto(new_row.uid);
1014   }
f52c93 1015
T 1016   return false;
1017 },
1018
1019
1020 /**
1021  * Special handling method for +/- keys
1022  */
1023 use_plusminus_key: function(keyCode, mod_key)
1024 {
1025   var selected_row = this.rows[this.last_selected];
1026   if (!selected_row)
1027     return;
1028
1029   if (keyCode == 32)
1030     keyCode = selected_row.expanded ? 109 : 61;
1031   if (keyCode == 61 || keyCode == 107)
1032     if (mod_key == CONTROL_KEY || this.multiexpand)
1033       this.expand_all(selected_row);
1034     else
1035      this.expand(selected_row);
1036   else
1037     if (mod_key == CONTROL_KEY || this.multiexpand)
1038       this.collapse_all(selected_row);
1039     else
1040       this.collapse(selected_row);
1041
1042   var expando = document.getElementById('rcmexpando' + selected_row.uid);
1043   if (expando)
1044     expando.className = selected_row.expanded?'expanded':'collapsed';
6b47de 1045
T 1046   return false;
1047 },
1048
1049
1050 /**
1051  * Try to scroll the list to make the specified row visible
1052  */
1053 scrollto: function(id)
1054 {
1055   var row = this.rows[id].obj;
1056   if (row && this.frame)
1057   {
1058     var scroll_to = Number(row.offsetTop);
1059
bc2acc 1060     // expand thread if target row is hidden (collapsed)
A 1061     if (!scroll_to && this.rows[id].parent_uid) {
1062       var parent = this.find_root(this.rows[id].uid);
1063       this.expand_all(this.rows[parent]);
1064       scroll_to = Number(row.offsetTop);
1065     }
1066
6b47de 1067     if (scroll_to < Number(this.frame.scrollTop))
T 1068       this.frame.scrollTop = scroll_to;
1069     else if (scroll_to + Number(row.offsetHeight) > Number(this.frame.scrollTop) + Number(this.frame.offsetHeight))
1070       this.frame.scrollTop = (scroll_to + Number(row.offsetHeight)) - Number(this.frame.offsetHeight);
1071   }
1072 },
1073
1074
1075 /**
1076  * Handler for mouse move events
1077  */
1078 drag_mouse_move: function(e)
1079 {
1080   if (this.drag_start)
1081   {
1082     // check mouse movement, of less than 3 pixels, don't start dragging
1083     var m = rcube_event.get_mouse_pos(e);
cf6bc5 1084
6b47de 1085     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))
T 1086       return false;
1087   
1088     if (!this.draglayer)
cc97ea 1089       this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body);
2ecb7f 1090       
T 1091     // also select childs of (collapsed) threads for dragging
1092     var selection = $.merge([], this.selection);
1093     var depth, row, uid, r;
1094     for (var n=0; n < selection.length; n++) {
1095       uid = selection[n];
84a331 1096       if (this.rows[uid].has_children && !this.rows[uid].expanded)
T 1097         this.select_childs(uid);
2ecb7f 1098     }
a80304 1099
f52c93 1100     // get subjects of selected messages
6b47de 1101     var names = '';
f52c93 1102     var c, i, subject, obj;
6b47de 1103     for(var n=0; n<this.selection.length; n++)
T 1104     {
1105       if (n>12)  // only show 12 lines
1106       {
1107         names += '...';
1108         break;
1109       }
1110
f52c93 1111       if (obj = this.rows[this.selection[n]].obj)
6b47de 1112       {
T 1113         subject = '';
1114
f52c93 1115         for (c=0, i=0; i<obj.childNodes.length; i++)
d24d20 1116         {
f52c93 1117       if (obj.childNodes[i].nodeName == 'TD')
6b47de 1118           {
f52c93 1119             if (n == 0)
T 1120           this.drag_start_pos = $(obj.childNodes[i]).offset();
1121
1122         if (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c))
1123         {
1124           var node, tmp_node, nodes = obj.childNodes[i].childNodes;
1125           // find text node
1126           for (m=0; m<nodes.length; m++) {
1127             if ((tmp_node = obj.childNodes[i].childNodes[m]) && (tmp_node.nodeType==3 || tmp_node.nodeName=='A'))
1128               node = tmp_node;
a80304 1129           }
f52c93 1130           
T 1131           if (!node)
1132             break;
1133
d24d20 1134               subject = node.nodeType==3 ? node.data : node.innerHTML;
451637 1135           // remove leading spaces
A 1136           subject = subject.replace(/^\s+/i, '');
1137               // truncate line to 50 characters
1138           names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
d24d20 1139               break;
T 1140             }
1141             c++;
6b47de 1142           }
d24d20 1143         }
6b47de 1144       }
T 1145     }
1146
cc97ea 1147     this.draglayer.html(names);
T 1148     this.draglayer.show();
6b47de 1149
T 1150     this.drag_active = true;
cc97ea 1151     this.triggerEvent('dragstart');
6b47de 1152   }
T 1153
1154   if (this.drag_active && this.draglayer)
1155   {
1156     var pos = rcube_event.get_mouse_pos(e);
cc97ea 1157     this.draglayer.css({ left:(pos.x+20)+'px', top:(pos.y-5 + (bw.ie ? document.documentElement.scrollTop : 0))+'px' });
9489ad 1158     this.triggerEvent('dragmove', e?e:window.event);
6b47de 1159   }
T 1160
1161   this.drag_start = false;
1162
1163   return false;
1164 },
1165
1166
1167 /**
1168  * Handler for mouse up events
1169  */
1170 drag_mouse_up: function(e)
1171 {
1172   document.onmousemove = null;
1173
cc97ea 1174   if (this.draglayer && this.draglayer.is(':visible')) {
T 1175     if (this.drag_start_pos)
1176       this.draglayer.animate(this.drag_start_pos, 300, 'swing').hide(20);
1177     else
1178       this.draglayer.hide();
1179   }
6b47de 1180
T 1181   this.drag_active = false;
cc97ea 1182   this.triggerEvent('dragend');
6b47de 1183
T 1184   rcube_event.remove_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
1185   rcube_event.remove_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
1186
91a35e 1187   var iframes = document.getElementsByTagName('iframe');
cf6bc5 1188   for (var n in iframes) {
A 1189     var iframedoc;
1190     
1191     if (iframes[n].contentDocument)
1192       iframedoc = iframes[n].contentDocument;
1193     else if (iframes[n].contentWindow)
1194       iframedoc = iframes[n].contentWindow.document;
1195     else if (iframes[n].document)
1196       iframedoc = iframes[n].document;
1197
1198     if (iframedoc) {
1199       if (this.iframe_events[n]) {
1200     if (iframedoc.removeEventListener)
1201       iframedoc.removeEventListener('mousemove', this.iframe_events[n], false);
1202     else if (iframedoc.detachEvent)
1203       iframedoc.detachEvent('onmousemove', this.iframe_events[n]);
1204     else
1205       iframedoc['onmousemove'] = null;
1206     }
1207       rcube_event.remove_listener({element:iframedoc, event:'mouseup', object:this, method:'drag_mouse_up'});
1208       }
1209     }
1210
6b47de 1211   return rcube_event.cancel(e);
c4b819 1212 },
A 1213
1214
1215 /**
1216  * Creating the list in background
1217  */
1218 set_background_mode: function(flag)
1219 {
1220   if (flag) {
91a35e 1221     this.background = document.createElement('tbody');
c4b819 1222   } else if (this.background) {
A 1223     this.list.replaceChild(this.background, this.list.tBodies[0]);
1224     this.background = null;
1225   }
6b47de 1226 }
T 1227
1228 };
1229
cc97ea 1230 rcube_list_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
T 1231 rcube_list_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
1232 rcube_list_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;