svncommit
2005-10-27 66773789e392305bba4cdf7ed8e6ae3b8380de51
commit | author | age
e0ed97 1 /*
4e17e6 2  +-----------------------------------------------------------------------+
T 3  | RoundCube Webmail Client Script                                       |
4  |                                                                       |
5  | This file is part of the RoundCube Webmail client                     |
6  | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
30233b 7  | Licensed under the GNU GPL                                            |
4e17e6 8  |                                                                       |
b076a4 9  | Modified: 2005/10/26 (roundcube)                                      |
4e17e6 10  |                                                                       |
T 11  +-----------------------------------------------------------------------+
12  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
13  +-----------------------------------------------------------------------+
14 */
15
16
17 var rcube_webmail_client;
18
19 function rcube_webmail()
20   {
21   this.env = new Object();
22   this.buttons = new Object();
23   this.gui_objects = new Object();
24   this.commands = new Object();
25   this.selection = new Array();
26
27   // create public reference to myself
28   rcube_webmail_client = this;
29   this.ref = 'rcube_webmail_client';
30  
31   // webmail client settings
32   this.dblclick_time = 600;
33   this.message_time = 5000;
a95e0e 34   this.request_timeout = 120000;
4e17e6 35   this.mbox_expression = new RegExp('[^0-9a-z\-_]', 'gi');
T 36   this.env.blank_img = 'skins/default/images/blank.gif';
37   
38   // mimetypes supported by the browser (default settings)
39   this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
40                              'image/jpeg', 'image/gif', 'image/png',
41                              'application/x-javascript', 'application/pdf',
42                              'application/x-shockwave-flash');
43
44
45   // set environment variable
46   this.set_env = function(name, value)
47     {
48     //if (!this.busy)
49       this.env[name] = value;    
50     };
51
52   // add a button to the button list
53   this.register_button = function(command, id, type, act, sel, over)
54     {
55     if (!this.buttons[command])
56       this.buttons[command] = new Array();
57       
58     var button_prop = {id:id, type:type};
59     if (act) button_prop.act = act;
60     if (sel) button_prop.sel = sel;
61     if (over) button_prop.over = over;
62
63     this.buttons[command][this.buttons[command].length] = button_prop;    
64     };
65
66
67   // register a specific gui object
68   this.gui_object = function(name, id)
69     {
70     this.gui_objects[name] = id;
71     };
72
73
74   // initialize webmail client
75   this.init = function()
76     {
77     this.task = this.env.task;
78     
79     // check browser
a95e0e 80     if (!bw.dom || !bw.xmlhttp_test())
4e17e6 81       {
T 82       location.href = this.env.comm_path+'&_action=error&_code=0x199';
83       return;
84       }
85     
86     // find all registered gui objects
87     for (var n in this.gui_objects)
88       this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
89       
90     // tell parent window that this frame is loaded
91     if (this.env.framed && parent.rcmail && parent.rcmail.set_busy)
92       parent.rcmail.set_busy(false);
93
94     // enable general commands
95     this.enable_command('logout', 'mail', 'addressbook', 'settings', true);
96     
97     switch (this.task)
98       {
99       case 'mail':
100         var msg_list = this.gui_objects.messagelist;
101         if (msg_list)
102           {
103           this.init_messagelist(msg_list);
104           this.enable_command('markread', true);
105           }
106
107         // enable mail commands
108         this.enable_command('list', 'compose', 'add-contact', true);
109         
110         if (this.env.action=='show')
111           {
112           this.enable_command('show', 'reply', 'forward', 'moveto', 'delete', 'viewsource', 'print', 'load-attachment', true);
113           if (this.env.next_uid)
114             this.enable_command('nextmessage', true);
115           if (this.env.prev_uid)
116             this.enable_command('previousmessage', true);
117           }
118
119         if (this.env.action=='show' && this.env.blockedobjects)
120           {
121           if (this.gui_objects.remoteobjectsmsg)
122             this.gui_objects.remoteobjectsmsg.style.display = 'block';
123           this.enable_command('load-images', true);
124           }  
125
126         if (this.env.action=='compose')
127           this.enable_command('add-attachment', 'send-attachment', 'send', true);
128           
129         if (this.env.messagecount)
f3b659 130           this.enable_command('select-all', 'select-none', 'sort', true);
4e17e6 131
T 132         this.set_page_buttons();
133
134         // focus this window
135         window.focus();
136
137         // init message compose form
138         if (this.env.action=='compose')
139           this.init_messageform();
140
141         // show printing dialog
142         if (this.env.action=='print')
143           window.print();
144
145         break;
146
147
148       case 'addressbook':
149         var contacts_list = this.gui_objects.contactslist;
150         if (contacts_list)
151           this.init_contactslist(contacts_list);
152       
153         this.set_page_buttons();
154           
155         if (this.env.cid)
156           this.enable_command('show', 'edit', true);
157
158         if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
159           this.enable_command('save', true);
160       
161         this.enable_command('list', 'add', true);
162         break;
163
164
165       case 'settings':
166         this.enable_command('preferences', 'identities', 'save', 'folders', true);
167         
168         if (this.env.action=='identities' || this.env.action=='edit-identity' || this.env.action=='add-identity')
169           this.enable_command('edit', 'add', 'delete', true);
170
171         if (this.env.action=='edit-identity' || this.env.action=='add-identity')
172           this.enable_command('save', true);
173           
174         if (this.env.action=='folders')
175           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'delete-folder', true);
176           
177         var identities_list = this.gui_objects.identitieslist;
178         if (identities_list)
179           this.init_identitieslist(identities_list);
180
181         break;
182
183       case 'login':
184         var input_user = rcube_find_object('_user');
185         var input_pass = rcube_find_object('_pass');
186         if (input_user && input_user.value=='')
187           input_user.focus();
188         else if (input_pass)
189           input_pass.focus();
190           
191         this.enable_command('login', true);
192         break;
193       
194       default:
195         break;
196       }
197
198
199     // enable basic commands
200     this.enable_command('logout', true);
201
202     // disable browser's contextmenus
203     //document.oncontextmenu = function(){ return false; }
204
205     // flag object as complete
206     this.loaded = true;
7902df 207           
4e17e6 208     // show message
T 209     if (this.pending_message)
210       this.display_message(this.pending_message[0], this.pending_message[1]);
211     };
212
213
214   // get all message rows from HTML table and init each row
215   this.init_messagelist = function(msg_list)
216     {
217     if (msg_list && msg_list.tBodies[0])
218       {
219       this.message_rows = new Array();
220
221       var row;
222       for(var r=0; r<msg_list.tBodies[0].childNodes.length; r++)
223         {
224         row = msg_list.tBodies[0].childNodes[r];
225         //row = msg_list.tBodies[0].rows[r];
226         this.init_message_row(row);
227         }
228       }
229       
230     // alias to common rows array
231     this.list_rows = this.message_rows;
232     };
233     
234     
235   // make references in internal array and set event handlers
236   this.init_message_row = function(row)
237     {
238     var uid, msg_icon;
239     
240     if (String(row.id).match(/rcmrow([0-9]+)/))
241       {
242       uid = RegExp.$1;
243       row.uid = uid;
244               
245       this.message_rows[uid] = {id:row.id, obj:row,
246                                 classname:row.className,
247                                 unread:this.env.messages[uid] ? this.env.messages[uid].unread : null,
248                                 replied:this.env.messages[uid] ? this.env.messages[uid].replied : null};
249               
250       // set eventhandlers to table row
251       row.onmousedown = function(e){ return rcube_webmail_client.drag_row(e, this.uid); };
252       row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.uid); };
253               
254       // set eventhandler to message icon
255       if ((msg_icon = row.cells[0].childNodes[0]) && row.cells[0].childNodes[0].nodeName=='IMG')
256         {                
257         msg_icon.id = 'msgicn_'+uid;
258         msg_icon._row = row;
259         msg_icon.onmousedown = function(e) { rcube_webmail_client.command('markread', this); };
260                 
261         // get message icon and save original icon src
262         this.message_rows[uid].icon = msg_icon;
263         }
264       }
265     };
266
267
268   // init message compose form: set focus and eventhandlers
269   this.init_messageform = function()
270     {
271     if (!this.gui_objects.messageform)
272       return false;
273     
274     //this.messageform = this.gui_objects.messageform;
275     var input_to = rcube_find_object('_to');
276     var input_cc = rcube_find_object('_cc');
277     var input_bcc = rcube_find_object('_bcc');
278     var input_replyto = rcube_find_object('_replyto');
279     var input_subject = rcube_find_object('_subject');
280     var input_message = rcube_find_object('_message');
281     
282     // init live search events
283     if (input_to)
284       this.init_address_input_events(input_to);
285     if (input_cc)
286       this.init_address_input_events(input_cc);
287     if (input_bcc)
288       this.init_address_input_events(input_bcc);
289
290     if (input_to && input_to.value=='')
291       input_to.focus();
292     else if (input_subject && input_subject.value=='')
293       input_subject.focus();
294     else if (input_message)
295       this.set_caret2start(input_message); // input_message.focus();
296     };
297
298
299   this.init_address_input_events = function(obj)
300     {
301     var handler = function(e){ return rcube_webmail_client.ksearch_keypress(e,this); };
302     var handler2 = function(e){ return rcube_webmail_client.ksearch_blur(e,this); };
303     
304     if (bw.safari)
305       {
306       obj.addEventListener('keydown', handler, false);
307       // obj.addEventListener('blur', handler2, false);
308       }
309     else if (bw.mz)
310       {
311       obj.addEventListener('keypress', handler, false);
312       obj.addEventListener('blur', handler2, false);
313       }
314     else if (bw.ie)
315       {
316       obj.onkeydown = handler;
317       //obj.attachEvent('onkeydown', handler);
318       // obj.attachEvent('onblur', handler2, false);
319       }
320     
321     obj.setAttribute('autocomplete', 'off');       
322     };
323
324
325
326   // get all contact rows from HTML table and init each row
327   this.init_contactslist = function(contacts_list)
328     {
329     if (contacts_list && contacts_list.tBodies[0])
330       {
331       this.contact_rows = new Array();
332
333       var row;
334       for(var r=0; r<contacts_list.tBodies[0].childNodes.length; r++)
335         {
336         row = contacts_list.tBodies[0].childNodes[r];
337         this.init_table_row(row, 'contact_rows');
338         }
339       }
340
341     // alias to common rows array
342     this.list_rows = this.contact_rows;
343     
344     if (this.env.cid)
345       this.select(this.env.cid);
346     };
347
348
349   // make references in internal array and set event handlers
350   this.init_table_row = function(row, array_name)
351     {
352     var cid;
353     
354     if (String(row.id).match(/rcmrow([0-9]+)/))
355       {
356       cid = RegExp.$1;
357       row.cid = cid;
358
359       this[array_name][cid] = {id:row.id,
360                                obj:row,
361                                classname:row.className};
362
363       // set eventhandlers to table row
364       row.onmousedown = function(e) { rcube_webmail_client.in_selection_before=this.cid; return false; };  // fake for drag handler
365       row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.cid); };
366       }
367     };
368
369
370   // get all contact rows from HTML table and init each row
371   this.init_identitieslist = function(identities_list)
372     {
373     if (identities_list && identities_list.tBodies[0])
374       {
375       this.identity_rows = new Array();
376
377       var row;
378       for(var r=0; r<identities_list.tBodies[0].childNodes.length; r++)
379         {
380         row = identities_list.tBodies[0].childNodes[r];
381         this.init_table_row(row, 'identity_rows');
382         }
383       }
384
385     // alias to common rows array
386     this.list_rows = this.identity_rows;
387     
388     if (this.env.iid)
389       this.select(this.env.iid);    
390     };
391     
392
393
394   /*********************************************************/
395   /*********       client command interface        *********/
396   /*********************************************************/
397
398
399   // execute a specific command on the web client
400   this.command = function(command, props, obj)
401     {
402     if (obj && obj.blur)
403       obj.blur();
404
405     if (this.busy)
406       return false;
407
408     // command not supported or allowed
409     if (!this.commands[command])
410       {
411       // pass command to parent window
412       if (this.env.framed && parent.rcmail && parent.rcmail.command)
413         parent.rcmail.command(command, props);
414
415       return false;
416       }
417
418     // process command
419     switch (command)
420       {
421       case 'login':
422         if (this.gui_objects.loginform)
423           this.gui_objects.loginform.submit();
424         break;
425
426       case 'logout':
427         location.href = this.env.comm_path+'&_action=logout';
428         break;      
429
430       // commands to switch task
431       case 'mail':
432       case 'addressbook':
433       case 'settings':
434         this.switch_task(command);
435         break;
436
437
438       // misc list commands
439       case 'list':
440         if (this.task=='mail')
441           this.list_mailbox(props);
442         else if (this.task=='addressbook')
443           this.list_contacts();
f3b659 444         break;
T 445
446       case 'sort':
447         // get the type of sorting
b076a4 448         var a_sort = props.split('_');
T 449         var sort_col = a_sort[0];
450         var sort_order = a_sort[1].toUpperCase();
451         var header;
452         
453         if (this.env.sort_col==sort_col && this.env.sort_order==sort_order)
454           break;
455
456         // set table header class
457         if (header = document.getElementById('rcmHead'+this.env.sort_col))
458           this.set_classname(header, 'sorted'+(this.env.sort_order.toUpperCase()), false);
459         if (header = document.getElementById('rcmHead'+sort_col))
460           this.set_classname(header, 'sorted'+sort_order, true);
461
462         // save new sort properties
463         this.env.sort_col = sort_col;
464         this.env.sort_order = sort_order;
465
466         // reload message list
f3b659 467         this.list_mailbox('', '', props);
4e17e6 468         break;
T 469
470       case 'nextpage':
471         this.list_page('next');
472         break;
473
474       case 'previouspage':
475         this.list_page('prev');
476         break;
477
478
479       // common commands used in multiple tasks
480       case 'show':
481         if (this.task=='mail')
482           {
483           var uid = this.get_single_uid();
484           if (uid && (!this.env.uid || uid != this.env.uid))
485             this.show_message(uid);
486           }
487         else if (this.task=='addressbook')
488           {
489           var cid = props ? props : this.get_single_cid();
490           if (cid && !(this.env.action=='show' && cid==this.env.cid))
491             this.load_contact(cid, 'show');
492           }
493         break;
494
495       case 'add':
496         if (this.task=='addressbook')
497           this.load_contact(0, 'add');
498         else if (this.task=='settings')
499           {
500           this.clear_selection();
501           this.load_identity(0, 'add-identity');
502           }
503         break;
504
505       case 'edit':
506         var cid;
507         if (this.task=='addressbook' && (cid = this.get_single_cid()))
508           this.load_contact(cid, 'edit');
509         else if (this.task=='settings' && props)
510           this.load_identity(props, 'edit-identity');
511         break;
512
513       case 'save-identity':
514       case 'save':
515         if (this.gui_objects.editform)
516           this.gui_objects.editform.submit();
517         break;
518
519       case 'delete':
520         // mail task
521         if (this.task=='mail' && this.env.trash_mailbox && String(this.env.mailbox).toLowerCase()!=String(this.env.trash_mailbox).toLowerCase())
522           this.move_messages(this.env.trash_mailbox);
523         else if (this.task=='mail')
524           this.delete_messages();
525         // addressbook task
526         else if (this.task=='addressbook')
527           this.delete_contacts();
528         // user settings task
529         else if (this.task=='settings')
530           this.delete_identity();
531         break;
532
533
534       // mail task commands
535       case 'move':
536       case 'moveto':
537         this.move_messages(props);
538         break;
539         
540       case 'markread':
541         if (props && !props._row)
542           break;
543         
544         var uid;
545         var flag = 'read';
546         
547         if (props._row.uid)
548           {
549           uid = props._row.uid;
550           this.dont_select = true;
551           
552           // toggle read/unread
553           if (!this.message_rows[uid].unread)
554             flag = 'unread';
555           }
556           
557         this.mark_message(flag, uid);
558         break;
559         
560       case 'load-images':
561         if (this.env.uid)
562           this.show_message(this.env.uid, true);
563         break;
564
565       case 'load-attachment':
566         var url = this.env.comm_path+'&_action=get&_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part;
567         
568         // open attachment in frame if it's of a supported mimetype
569         if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0)
570           {
571           this.attachment_win = window.open(url+'&_frame=1', 'rcubemailattachment');
572           if (this.attachment_win)
573             {
574             setTimeout(this.ref+'.attachment_win.focus()', 10);
575             break;
576             }
577           }
578
579         location.href = url;
580         break;
581         
582       case 'select-all':
583         this.select_all(props);
584         break;
585
586       case 'select-none':
587         this.clear_selection();
588         break;
589
590       case 'nextmessage':
591         if (this.env.next_uid)
09941e 592           this.show_message(this.env.next_uid);
T 593           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.next_uid+'&_mbox='+this.env.mailbox;
4e17e6 594         break;
T 595
596       case 'previousmessage':
597         if (this.env.prev_uid)
09941e 598           this.show_message(this.env.prev_uid);
T 599           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.prev_uid+'&_mbox='+this.env.mailbox;
4e17e6 600         break;
T 601
602       case 'compose':
603         var url = this.env.comm_path+'&_action=compose';
604         
605         // modify url if we're in addressbook
606         if (this.task=='addressbook')
607           {
608           url = this.get_task_url('mail', url);            
609           var a_cids = new Array();
610           
611           // use contact_id passed as command parameter
612           if (props)
613             a_cids[a_cids.length] = props;
614             
615           // get selected contacts
616           else
617             {
618             for (var n=0; n<this.selection.length; n++)
619               a_cids[a_cids.length] = this.selection[n];
620             }
621
622           if (a_cids.length)
623             url += '&_to='+a_cids.join(',');
624           else
625             break;
626           }
627         else if (props)
628            url += '&_to='+props;
629
630         this.set_busy(true);
631         location.href = url;
632         break;      
633
634       case 'send':
635         if (!this.gui_objects.messageform)
636           break;
637           
638         // check input fields
639         var input_to = rcube_find_object('_to');
640         var input_subject = rcube_find_object('_subject');
641         var input_message = rcube_find_object('_message');
642         
643         if (input_to.value!='' && input_message.value!='')
644           {
645           this.set_busy(true, 'sendingmessage');
646           var form = this.gui_objects.messageform;
647           form.submit();
648           }
649           
650         break;
651
652       case 'add-attachment':
653         this.show_attachment_form(true);
654         
655       case 'send-attachment':
656         this.upload_file(props)      
657         break;
658
659       case 'reply':
660         var uid;
661         if (uid = this.get_single_uid())
662           {
663           this.set_busy(true);
664           location.href = this.env.comm_path+'&_action=compose&_reply_uid='+uid+'&_mbox='+escape(this.env.mailbox);
665           }
666         break;      
667
668       case 'forward':
669         var uid;
670         if (uid = this.get_single_uid())
671           {
672           this.set_busy(true);
673           location.href = this.env.comm_path+'&_action=compose&_forward_uid='+uid+'&_mbox='+escape(this.env.mailbox);
674           }
675         break;
676         
677       case 'print':
678         var uid;
679         if (uid = this.get_single_uid())
680           {
681           this.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+escape(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
682           if (this.printwin)
683             setTimeout(this.ref+'.printwin.focus()', 20);
684           }
685         break;
686
687       case 'viewsource':
688         var uid;
689         if (uid = this.get_single_uid())
690           {          
691           this.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+this.env.uid+'&_mbox='+escape(this.env.mailbox));
692           if (this.sourcewin)
693             setTimeout(this.ref+'.sourcewin.focus()', 20);
694           }
695         break;
696
697       case 'add-contact':
698         this.add_contact(props);
699         break;
700
701
702       // user settings commands
703       case 'preferences':
704         location.href = this.env.comm_path;
705         break;
706
707       case 'identities':
708         location.href = this.env.comm_path+'&_action=identities';
709         break;
710           
711       case 'delete-identity':
712         this.delete_identity();
713         
714       case 'folders':
715         location.href = this.env.comm_path+'&_action=folders';
716         break;
717
718       case 'subscribe':
719         this.subscribe_folder(props);
720         break;
721
722       case 'unsubscribe':
723         this.unsubscribe_folder(props);
724         break;
725         
726       case 'create-folder':
727         this.create_folder(props);
728         break;
729
730       case 'delete-folder':
731         if (confirm('Do you really want to delete this folder?'))
732           this.delete_folder(props);
733         break;
734
735       }
736
737     return obj ? false : true;
738     };
739
740
741   // set command enabled or disabled
742   this.enable_command = function()
743     {
744     var args = this.enable_command.arguments;
745     if(!args.length) return -1;
746
747     var command;
748     var enable = args[args.length-1];
749     
750     for(var n=0; n<args.length-1; n++)
751       {
752       command = args[n];
753       this.commands[command] = enable;
754       this.set_button(command, (enable ? 'act' : 'pas'));
755       }
756     };
757
758
a95e0e 759   // lock/unlock interface
4e17e6 760   this.set_busy = function(a, message)
T 761     {
762     if (a && message)
763       this.display_message('Loading...', 'loading', true);
764     else if (!a && this.busy)
765       this.hide_message();
766
767     this.busy = a;
768     //document.body.style.cursor = a ? 'wait' : 'default';
769     
770     if (this.gui_objects.editform)
771       this.lock_form(this.gui_objects.editform, a);
a95e0e 772       
T 773     // clear pending timer
774     if (this.request_timer)
775       clearTimeout(this.request_timer);
776
777     // set timer for requests
778     if (a && this.request_timeout)
779       this.request_timer = setTimeout(this.ref+'.request_timed_out()', this.request_timeout);
4e17e6 780     };
T 781
782
783   this.switch_task = function(task)
784     {
01bb03 785     if (this.task===task && task!='mail')
4e17e6 786       return;
T 787
01bb03 788     var url = this.get_task_url(task);
T 789     if (task=='mail')
790       url += '&_mbox=INBOX';
791
4e17e6 792     this.set_busy(true);
01bb03 793     location.href = url;
4e17e6 794     };
T 795
796
797   this.get_task_url = function(task, url)
798     {
799     if (!url)
800       url = this.env.comm_path;
801
802     return url.replace(/_task=[a-z]+/, '_task='+task);
a95e0e 803     };
T 804     
805   
806   // called when a request timed out
807   this.request_timed_out = function()
808     {
809     this.set_busy(false);
810     this.display_message('Request timed out!', 'error');
4e17e6 811     };
T 812
813
814   /*********************************************************/
815   /*********        event handling methods         *********/
816   /*********************************************************/
817
818
819   // onmouseup handler for mailboxlist item
820   this.mbox_mouse_up = function(mbox)
821     {
822     if (this.drag_active)
823       this.command('moveto', mbox);
824     else
825       this.command('list', mbox);
826       
827     return false;
828     };
829
830
831   // onmousedown-handler of message list row
832   this.drag_row = function(e, id)
833     {
834     this.in_selection_before = this.in_selection(id) ? id : false;
835
836     // don't do anything (another action processed before)
837     if (this.dont_select)
838       return false;
839
840     if (!this.in_selection_before)
841       {
842       var shift = this.check_shiftkey(e);
843       this.select(id, shift);
844       }
845     
846     if (this.selection.length)
847       {
848       this.drag_start = true;
849       document.onmousemove = function(e){ return rcube_webmail_client.drag_mouse_move(e); };
850       document.onmouseup = function(e){ return rcube_webmail_client.drag_mouse_up(e); };
851       }
852
853     return false;
854     };
855
856
857   // onmouseup-handler of message list row
858   this.click_row = function(e, id)
859     {
860     var shift = this.check_shiftkey(e);
861     
862     // don't do anything (another action processed before)
863     if (this.dont_select)
864       {
865       this.dont_select = false;
866       return false;
867       }
868     
869     if (!this.drag_active && this.in_selection_before==id)
870       this.select(id, (shift && this.task!='settings'));
871     
872     this.drag_start = false;
873     this.in_selection_before = false;
874         
875     // row was double clicked
876     if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && !shift)
877       {
878       this.show_message(id);
879       return false;
880       }
881     else if (this.task=='addressbook')
882       {
883       if (this.selection.length==1 && this.env.contentframe)
884         this.load_contact(this.selection[0], 'show', true);
885       else if (this.task=='addressbook' && this.list_rows && this.list_rows[id].clicked)
886         {
887         this.load_contact(id, 'show');
888         return false;
889         }
890       else if (this.env.contentframe)
891         {
892         var elm = document.getElementById(this.env.contentframe);
893         elm.style.visibility = 'hidden';
894         }
895       }
896     else if (this.task=='settings')
897       {
898       if (this.selection.length==1)
899         this.command('edit', this.selection[0]);
900       }
901
902     this.list_rows[id].clicked = true;
903     setTimeout(this.ref+'.list_rows['+id+'].clicked=false;', this.dblclick_time);
904       
905     return false;
906     };
907
908
909
910
911   /*********************************************************/
912   /*********     (message) list functionality      *********/
913   /*********************************************************/
914
915
916   // select/unselect a row
917   this.select = function(id, multiple)
918     {
919     var selected = false
920     
921     if (this.list_rows[id] && !multiple)
922       {
923       this.clear_selection();
924       this.selection[0] = id;
925       this.list_rows[id].obj.className += ' selected';
926       selected = true;
927       }
928     
929     else if (this.list_rows[id])
930       {
931       if (!this.in_selection(id))  // select row
932         {
933         this.selection[this.selection.length] = id;
934         this.set_classname(this.list_rows[id].obj, 'selected', true);        
935         }
936       else  // unselect row
937         {
938         var p = find_in_array(id, this.selection);
939         var a_pre = this.selection.slice(0, p);
940         var a_post = this.selection.slice(p+1, this.selection.length);
941         this.selection = a_pre.concat(a_post);
942         this.set_classname(this.list_rows[id].obj, 'selected', false);
943         }
944         
945       selected = (this.selection.length==1);
946       }
947
948     // enable/disable commands for message
949     if (this.task=='mail')
950       {
951       this.enable_command('show', 'reply', 'forward', 'print', selected);
952       this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
953       }
954     else if (this.task=='addressbook')
955       {
956       this.enable_command('edit', /*'print',*/ selected);
957       this.enable_command('delete', 'compose', this.selection.length>0 ? true : false);
958       }
959     };
960
961
962   this.clear_selection = function()
963     {
964     for(var n=0; n<this.selection.length; n++)
965       if (this.list_rows[this.selection[n]])
966         this.set_classname(this.list_rows[this.selection[n]].obj, 'selected', false);
967
968     this.selection = new Array();    
969     };
970
971
972   // check if given id is part of the current selection
973   this.in_selection = function(id)
974     {
975     for(var n in this.selection)
976       if (this.selection[n]==id)
977         return true;
978
979     return false;    
980     };
981
982
983   // select each row in list
984   this.select_all = function(filter)
985     {
986     if (!this.list_rows || !this.list_rows.length)
987       return false;
988       
989     // reset selection first
990     this.clear_selection();
991     
992     for (var n in this.list_rows)
993       if (!filter || this.list_rows[n][filter]==true)
994       this.select(n, true);
995     };
996     
997
998   // when user doble-clicks on a row
999   this.show_message = function(id, safe)
1000     {
1001     var add_url = '';
1002     var target = window;
1003     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1004       {
1005       target = window.frames[this.env.contentframe];
1006       add_url = '&_framed=1';
1007       }
1008       
1009     if (safe)
1010       add_url = '&_safe=1';
1011
1012     if (id)
1013       {
09941e 1014       this.set_busy(true, 'loading');
4e17e6 1015       target.location.href = this.env.comm_path+'&_action=show&_uid='+id+'&_mbox='+escape(this.env.mailbox)+add_url;
T 1016       }
1017     };
1018
1019
1020
1021   // list a specific page
1022   this.list_page = function(page)
1023     {
1024     if (page=='next')
1025       page = this.env.current_page+1;
1026     if (page=='prev' && this.env.current_page>1)
1027       page = this.env.current_page-1;
1028       
1029     if (page > 0 && page <= this.env.pagecount)
1030       {
1031       this.env.current_page = page;
1032       
1033       if (this.task=='mail')
1034         this.list_mailbox(this.env.mailbox, page);
1035       else if (this.task=='addressbook')
1036         this.list_contacts(page);
1037       }
1038     };
1039
1040
1041   // list messages of a specific mailbox
f3b659 1042   this.list_mailbox = function(mbox, page, sort)
4e17e6 1043     {
T 1044     var add_url = '';
1045     var target = window;
1046
1047     if (!mbox)
1048       mbox = this.env.mailbox;
1049
f3b659 1050     // add sort to url if set
T 1051     if (sort)
1052       add_url += '&_sort=' + sort;
1053       
4e17e6 1054     // set page=1 if changeing to another mailbox
T 1055     if (!page && mbox != this.env.mailbox)
1056       {
1057       page = 1;
9fee0e 1058       add_url += '&_refresh=1';
4e17e6 1059       this.env.current_page = page;
T 1060       this.clear_selection();
1061       }
1062       
1063     if (this.env.mailbox!=mbox)
1064       this.select_mailbox(mbox);
1065
1066     // load message list remotely
1067     if (this.gui_objects.messagelist)
1068       {
9fee0e 1069       this.list_mailbox_remote(mbox, page, add_url);
4e17e6 1070       return;
T 1071       }
1072     
1073     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1074       {
1075       target = window.frames[this.env.contentframe];
9fee0e 1076       add_url += '&_framed=1';
4e17e6 1077       }
T 1078
1079     // load message list to target frame/window
1080     if (mbox)
1081       {
1082       this.set_busy(true, 'loading');
1083       target.location.href = this.env.comm_path+'&_mbox='+escape(mbox)+(page ? '&_page='+page : '')+add_url;
1084       }
1085     };
1086
1087
1088   // send remote request to load message list
9fee0e 1089   this.list_mailbox_remote = function(mbox, page, add_url)
4e17e6 1090     {
T 1091     // clear message list
1092     var table = this.gui_objects.messagelist;
1093     var tbody = document.createElement('TBODY');
1094     table.insertBefore(tbody, table.tBodies[0]);
1095     table.removeChild(table.tBodies[1]);
1096     
1097     this.message_rows = new Array();
1098     this.list_rows = this.message_rows;
1099
1100     // send request to server
1101     var url = '_mbox='+escape(mbox)+(page ? '&_page='+page : '');
1102     this.set_busy(true, 'loading');
9fee0e 1103     this.http_request('list', url+add_url);
4e17e6 1104     };
T 1105
1106
1107   // move selected messages to the specified mailbox
1108   this.move_messages = function(mbox)
1109     {
1110     // exit if no mailbox specified or if selection is empty
1111     if (!mbox || !(this.selection.length || this.env.uid) || mbox==this.env.mailbox)
1112       return;
1113     
1114     var a_uids = new Array();
1115
1116     if (this.env.uid)
1117       a_uids[a_uids.length] = this.env.uid;
1118     else
1119       {
1120       var id;
1121       for (var n=0; n<this.selection.length; n++)
1122         {
1123         id = this.selection[n];
1124         a_uids[a_uids.length] = id;
1125       
1126         // 'remove' message row from list (just hide it)
1127         if (this.message_rows[id].obj)
1128           this.message_rows[id].obj.style.display = 'none';
1129         }
1130       }
1131
1132     // show wait message
1133     if (this.env.action=='show')
1134       this.set_busy(true, 'movingmessage');
1135
1136     // send request to server
1137     this.http_request('moveto', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_target_mbox='+escape(mbox)+'&_from='+(this.env.action ? this.env.action : ''));
1138     };
1139
1140
1141   // delete selected messages from the current mailbox
1142   this.delete_messages = function()
1143     {
1144     // exit if no mailbox specified or if selection is empty
1145     if (!(this.selection.length || this.env.uid))
1146       return;
1147     
1148     var a_uids = new Array();
1149
1150     if (this.env.uid)
1151       a_uids[a_uids.length] = this.env.uid;
1152     else
1153       {
1154       var id;
1155       for (var n=0; n<this.selection.length; n++)
1156         {
1157         id = this.selection[n];
1158         a_uids[a_uids.length] = id;
1159       
1160         // 'remove' message row from list (just hide it)
1161         if (this.message_rows[id].obj)
1162           this.message_rows[id].obj.style.display = 'none';
1163         }
1164       }
1165
1166     // send request to server
1167     this.http_request('delete', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_from='+(this.env.action ? this.env.action : ''));
1168     };
1169
1170
1171   // set a specific flag to one or more messages
1172   this.mark_message = function(flag, uid)
1173     {
1174     var a_uids = new Array();
1175     
1176     if (uid)
1177       a_uids[0] = uid;
1178     else if (this.env.uid)
1179       a_uids[0] = this.env.uid;
1180     else
1181       {
1182       var id;
1183       for (var n=0; n<this.selection.length; n++)
1184         {
1185         id = this.selection[n];
1186         a_uids[a_uids.length] = id;
1187       
1188         // 'remove' message row from list (just hide it)
1189         if (this.message_rows[id].obj)
1190           this.message_rows[id].obj.style.display = 'none';
1191         }
1192       }
1193
1194     // mark all message rows as read/unread
1195     var icn_src;
1196     for (var i=0; i<a_uids.length; i++)
1197       {
1198       uid = a_uids[i];
1199       if (this.message_rows[uid])
1200         {
1201         this.message_rows[uid].unread = (flag=='unread' ? true : false);
1202         
1203         if (this.message_rows[uid].classname.indexOf('unread')<0 && this.message_rows[uid].unread)
1204           {
1205           this.message_rows[uid].classname += ' unread';
1206           if (!this.in_selection(uid))
1207             this.message_rows[uid].obj.className += ' unread';
1208           if (this.env.unreadicon)
1209             icn_src = this.env.unreadicon;
1210           }
1211         else if (!this.message_rows[uid].unread)
1212           {
1213           this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*unread/, '');
1214           if (!this.in_selection(uid))
1215             this.message_rows[uid].obj.className = this.message_rows[uid].obj.className.replace(/\s*unread/, '');
1216
1217           if (this.message_rows[uid].replied && this.env.repliedicon)
1218             icn_src = this.env.repliedicon;
1219           else if (this.env.messageicon)
1220             icn_src = this.env.messageicon;
1221           }
1222
1223         if (this.message_rows[uid].icon && icn_src)
1224           this.message_rows[uid].icon.src = icn_src;
1225         }
1226       }
1227
1228     // send request to server
1229     this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
1230     };
1231
1232
1233
1234   /*********************************************************/
1235   /*********        message compose methods        *********/
1236   /*********************************************************/
1237
1238
1239   this.show_attachment_form = function(a)
1240     {
1241     if (!this.gui_objects.uploadbox)
1242       return false;
1243       
1244     var elm, list;
1245     if (elm = this.gui_objects.uploadbox)
1246       {
1247       if (a &&  (list = this.gui_objects.attachmentlist))
1248         {
1249         var pos = rcube_get_object_pos(list);
1250         var left = pos.x;
1251         var top = pos.y + list.offsetHeight + 10;
1252       
1253         elm.style.top = top+'px';
1254         elm.style.left = left+'px';
1255         }
1256       
1257       elm.style.visibility = a ? 'visible' : 'hidden';
1258       }
1259       
1260     // clear upload form
1261     if (!a && this.gui_objects.attachmentform && this.gui_objects.attachmentform!=this.gui_objects.messageform)
1262       this.gui_objects.attachmentform.reset();
1263     };
1264
1265
1266   // upload attachment file
1267   this.upload_file = function(form)
1268     {
1269     if (!form)
1270       return false;
1271       
1272     // get file input fields
1273     var send = false;
1274     for (var n=0; n<form.elements.length; n++)
1275       if (form.elements[n].type=='file' && form.elements[n].value)
1276         {
1277         send = true;
1278         break;
1279         }
1280     
1281     // create hidden iframe and post upload form
1282     if (send)
1283       {
1284       var ts = new Date().getTime();
1285       var frame_name = 'rcmupload'+ts;
1286
1287       // have to do it this way for IE
1288       // otherwise the form will be posted to a new window
1289       if(document.all && !window.opera)
1290         {
1291         var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
1292         document.body.insertAdjacentHTML('BeforeEnd',html);
1293         }
1294       else  // for standards-compilant browsers
1295         {
1296         var frame = document.createElement('IFRAME');
1297         frame.name = frame_name;
1298         frame.width = 10;
1299         frame.height = 10;
1300         frame.style.visibility = 'hidden';
1301         document.body.appendChild(frame);
1302         }
1303
1304       form.target = frame_name;
1305       form.action = this.env.comm_path+'&_action=upload';
1306       form.setAttribute('enctype', 'multipart/form-data');
1307       form.submit();
1308       }
1309     
1310     // set reference to the form object
1311     this.gui_objects.attachmentform = form;
1312     };
1313
1314
1315   // add file name to attachment list
1316   // called from upload page
1317   this.add2attachment_list = function(name)
1318     {
1319     if (!this.gui_objects.attachmentlist)
1320       return false;
1321       
1322     var li = document.createElement('LI');
1323     li.innerHTML = name;
1324     this.gui_objects.attachmentlist.appendChild(li);
1325     };
1326
1327
1328   // send remote request to add a new contact
1329   this.add_contact = function(value)
1330     {
1331     if (value)
1332       this.http_request('addcontact', '_address='+value);
1333     };
1334
1335
1336   /*********************************************************/
1337   /*********     keyboard live-search methods      *********/
1338   /*********************************************************/
1339
1340
1341   // handler for keyboard events on address-fields
1342   this.ksearch_keypress = function(e, obj)
1343     {
1344     if (typeof(this.env.contacts)!='object' || !this.env.contacts.length)
1345       return true;
1346
1347     if (this.ksearch_timer)
1348       clearTimeout(this.ksearch_timer);
1349
1350     if (!e)
1351       e = window.event;
1352       
1353     var highlight;
1354     var key = e.keyCode ? e.keyCode : e.which;
1355
1356     switch (key)
1357       {
1358       case 38:  // key up
1359       case 40:  // key down
1360         if (!this.ksearch_pane)
1361           break;
1362           
1363         var dir = key==38 ? 1 : 0;
1364         var next;
1365         
1366         highlight = document.getElementById('rcmksearchSelected');
1367         if (!highlight)
1368           highlight = this.ksearch_pane.ul.firstChild;
1369         
1370         if (highlight && (next = dir ? highlight.previousSibling : highlight.nextSibling))
1371           {
1372           highlight.removeAttribute('id');
1373           //highlight.removeAttribute('class');
1374           this.set_classname(highlight, 'selected', false);
1375           }
1376
1377         if (next)
1378           {
1379           next.setAttribute('id', 'rcmksearchSelected');
1380           this.set_classname(next, 'selected', true);
1381           this.ksearch_selected = next._rcm_id;
1382           }
1383
1384         if (e.preventDefault)
1385           e.preventDefault();
1386         return false;
1387
1388       case 9:  // tab
1389         if(e.shiftKey)
1390           break;
1391
1392       case 13:  // enter     
1393         if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
1394           break;
1395
1396         // get cursor pos
1397         var inp_value = this.ksearch_input.value.toLowerCase();
1398         var cpos = this.get_caret_pos(this.ksearch_input);
1399         var p = inp_value.lastIndexOf(this.ksearch_value, cpos);
1400         
1401         // replace search string with full address
1402         var pre = this.ksearch_input.value.substring(0, p);
1403         var end = this.ksearch_input.value.substring(p+this.ksearch_value.length, this.ksearch_input.value.length);
1404         var insert = this.env.contacts[this.ksearch_selected]+', ';
1405         this.ksearch_input.value = pre + insert + end;
1406         
1407         //this.ksearch_input.value = this.ksearch_input.value.substring(0, p)+insert;
1408         
1409         // set caret to insert pos
1410         cpos = p+insert.length;
1411         if (this.ksearch_input.setSelectionRange)
1412           this.ksearch_input.setSelectionRange(cpos, cpos);
1413         
1414         // hide ksearch pane
1415         this.ksearch_hide();
1416       
1417         if (e.preventDefault)
1418           e.preventDefault();
1419         return false;
1420
1421       case 27:  // escape
1422         this.ksearch_hide();
1423         break;
1424
1425       }
1426
1427     // start timer
1428     this.ksearch_timer = setTimeout(this.ref+'.ksearch_get_results()', 200);      
1429     this.ksearch_input = obj;
1430     
1431     return true;
1432     };
1433
1434
1435   // address search processor
1436   this.ksearch_get_results = function()
1437     {
1438     var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
1439     if (inp_value===null)
1440       return;
1441
1442     // get string from current cursor pos to last comma
1443     var cpos = this.get_caret_pos(this.ksearch_input);
1444     var p = inp_value.lastIndexOf(',', cpos-1);
1445     var q = inp_value.substring(p+1, cpos);
1446
1447     // trim query string
1448     q = q.replace(/(^\s+|\s+$)/g, '').toLowerCase();
1449
1450     if (!q.length || q==this.ksearch_value)
1451       {
1452       if (!q.length && this.ksearch_pane && this.ksearch_pane.visible)
1453         this.ksearch_pane.show(0);
1454
1455       return;
1456       }
1457
1458     this.ksearch_value = q;
1459     
1460     // start searching the contact list
1461     var a_results = new Array();
1462     var a_result_ids = new Array();
1463     var c=0;
1464     for (var i=0; i<this.env.contacts.length; i++)
1465       {
1466       if (this.env.contacts[i].toLowerCase().indexOf(q)>=0)
1467         {
1468         a_results[c] = this.env.contacts[i];
1469         a_result_ids[c++] = i;
1470         
1471         if (c==15)  // limit search results
1472           break;
1473         }
1474       }
1475
1476     // display search results
1477     if (c && a_results.length)
1478       {
1479       var p, ul, li;
1480       
1481       // create results pane if not present
1482       if (!this.ksearch_pane)
1483         {
1484         ul = document.createElement('UL');
1485         this.ksearch_pane = new rcube_layer('rcmKSearchpane', {vis:0, zindex:30000});
1486         this.ksearch_pane.elm.appendChild(ul);
1487         this.ksearch_pane.ul = ul;
1488         }
1489       else
1490         ul = this.ksearch_pane.ul;
1491
1492       // remove all search results
1493       ul.innerHTML = '';
1494             
1495       // add each result line to list
1496       for (i=0; i<a_results.length; i++)
1497         {
1498         li = document.createElement('LI');
1499         li.innerHTML = a_results[i].replace(/</, '&lt;').replace(/>/, '&gt;');
1500         li._rcm_id = a_result_ids[i];
1501         ul.appendChild(li);
1502         }
1503
1504       // check if last selected item is still in result list
1505       if (this.ksearch_selected!==null)
1506         {
1507         p = find_in_array(this.ksearch_selected, a_result_ids);
1508         if (p>=0 && ul.childNodes)
1509           {
1510           ul.childNodes[p].setAttribute('id', 'rcmksearchSelected');
1511           this.set_classname(ul.childNodes[p], 'selected', true);
1512           }
1513         else
1514           this.ksearch_selected = null;
1515         }
1516       
1517       // if no item selected, select the first one
1518       if (this.ksearch_selected===null)
1519         {
1520         ul.firstChild.setAttribute('id', 'rcmksearchSelected');
1521         this.set_classname(ul.firstChild, 'selected', true);
1522         this.ksearch_selected = a_result_ids[0];
1523         }
1524
1525       // resize the containing layer to fit the list
1526       //this.ksearch_pane.resize(ul.offsetWidth, ul.offsetHeight);
1527     
1528       // move the results pane right under the input box and make it visible
1529       var pos = rcube_get_object_pos(this.ksearch_input);
1530       this.ksearch_pane.move(pos.x, pos.y+this.ksearch_input.offsetHeight);
1531       this.ksearch_pane.show(1); 
1532       }
1533     // hide results pane
1534     else
1535       this.ksearch_hide();
1536     };
1537
1538
1539   this.ksearch_blur = function(e, obj)
1540     {
1541     if (this.ksearch_timer)
1542       clearTimeout(this.ksearch_timer);
1543
1544     this.ksearch_value = '';      
1545     this.ksearch_input = null;
1546     
1547     this.ksearch_hide();
1548     };
1549
1550
1551   this.ksearch_hide = function()
1552     {
1553     this.ksearch_selected = null;
1554     
1555     if (this.ksearch_pane)
1556       this.ksearch_pane.show(0);    
1557     };
1558
1559
1560
1561   /*********************************************************/
1562   /*********         address book methods          *********/
1563   /*********************************************************/
1564
1565
1566   this.list_contacts = function(page)
1567     {
1568     var add_url = '';
1569     var target = window;
1570     
1571     if (page && this.current_page==page)
1572       return false;
1573
1574     // load contacts remotely
1575     if (this.gui_objects.contactslist)
1576       {
1577       this.list_contacts_remote(page);
1578       return;
1579       }
1580
1581     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1582       {
1583       target = window.frames[this.env.contentframe];
1584       add_url = '&_framed=1';
1585       }
1586
1587     this.set_busy(true, 'loading');
1588     location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
1589     };
1590
1591
1592   // send remote request to load contacts list
1593   this.list_contacts_remote = function(page)
1594     {
1595     // clear list
1596     var table = this.gui_objects.contactslist;
1597     var tbody = document.createElement('TBODY');
1598     table.insertBefore(tbody, table.tBodies[0]);
1599     table.tBodies[1].style.display = 'none';
1600     
1601     this.contact_rows = new Array();
1602     this.list_rows = this.contact_rows;
1603
1604     // send request to server
1605     var url = page ? '&_page='+page : '';
1606     this.set_busy(true, 'loading');
1607     this.http_request('list', url);
1608     };
1609
1610
1611   // load contact record
1612   this.load_contact = function(cid, action, framed)
1613     {
1614     var add_url = '';
1615     var target = window;
1616     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1617       {
1618       add_url = '&_framed=1';
1619       target = window.frames[this.env.contentframe];
1620       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
1621       }
1622     else if (framed)
1623       return false;
1624       
1625     //if (this.env.framed && add_url=='')
1626     //  add_url = '&_framed=1';
1627     
1628     if (action && (cid || action=='add'))
1629       {
1630       this.set_busy(true);
1631       target.location.href = this.env.comm_path+'&_action='+action+'&_cid='+cid+add_url;
1632       }
1633     };
1634
1635
1636   this.delete_contacts = function()
1637     {
1638     // exit if no mailbox specified or if selection is empty
1639     if (!(this.selection.length || this.env.cid))
1640       return;
1641     
1642     var a_cids = new Array();
1643
1644     if (this.env.cid)
1645       a_cids[a_cids.length] = this.env.cid;
1646     else
1647       {
1648       var id;
1649       for (var n=0; n<this.selection.length; n++)
1650         {
1651         id = this.selection[n];
1652         a_cids[a_cids.length] = id;
1653       
1654         // 'remove' row from list (just hide it)
1655         if (this.contact_rows[id].obj)
1656           this.contact_rows[id].obj.style.display = 'none';
1657         }
1658
1659       // hide content frame if we delete the currently displayed contact
1660       if (this.selection.length==1 && this.env.contentframe)
1661         {
1662         var elm = document.getElementById(this.env.contentframe);
1663         elm.style.visibility = 'hidden';
1664         }
1665       }
1666
1667     // send request to server
1668     this.http_request('delete', '_cid='+a_cids.join(',')+'&_from='+(this.env.action ? this.env.action : ''));
1669     };
1670
1671
1672   // update a contact record in the list
1673   this.update_contact_row = function(cid, cols_arr)
1674     {
1675     if (!this.contact_rows[cid] || !this.contact_rows[cid].obj)
1676       return false;
1677       
1678     var row = this.contact_rows[cid].obj;
1679     for (var c=0; c<cols_arr.length; c++)
1680       if (row.cells[c])
1681         row.cells[c].innerHTML = cols_arr[c];
1682
1683     };
1684   
1685
1686
1687   /*********************************************************/
1688   /*********        user settings methods          *********/
1689   /*********************************************************/
1690
1691
1692   // load contact record
1693   this.load_identity = function(id, action)
1694     {
1695     if (action=='edit-identity' && (!id || id==this.env.iid))
1696       return;
1697
1698     var add_url = '';
1699     var target = window;
1700     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1701       {
1702       add_url = '&_framed=1';
1703       target = window.frames[this.env.contentframe];
1704       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
1705       }
1706
1707     if (action && (id || action=='add-identity'))
1708       {
1709       this.set_busy(true);
1710       target.location.href = this.env.comm_path+'&_action='+action+'&_iid='+id+add_url;
1711       }
1712     };
1713
1714
1715
1716   this.delete_identity = function(id)
1717     {
1718     // exit if no mailbox specified or if selection is empty
1719     if (!(this.selection.length || this.env.iid))
1720       return;
1721     
1722     if (!id)
1723       id = this.env.iid ? this.env.iid : this.selection[0];
1724
1725 /*
1726     // 'remove' row from list (just hide it)
1727     if (this.identity_rows && this.identity_rows[id].obj)
1728       {
1729       this.clear_selection();
1730       this.identity_rows[id].obj.style.display = 'none';
1731       }
1732 */
1733
1734     // if (this.env.framed && id)
1735       this.set_busy(true);
1736       location.href = this.env.comm_path+'&_action=delete-identity&_iid='+id;
1737     // else if (id)
1738     //  this.http_request('delete-identity', '_iid='+id);
1739     };
1740
1741
1742   this.create_folder = function(name)
1743     {
1744     var form;
1745     if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
1746       name = form.elements['_folder_name'].value;
1747
1748     if (name)
1749       this.http_request('create-folder', '_name='+escape(name));
1750     else if (form.elements['_folder_name'])
1751       form.elements['_folder_name'].focus();
1752     };
1753
1754
1755   this.delete_folder = function(folder)
1756     {
1757     if (folder)
1758       {
1759       for (var id in this.env.subscriptionrows)
1760         if (this.env.subscriptionrows[id]==folder)
1761           break;
1762           
1763       var row;
1764       if (id && (row = document.getElementById(id)))
1765         row.style.display = 'none';
1766
1767       this.http_request('delete-folder', '_mboxes='+escape(folder));
1768       }
1769     };
1770
1771
1772   this.subscribe_folder = function(folder)
1773     {
1774     var form;
1775     if ((form = this.gui_objects.editform) && form.elements['_unsubscribed'])
1776       this.change_subscription('_unsubscribed', '_subscribed', 'subscribe');
1777     else if (folder)
1778       this.http_request('subscribe', '_mboxes='+escape(folder));
1779     };
1780
1781
1782   this.unsubscribe_folder = function(folder)
1783     {
1784     var form;
1785     if ((form = this.gui_objects.editform) && form.elements['_subscribed'])
1786       this.change_subscription('_subscribed', '_unsubscribed', 'unsubscribe');
1787     else if (folder)
1788       this.http_request('unsubscribe', '_mboxes='+escape(folder));
1789     };
1790     
1791
1792   this.change_subscription = function(from, to, action)
1793     {
1794     var form;
1795     if (form = this.gui_objects.editform)
1796       {
1797       var a_folders = new Array();
1798       var list_from = form.elements[from];
1799
1800       for (var i=0; list_from && i<list_from.options.length; i++)
1801         {
1802         if (list_from.options[i] && list_from.options[i].selected)
1803           {
1804           a_folders[a_folders.length] = list_from.options[i].value;
1805           list_from[i] = null;
1806           i--;
1807           }
1808         }
1809
1810       // yes, we have some folders selected
1811       if (a_folders.length)
1812         {
1813         var list_to = form.elements[to];
1814         var index;
1815         
1816         for (var n=0; n<a_folders.length; n++)
1817           {
1818           index = list_to.options.length;
1819           list_to[index] = new Option(a_folders[n]);
1820           }
1821           
1822         this.http_request(action, '_mboxes='+escape(a_folders.join(',')));
1823         }
1824       }
1825       
1826     };
1827
1828
1829    // add a new folder to the subscription list by cloning a folder row
1830    this.add_folder_row = function(name)
1831      {
1832      if (!this.gui_objects.subscriptionlist)
1833        return false;
1834
1835      var tbody = this.gui_objects.subscriptionlist.tBodies[0];
1836      var id = tbody.childNodes.length+1;
1837      
1838      // clone a table row
1839      var row = this.clone_table_row(tbody.rows[0]);
1840      row.id = 'rcmrow'+id;
1841      tbody.appendChild(row);
1842
1843      // add to folder/row-ID map
1844      this.env.subscriptionrows[row.id] = name;
1845
1846      // set folder name
1847      row.cells[0].innerHTML = name;
1848      if (row.cells[1].firstChild.tagName=='INPUT')
1849        {
1850        row.cells[1].firstChild.value = name;
1851        row.cells[1].firstChild.checked = true;
1852        }
1853      if (row.cells[2].firstChild.tagName=='A')
1854        row.cells[2].firstChild.onclick = new Function(this.ref+".command('delete-folder','"+name+"')");
14eafe 1855
T 1856     var form;
1857     if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
1858       form.elements['_folder_name'].value = '';
4e17e6 1859      };
T 1860
1861
1862   // duplicate a specific table row
1863   this.clone_table_row = function(row)
1864     {
1865     var cell, td;
1866     var new_row = document.createElement('TR');
1867     for(var n=0; n<row.childNodes.length; n++)
1868       {
1869       cell = row.childNodes[n];
1870       td = document.createElement('TD');
1871
1872       if (cell.className)
1873         td.className = cell.className;
1874       if (cell.align)
1875         td.setAttribute('align', cell.align);
1876         
1877       td.innerHTML = cell.innerHTML;
1878       new_row.appendChild(td);
1879       }
1880     
1881     return new_row;
1882     };
1883
1884
1885   /*********************************************************/
1886   /*********           GUI functionality           *********/
1887   /*********************************************************/
1888
1889
1890   // eable/disable buttons for page shifting
1891   this.set_page_buttons = function()
1892     {
1893     this.enable_command('nextpage', (this.env.pagecount > this.env.current_page));
1894     this.enable_command('previouspage', (this.env.current_page > 1));
1895     }
1896
1897
1898   // set button to a specific state
1899   this.set_button = function(command, state)
1900     {
1901     var a_buttons = this.buttons[command];
1902     var button, obj;
1903
1904     if(!a_buttons || !a_buttons.length)
1905       return;
1906
1907     for(var n=0; n<a_buttons.length; n++)
1908       {
1909       button = a_buttons[n];
1910       obj = document.getElementById(button.id);
1911
1912       // get default/passive setting of the button
1913       if (obj && button.type=='image' && !button.status)
1914         button.pas = obj._original_src ? obj._original_src : obj.src;
1915       else if (obj && !button.status)
1916         button.pas = String(obj.className);
1917
1918       // set image according to button state
1919       if (obj && button.type=='image' && button[state])
1920         {
1921         button.status = state;        
1922         obj.src = button[state];
1923         }
1924       // set class name according to button state
1925       else if (obj && typeof(button[state])!='undefined')
1926         {
1927         button.status = state;        
1928         obj.className = button[state];        
1929         }
1930       // disable/enable input buttons
1931       if (obj && button.type=='input')
1932         {
1933         button.status = state;
1934         obj.disabled = !state;
1935         }
1936       }
1937     };
1938
1939
1940   // mouse over button
1941   this.button_over = function(command, id)
1942     {
1943     var a_buttons = this.buttons[command];
1944     var button, img;
1945
1946     if(!a_buttons || !a_buttons.length)
1947       return;
1948
1949     for(var n=0; n<a_buttons.length; n++)
1950       {
1951       button = a_buttons[n];
1952       if(button.id==id && button.status=='act')
1953         {
1954         img = document.getElementById(button.id);
1955         if (img && button.over)
1956           img.src = button.over;
1957         }
1958       }
1959     };
1960
1961
1962   // mouse out of button
1963   this.button_out = function(command, id)
1964     {
1965     var a_buttons = this.buttons[command];
1966     var button, img;
1967
1968     if(!a_buttons || !a_buttons.length)
1969       return;
1970
1971     for(var n=0; n<a_buttons.length; n++)
1972       {
1973       button = a_buttons[n];
1974       if(button.id==id && button.status=='act')
1975         {
1976         img = document.getElementById(button.id);
1977         if (img && button.act)
1978           img.src = button.act;
1979         }
1980       }
1981     };
1982
1983
1984   // set/unset a specific class name
1985   this.set_classname = function(obj, classname, set)
1986     {
1987     var reg = new RegExp('\s*'+classname, 'i');
1988     if (!set && obj.className.match(reg))
1989       obj.className = obj.className.replace(reg, '');
1990     else if (set && !obj.className.match(reg))
1991       obj.className += ' '+classname;
1992     };
1993
1994
1995   // display a specific alttext
1996   this.alttext = function(text)
1997     {
1998     
1999     };
2000
2001
2002   // display a system message
2003   this.display_message = function(msg, type, hold)
2004     {
2005     if (!this.loaded)  // save message in order to display after page loaded
2006       {
2007       this.pending_message = new Array(msg, type);
2008       return true;
2009       }
2010     
2011     if (!this.gui_objects.message)
2012       return false;
2013       
2014     if (this.message_timer)
2015       clearTimeout(this.message_timer);
2016     
2017     var cont = msg;
2018     if (type)
2019       cont = '<div class="'+type+'">'+cont+'</div>';
a95e0e 2020
T 2021     this.gui_objects.message._rcube = this;
4e17e6 2022     this.gui_objects.message.innerHTML = cont;
T 2023     this.gui_objects.message.style.display = 'block';
a95e0e 2024     
T 2025     if (type!='loading')
2026       this.gui_objects.message.onmousedown = function(){ this._rcube.hide_message(); return true; };
4e17e6 2027     
T 2028     if (!hold)
2029       this.message_timer = setTimeout(this.ref+'.hide_message()', this.message_time);
2030     };
2031
2032
2033   // make a message row disapear
2034   this.hide_message = function()
2035     {
2036     if (this.gui_objects.message)
a95e0e 2037       {
4e17e6 2038       this.gui_objects.message.style.display = 'none';
a95e0e 2039       this.gui_objects.message.onmousedown = null;
T 2040       }
4e17e6 2041     };
T 2042
2043
2044   // mark a mailbox as selected and set environment variable
2045   this.select_mailbox = function(mbox)
2046     {
2047     if (this.gui_objects.mailboxlist)
2048       {
2049       var item, reg, text_obj;
2050       var s_mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
2051       var s_current = this.env.mailbox.toLowerCase().replace(this.mbox_expression, '');
597170 2052       var nodes = this.gui_objects.mailboxlist.getElementsByTagName('LI');
T 2053       
2054       for (var n=0; n<nodes.length; n++)
4e17e6 2055         {
597170 2056         item = nodes[n];
4e17e6 2057         if (item.className && item.className.indexOf('mailbox '+s_mbox+' ')>=0)
T 2058           this.set_classname(item, 'selected', true);
2059         else if (item.className && item.className.indexOf('mailbox '+s_current)>=0)
2060           this.set_classname(item, 'selected', false);          
2061         }
2062       }
2063     
2064     this.env.mailbox = mbox;
2065     };
2066
2067
2068   // create a table row in the message list
2069   this.add_message_row = function(uid, cols, flags, attachment)
2070     {
2071     if (!this.gui_objects.messagelist || !this.gui_objects.messagelist.tBodies[0])
2072       return false;
2073     
2074     var tbody = this.gui_objects.messagelist.tBodies[0];
2075     var rowcount = tbody.rows.length;
2076     var even = rowcount%2;
2077     
2078     this.env.messages[uid] = {replied:flags.replied?1:0,
2079                               unread:flags.unread?1:0};
2080     
2081     var row = document.createElement('TR');
2082     row.id = 'rcmrow'+uid;
2083     row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : '');
2084
2085     if (this.in_selection(uid))
2086       row.className += ' selected';
2087
2088     var icon = flags.unread && this.env.unreadicon ? this.env.unreadicon :
2089                (flags.replied && this.env.repliedicon ? this.env.repliedicon : this.env.messageicon);
2090
2091     var col = document.createElement('TD');
2092     col.className = 'icon';
2093     col.innerHTML = icon ? '<img src="'+icon+'" alt="" border="0" />' : '';
2094     row.appendChild(col);
2095
2096     // add each submitted col
2097     for (var c in cols)
2098       {
2099       col = document.createElement('TD');
2100       col.className = String(c).toLowerCase();
2101       col.innerHTML = cols[c];
2102       row.appendChild(col);
2103       }
2104
2105     col = document.createElement('TD');
2106     col.className = 'icon';
2107     col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" border="0" />' : '';
2108     row.appendChild(col);
2109     
2110     tbody.appendChild(row);
2111     this.init_message_row(row);
2112     };
2113
2114
2115   // replace content of row count display
2116   this.set_rowcount = function(text)
2117     {
2118     if (this.gui_objects.countdisplay)
2119       this.gui_objects.countdisplay.innerHTML = text;
2120
2121     // update page navigation buttons
2122     this.set_page_buttons();
2123     };
2124
2125
2126   // update the mailboxlist
2127   this.set_unread_count = function(mbox, count)
2128     {
2129     if (!this.gui_objects.mailboxlist)
2130       return false;
2131       
2132     mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
2133     
2134     var item, reg, text_obj;
2135     for (var n=0; n<this.gui_objects.mailboxlist.childNodes.length; n++)
2136       {
2137       item = this.gui_objects.mailboxlist.childNodes[n];
2138
2139       if (item.className && item.className.indexOf('mailbox '+mbox)>=0)
2140         {
2141         // set new text
2142         text_obj = item.firstChild;
2143         reg = /\s+\([0-9]+\)$/i;
2144
2145         if (count && text_obj.innerHTML.match(reg))
2146           text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+count+')');
2147         else if (count)
2148           text_obj.innerHTML += ' ('+count+')';
2149         else
2150           text_obj.innerHTML = text_obj.innerHTML.replace(reg, '');
2151           
2152         // set the right classes
2153         this.set_classname(item, 'unread', count>0 ? true : false);        
2154         break;
2155         }
2156       }
2157     };
2158
2159
2160   // add row to contacts list
2161   this.add_contact_row = function(cid, cols)
2162     {
2163     if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
2164       return false;
2165     
2166     var tbody = this.gui_objects.contactslist.tBodies[0];
2167     var rowcount = tbody.rows.length;
2168     var even = rowcount%2;
2169     
2170     var row = document.createElement('TR');
2171     row.id = 'rcmrow'+cid;
2172     row.className = 'contact '+(even ? 'even' : 'odd');
2173     
2174     if (this.in_selection(cid))
2175       row.className += ' selected';
2176
2177     // add each submitted col
2178     for (var c in cols)
2179       {
2180       col = document.createElement('TD');
2181       col.className = String(c).toLowerCase();
2182       col.innerHTML = cols[c];
2183       row.appendChild(col);
2184       }
2185     
2186     tbody.appendChild(row);
2187     this.init_table_row(row, 'contact_rows');
2188     };
2189
2190
2191
2192   /********************************************************/
2193   /*********          drag & drop methods         *********/
2194   /********************************************************/
2195
2196
2197   this.drag_mouse_move = function(e)
2198     {
2199     if (this.drag_start)
2200       {
2201       if (!this.draglayer)
2202         this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
2203       
2204       // get subjects of selectedd messages
2205       var names = '';
2206       var c, subject, obj;
2207       for(var n=0; n<this.selection.length; n++)
2208         {
2209         if (n>12)  // only show 12 lines
2210           {
2211           names += '...';
2212           break;
2213           }
2214
2215         if (this.message_rows[this.selection[n]].obj)
2216           {
2217           obj = this.message_rows[this.selection[n]].obj;
2218           subject = '';
2219
2220           for(c=0; c<obj.childNodes.length; c++)
2221             if (!subject && obj.childNodes[c].nodeName=='TD' && obj.childNodes[c].firstChild && obj.childNodes[c].firstChild.nodeType==3)
2222               {
2223               subject = obj.childNodes[c].firstChild.data;
2224               names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
2225               }
2226           }
2227         }
2228         
2229       this.draglayer.write(names);
2230       this.draglayer.show(1);
2231       }
2232
2233     var pos = this.get_mouse_pos(e);
2234     this.draglayer.move(pos.x+20, pos.y-5);
2235     
2236     this.drag_start = false;
2237     this.drag_active = true;
2238     
2239     return false;
2240     };
2241
2242
2243   this.drag_mouse_up = function()
2244     {
2245     document.onmousemove = null;
2246     
2247     if (this.draglayer && this.draglayer.visible)
2248       this.draglayer.show(0);
2249       
2250     this.drag_active = false;
2251     
2252     return false;
2253     };
2254
2255
2256
2257   /********************************************************/
2258   /*********        remote request methods        *********/
2259   /********************************************************/
2260
2261
2262   // send a http request to the server
2263   this.http_request = function(action, querystring)
2264     {
2265     if (window.XMLHttpRequest)
2266       this.request_obj = new XMLHttpRequest();
2267     else if (window.ActiveXObject)
2268       this.request_obj = new ActiveXObject("Microsoft.XMLHTTP");
2269     else
2270       {
2271       
2272       }
2273
2274     querystring += '&_remote=1';
2275     
2276     // add timestamp to request url to avoid cacheing problems in Safari
2277     if (bw.safari)
2278       querystring += '&_ts='+(new Date().getTime());
2279
2280     // send request
2281     if (this.request_obj)
2282       {
2283       // prompt('request', this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
2284       console('HTTP request: '+this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
2285       this.set_busy(true);
2286       this.request_action = action;
2287       this.request_obj.onreadystatechange = function(){ rcube_webmail_client.http_response(); };
2288       this.request_obj.open('GET', this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
2289       this.request_obj.send(null);
2290       }
2291     };
2292
2293
2294   // handle http response
2295   this.http_response = function()
2296     {
2297     if (this.request_obj.readyState == 4) // || this.request_obj.readyState == 2)
2298       {
2299       var ctype = this.request_obj.getResponseHeader('Content-Type');
2300       if (ctype)
2301         ctype = String(ctype).toLowerCase();
2302
2303       this.set_busy(false);
2304
2305   console(this.request_obj.responseText);
2306
2307       // if we get javascript code from server -> execute it
2308       if (this.request_obj.responseText && (ctype=='text/javascript' || ctype=='application/x-javascript'))
2309         eval(this.request_obj.responseText);
2310
2311       // process the response data according to the sent action
2312       switch (this.request_action)
2313         {
2314         case 'delete':
2315         case 'moveto':
2316           if (this.env.action=='show')
2317             this.command('list');
2318           break;
2319           
2320         case 'list':
2321           this.enable_command('select-all', 'select-none', this.env.messagecount ? true : false);
2322           break;
2323         }
2324       }
2325     };
2326
2327
2328   /********************************************************/
2329   /*********            helper methods            *********/
2330   /********************************************************/
2331   
2332   // check if we're in show mode or if we have a unique selection
2333   // and return the message uid
2334   this.get_single_uid = function()
2335     {
2336     return this.env.uid ? this.env.uid : (this.selection.length==1 ? this.selection[0] : null);
2337     };
2338
2339   // same as above but for contacts
2340   this.get_single_cid = function()
2341     {
2342     return this.env.cid ? this.env.cid : (this.selection.length==1 ? this.selection[0] : null);
2343     };
2344
2345
2346   // check if Shift-key is pressed on event
2347   this.check_shiftkey = function(e)
2348     {
2349     if(!e && window.event)
2350       e = window.event;
2351
2352     if(bw.linux && bw.ns4 && e.modifiers)
2353       return true;
2354     else if((bw.ns4 && e.modifiers & Event.SHIFT_MASK) || (e && e.shiftKey))
2355       return true;
2356     else
2357       return false;
2358     }
2359
2360
2361   this.get_mouse_pos = function(e)
2362     {
2363     if(!e) e = window.event;
2364     var mX = (e.pageX) ? e.pageX : e.clientX;
2365     var mY = (e.pageY) ? e.pageY : e.clientY;
2366
2367     if(document.body && document.all)
2368       {
2369       mX += document.body.scrollLeft;
2370       mY += document.body.scrollTop;
2371       }
2372
2373     return { x:mX, y:mY };
2374     };
2375     
2376   
2377   this.get_caret_pos = function(obj)
2378     {
2379     if (typeof(obj.selectionEnd)!='undefined')
2380       return obj.selectionEnd;
2381
2382     else if (document.selection && document.selection.createRange)
2383       {
2384       var range = document.selection.createRange();
2385       if (range.parentElement()!=obj)
2386         return 0;
2387
2388       var gm = range.duplicate();
2389       if (obj.tagName=='TEXTAREA')
2390         gm.moveToElementText(obj);
2391       else
2392         gm.expand('textedit');
2393       
2394       gm.setEndPoint('EndToStart', range);
2395       var p = gm.text.length;
2396
2397       return p<=obj.value.length ? p : -1;
2398       }
2399
2400     else
2401       return obj.value.length;
2402     };
2403
2404
2405   this.set_caret2start = function(obj)
2406     {
2407     if (obj.createTextRange)
2408       {
2409       var range = obj.createTextRange();
2410       range.collapse(true);
2411       range.select();
2412       }
2413     else if (obj.setSelectionRange)
2414       obj.setSelectionRange(0,0);
2415
2416     obj.focus();
2417     };
2418
2419
2420   // set all fields of a form disabled
2421   this.lock_form = function(form, lock)
2422     {
2423     if (!form || !form.elements)
2424       return;
2425     
2426     var type;
2427     for (var n=0; n<form.elements.length; n++)
2428       {
2429       type = form.elements[n];
2430       if (type=='hidden')
2431         continue;
2432         
2433       form.elements[n].disabled = lock;
2434       }
2435     };
2436     
2437   }  // end object rcube_webmail
2438
2439
2440
2441
2442 function console(str)
2443   {
2444   if (document.debugform && document.debugform.console)
2445     document.debugform.console.value += str+'\n--------------------------------------\n';
2446   }
2447
2448
2449 // set onload handler
2450 window.onload = function(e)
2451   {
2452   if (window.rcube_webmail_client)
2453     rcube_webmail_client.init();
2454   };