78966268b8d2a7771b387df04313fafb0fd96d7c
[tine20] / tine20 / Felamimail / js / MessageEditDialog.js
1 /*
2  * Tine 2.0
3  * 
4  * @package     Felamimail
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Philipp Schüle <p.schuele@metaways.de>
7  * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
8  */
9  
10 Ext.namespace('Tine.Felamimail');
11
12 /**
13  * @namespace   Tine.Felamimail
14  * @class       Tine.Felamimail.MessageEditDialog
15  * @extends     Tine.widgets.dialog.EditDialog
16  * 
17  * <p>Message Compose Dialog</p>
18  * <p>This dialog is for composing emails with recipients, body and attachments. 
19  * you can choose from which account you want to send the mail.</p>
20  * <p>
21  * TODO         make email note editable
22  * </p>
23  * 
24  * @author      Philipp Schüle <p.schuele@metaways.de>
25  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
26  * 
27  * @param       {Object} config
28  * @constructor
29  * Create a new MessageEditDialog
30  */
31 Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
32     /**
33      * @cfg {Array/String} bcc
34      * initial config for bcc
35      */
36     bcc: null,
37     
38     /**
39      * @cfg {String} body
40      */
41     msgBody: '',
42     
43     /**
44      * @cfg {Array/String} cc
45      * initial config for cc
46      */
47     cc: null,
48     
49     /**
50      * @cfg {Array} of Tine.Felamimail.Model.Message (optionally encoded)
51      * messages to forward
52      */
53     forwardMsgs: null,
54     
55     /**
56      * @cfg {String} accountId
57      * the accout id this message is sent from
58      */
59     accountId: null,
60     
61     /**
62      * @cfg {Tine.Felamimail.Model.Message} (optionally encoded)
63      * message to reply to
64      */
65     replyTo: null,
66
67     /**
68      * @cfg {Tine.Felamimail.Model.Message} (optionally encoded)
69      * message to use as draft/template
70      */
71     draftOrTemplate: null,
72     
73     /**
74      * @cfg {Boolean} (defaults to false)
75      */
76     replyToAll: false,
77     
78     /**
79      * @cfg {String} subject
80      */
81     subject: '',
82     
83     /**
84      * @cfg {Array/String} to
85      * initial config for to
86      */
87     to: null,
88     
89     /**
90      * validation error message
91      * @type String
92      */
93     validationErrorMessage: '',
94     
95     /**
96      * array with e-mail-addresses used as recipients
97      * @type {Array}
98      */
99     mailAddresses: null,
100     /**
101      * json-encoded selection filter
102      * @type {String} selectionFilter
103      */
104     selectionFilter: null,
105     
106     /**
107      * holds default values for the record
108      * @type {Object}
109      */
110     recordDefaults: null,
111     
112     /**
113      * @private
114      */
115     windowNamePrefix: 'MessageEditWindow_',
116     appName: 'Felamimail',
117     recordClass: Tine.Felamimail.Model.Message,
118     recordProxy: Tine.Felamimail.messageBackend,
119     loadRecord: false,
120     evalGrants: false,
121     
122     bodyStyle:'padding:0px',
123     
124     /**
125      * overwrite update toolbars function (we don't have record grants)
126      * @private
127      */
128     updateToolbars: Ext.emptyFn,
129     
130     //private
131     initComponent: function() {
132          Tine.Felamimail.MessageEditDialog.superclass.initComponent.call(this);
133          this.on('save', this.onSave, this);
134     },
135     
136     /**
137      * init buttons
138      */
139     initButtons: function() {
140         this.fbar = [];
141         
142         this.action_send = new Ext.Action({
143             text: this.app.i18n._('Send'),
144             handler: this.onSaveAndClose,
145             iconCls: 'FelamimailIconCls',
146             disabled: false,
147             scope: this
148         });
149
150         this.action_searchContacts = new Ext.Action({
151             text: this.app.i18n._('Search Recipients'),
152             handler: this.onSearchContacts,
153             iconCls: 'AddressbookIconCls',
154             disabled: false,
155             scope: this
156         });
157         
158         this.action_saveAsDraft = new Ext.Action({
159             text: this.app.i18n._('Save As Draft'),
160             handler: this.onSaveInFolder.createDelegate(this, ['drafts_folder']),
161             iconCls: 'action_saveAsDraft',
162             disabled: false,
163             scope: this
164         });
165
166         this.action_saveAsTemplate = new Ext.Action({
167             text: this.app.i18n._('Save As Template'),
168             handler: this.onSaveInFolder.createDelegate(this, ['templates_folder']),
169             iconCls: 'action_saveAsTemplate',
170             disabled: false,
171             scope: this
172         });
173         
174         // TODO think about changing icon onToggle
175         this.action_saveEmailNote = new Ext.Action({
176             text: this.app.i18n._('Save Email Note'),
177             handler: this.onToggleSaveNote,
178             iconCls: 'notes_noteIcon',
179             disabled: false,
180             scope: this,
181             enableToggle: true
182         });
183         this.button_saveEmailNote = Ext.apply(new Ext.Button(this.action_saveEmailNote), {
184             tooltip: this.app.i18n._('Activate this toggle button to save the email text as a note attached to the recipient(s) contact(s).')
185         });
186
187         this.tbar = new Ext.Toolbar({
188             defaults: {height: 55},
189             items: [{
190                 xtype: 'buttongroup',
191                 columns: 5,
192                 items: [
193                     Ext.apply(new Ext.Button(this.action_send), {
194                         scale: 'medium',
195                         rowspan: 2,
196                         iconAlign: 'top'
197                     }),
198                     Ext.apply(new Ext.Button(this.action_searchContacts), {
199                         scale: 'medium',
200                         rowspan: 2,
201                         iconAlign: 'top',
202                         tooltip: this.app.i18n._('Click to search for and add recipients from the Addressbook.')
203                     }),
204                     this.action_saveAsDraft,
205                     this.button_saveEmailNote,
206                     this.action_saveAsTemplate
207                 ]
208             }]
209         });
210     },
211
212     
213     /**
214      * @private
215      */
216     initRecord: function() {
217         this.decodeMsgs();
218         
219         this.recordDefaults = Tine.Felamimail.Model.Message.getDefaultData();
220         
221         if (this.mailAddresses) {
222             this.recordDefaults.to = Ext.decode(this.mailAddresses);
223         } else if (this.selectionFilter) {
224             this.on('load', this.fetchRecordsOnLoad, this);
225         }
226         
227         if (! this.record) {
228             this.record = new Tine.Felamimail.Model.Message(this.recordDefaults, 0);
229         }
230         this.initFrom();
231         this.initRecipients();
232         this.initSubject();
233         this.initContent();
234         // legacy handling:...
235         // TODO add this information to attachment(s) + flags and remove this
236         if (this.replyTo) {
237             this.record.set('flags', '\\Answered');
238             this.record.set('original_id', this.replyTo.id);
239         } else if (this.forwardMsgs) {
240             this.record.set('flags', 'Passed');
241             this.record.set('original_id', this.forwardMsgs[0].id);
242         } else if (this.draftOrTemplate) {
243             this.record.set('original_id', this.draftOrTemplate.id);
244         }
245         
246         Tine.log.debug('Tine.Felamimail.MessageEditDialog::initRecord() -> record:');
247         Tine.log.debug(this.record);
248     },
249     
250     /**
251      * show loadMask (loadRecord is false in this dialog)
252      * @param {} ct
253      * @param {} position
254      */
255     onRender : function(ct, position) {
256         Tine.Felamimail.MessageEditDialog.superclass.onRender.call(this, ct, position);
257         this.loadMask.show();
258     },
259     
260     /**
261      * init attachments when forwarding message
262      * 
263      * @param {Tine.Felamimail.Model.Message} message
264      */
265     initAttachements: function(message) {
266         if (message.get('attachments').length > 0) {
267             this.record.set('attachments', [{
268                 name: message.get('subject'),
269                 type: 'message/rfc822',
270                 size: message.get('size'),
271                 id: message.id
272             }]);
273         }
274     },
275     
276     /**
277      * inits body and attachments from reply/forward/template
278      */
279     initContent: function() {
280         if (! this.record.get('body')) {
281             var account = Tine.Tinebase.appMgr.get('Felamimail').getAccountStore().getById(this.record.get('account_id'));
282             
283             if (! this.msgBody) {
284                 var message = this.getMessageFromConfig();
285                 if (message) {
286                     if (! message.bodyIsFetched()) {
287                         // self callback when body needs to be fetched
288                         return this.recordProxy.fetchBody(message, this.initContent.createDelegate(this));
289                     }
290                     
291                     this.setMessageBody(message, account);
292                     
293                     if (this.isForwardedMessage() || this.draftOrTemplate) {
294                         this.initAttachements(message);
295                     }
296                 }
297             } 
298             this.addSignature(account);
299             
300             this.record.set('body', this.msgBody);
301         }
302         
303         delete this.msgBody;
304         this.onRecordLoad();
305     },
306     
307     /**
308      * set message body: converts newlines, adds quotes
309      * 
310      * @param {Tine.Felamimail.Model.Message} message
311      * @param {Tine.Felamimail.Model.Account} account
312      */
313     setMessageBody: function(message, account) {
314         this.msgBody = message.get('body');
315         
316         if (account.get('display_format') == 'plain' || (account.get('display_format') == 'content_type' && message.get('body_content_type') == 'text/plain')) {
317             this.msgBody = Ext.util.Format.nl2br(this.msgBody);
318         }
319         
320         if (this.replyTo) {
321             this.setMessageBodyReply();
322         } else if (this.isForwardedMessage()) {
323             this.setMessageBodyForward();
324         }
325     },
326     
327     /**
328      * set message body for reply message
329      */
330     setMessageBodyReply: function() {
331         var date = (this.replyTo.get('sent')) 
332             ? this.replyTo.get('sent') 
333             : ((this.replyTo.get('received')) ? this.replyTo.get('received') : new Date());
334         
335         this.msgBody = String.format(this.app.i18n._('On {0}, {1} wrote'), 
336             Tine.Tinebase.common.dateTimeRenderer(date), 
337             Ext.util.Format.htmlEncode(this.replyTo.get('from_name'))
338         ) + ':<br/>'
339           + '<blockquote class="felamimail-body-blockquote">' + this.msgBody + '</blockquote><br/>';
340     },
341     
342     /**
343      * returns true if message is forwarded
344      * 
345      * @return {Boolean}
346      */
347     isForwardedMessage: function() {
348         return (this.forwardMsgs && this.forwardMsgs.length === 1);
349     },
350     
351     /**
352      * set message body for forwarded message
353      */
354     setMessageBodyForward: function() {
355         this.msgBody = '<br/>-----' + this.app.i18n._('Original message') + '-----<br/>'
356             + Tine.Felamimail.GridPanel.prototype.formatHeaders(this.forwardMsgs[0].get('headers'), false, true) + '<br/><br/>'
357             + this.msgBody + '<br/>';
358     },
359     
360     /**
361      * add signature to message
362      * 
363      * @param {Tine.Felamimail.Model.Account} account
364      */
365     addSignature: function(account) {
366         if (this.draftOrTemplate || ! account) {
367             return;
368         }
369
370         var signaturePosition = (account.get('signature_position')) ? account.get('signature_position') : 'below',
371             signature = Tine.Felamimail.getSignature(this.record.get('account_id'));
372         if (signaturePosition == 'below') {
373             this.msgBody += signature;
374         } else {
375             this.msgBody = signature + '<br/><br/>' + this.msgBody;
376         }
377     },
378     
379     /**
380      * inits / sets sender of message
381      */
382     initFrom: function() {
383         if (! this.record.get('account_id')) {
384             if (! this.accountId) {
385                 var message = this.getMessageFromConfig(),
386                     folderId = message ? message.get('folder_id') : null, 
387                     folder = folderId ? Tine.Tinebase.appMgr.get('Felamimail').getFolderStore().getById(folderId) : null,
388                     accountId = folder ? folder.get('account_id') : null;
389                     
390                 if (! accountId) {
391                     var activeAccount = Tine.Tinebase.appMgr.get('Felamimail').getActiveAccount();
392                     accountId = (activeAccount) ? activeAccount.id : null;
393                 }
394                 
395                 this.accountId = accountId;
396             }
397             
398             this.record.set('account_id', this.accountId);
399         }
400         delete this.accountId;
401     },
402     
403     /**
404      * after render
405      */
406     afterRender: function() {
407         Tine.Felamimail.MessageEditDialog.superclass.afterRender.apply(this, arguments);
408         
409         this.getEl().on(Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.onKeyPress, this);
410         this.recipientGrid.on('specialkey', function(field, e) {
411             this.onKeyPress(e);
412         }, this);
413         
414         this.htmlEditor.on('keydown', function(e) {
415             if (e.getKey() == e.ENTER && e.ctrlKey) {
416                 this.onSaveAndClose();
417             } else if (e.getKey() == e.TAB && e.shiftKey) {
418                 this.subjectField.focus.defer(50, this.subjectField);
419             }
420         }, this);
421     },
422     
423     /**
424      * on key press
425      * @param {} e
426      * @param {} t
427      * @param {} o
428      */
429     onKeyPress: function(e, t, o) {
430         if ((e.getKey() == e.TAB || e.getKey() == e.ENTER) && ! e.shiftKey) {
431             if (e.getTarget('input[name=subject]')) {
432                 this.htmlEditor.focus.defer(50, this.htmlEditor);
433             } else if (e.getTarget('input[type=text]')) {
434                 this.subjectField.focus.defer(50, this.subjectField);
435             }
436         }
437     },
438     
439     /**
440      * returns message passed with config
441      * 
442      * @return {Tine.Felamimail.Model.Message}
443      */
444     getMessageFromConfig: function() {
445         return this.replyTo ? this.replyTo : 
446                this.forwardMsgs && this.forwardMsgs.length === 1 ? this.forwardMsgs[0] :
447                this.draftOrTemplate ? this.draftOrTemplate : null;
448     },
449     
450     /**
451      * inits to/cc/bcc
452      */
453     initRecipients: function() {
454         if (this.replyTo) {
455             this.initReplyRecipients();
456         }
457         
458         Ext.each(['to', 'cc', 'bcc'], function(field) {
459             if (this.draftOrTemplate) {
460                 this[field] = this.draftOrTemplate.get(field);
461             }
462             
463             if (! this.record.get(field)) {
464                 this[field] = Ext.isArray(this[field]) ? this[field] : Ext.isString(this[field]) ? [this[field]] : [];
465                 this.record.set(field, Ext.unique(this[field]));
466             }
467             delete this[field];
468             
469             this.resolveRecipientFilter(field);
470             
471         }, this);
472     },
473     
474     /**
475      * init recipients from reply/replyToAll information
476      */
477     initReplyRecipients: function() {
478         var replyTo = this.replyTo.get('headers')['reply-to'];
479         
480         if (replyTo) {
481             this.to = replyTo;
482         } else {
483             var toemail = '<' + this.replyTo.get('from_email') + '>';
484             if (this.replyTo.get('from_name') && this.replyTo.get('from_name') != this.replyTo.get('from_email')) {
485                 this.to = this.replyTo.get('from_name') + ' ' + toemail;
486             } else {
487                 this.to = toemail;
488             }
489         }
490         
491         if (this.replyToAll) {
492             if (! Ext.isArray(this.to)) {
493                 this.to = [this.to];
494             }
495             this.to = this.to.concat(this.replyTo.get('to'));
496             this.cc = this.replyTo.get('cc');
497             
498             // remove own email and all non-email strings/objects from to/cc
499             var account = Tine.Tinebase.appMgr.get('Felamimail').getAccountStore().getById(this.record.get('account_id')),
500                 ownEmailRegexp = new RegExp(account.get('email'));
501             Ext.each(['to', 'cc'], function(field) {
502                 for (var i=0; i < this[field].length; i++) {
503                     if (! Ext.isString(this[field][i]) || ! this[field][i].match(/@/) || ownEmailRegexp.test(this[field][i])) {
504                         this[field].splice(i, 1);
505                     }
506                 }
507             }, this);
508         }
509     },
510     
511     /**
512      * resolve recipient filter / queries addressbook
513      * 
514      * @param {String} field to/cc/bcc
515      */
516     resolveRecipientFilter: function(field) {
517         if (! Ext.isEmpty(this.record.get(field)) && Ext.isObject(this.record.get(field)[0]) &&  this.record.get(field)[0].operator) {
518             // found a filter
519             var filter = this.record.get(field);
520             this.record.set(field, []);
521             
522             this['AddressLoadMask'] = new Ext.LoadMask(Ext.getBody(), {msg: this.app.i18n._('Loading Mail Addresses')});
523             this['AddressLoadMask'].show();
524             
525             Tine.Addressbook.searchContacts(filter, null, function(response) {
526                 var mailAddresses = Tine.Felamimail.GridPanelHook.prototype.getMailAddresses(response.results);
527                 
528                 this.record.set(field, mailAddresses);
529                 this.recipientGrid.syncRecipientsToStore([field], this.record, true, false);
530                 this['AddressLoadMask'].hide();
531                 
532             }.createDelegate(this));
533         }
534     },
535     
536     /**
537      * sets / inits subject
538      */
539     initSubject: function() {
540         if (! this.record.get('subject')) {
541             if (! this.subject) {
542                 if (this.replyTo) {
543                     this.setReplySubject();
544                 } else if (this.forwardMsgs) {
545                     this.setForwardSubject();
546                 } else if (this.draftOrTemplate) {
547                     this.subject = this.draftOrTemplate.get('subject');
548                 }
549             }
550             this.record.set('subject', this.subject);
551         }
552         
553         delete this.subject;
554     },
555     
556     /**
557      * setReplySubject -> this.subject
558      * 
559      * removes existing prefixes + just adds 'Re: '
560      */
561     setReplySubject: function() {
562         var replyPrefix = 'Re: ',
563             replySubject = (this.replyTo.get('subject')) ? this.replyTo.get('subject') : '',
564             replySubject = replySubject.replace(/^((re|aw|antw|fwd|odp|sv|wg|tr):\s*)*/i, replyPrefix);
565             
566         this.subject = replySubject;
567     },
568     
569     /**
570      * setForwardSubject -> this.subject
571      */
572     setForwardSubject: function() {
573         this.subject =  this.app.i18n._('Fwd:') + ' ';
574         this.subject += this.forwardMsgs.length === 1 ?
575             this.forwardMsgs[0].get('subject') :
576             String.format(this.app.i18n._('{0} Message', '{0} Messages', this.forwardMsgs.length));
577     },
578     
579     /**
580      * decode this.replyTo / this.forwardMsgs from interwindow json transport
581      */
582     decodeMsgs: function() {
583         if (Ext.isString(this.draftOrTemplate)) {
584             this.draftOrTemplate = new this.recordClass(Ext.decode(this.draftOrTemplate));
585         }
586         
587         if (Ext.isString(this.replyTo)) {
588             this.replyTo = new this.recordClass(Ext.decode(this.replyTo));
589         }
590         
591         if (Ext.isString(this.forwardMsgs)) {
592             var msgs = [];
593             Ext.each(Ext.decode(this.forwardMsgs), function(msg) {
594                 msgs.push(new this.recordClass(msg));
595             }, this);
596             
597             this.forwardMsgs = msgs;
598         }
599     },
600     
601     /**
602      * fix input fields layout
603      */
604     fixLayout: function() {
605         if (! this.subjectField.rendered || ! this.accountCombo.rendered || ! this.recipientGrid.rendered) {
606             return;
607         }
608         
609         var scrollWidth = this.recipientGrid.getView().getScrollOffset();
610         this.subjectField.setWidth(this.subjectField.getWidth() - scrollWidth + 1);
611         this.accountCombo.setWidth(this.accountCombo.getWidth() - scrollWidth + 1);
612     },
613     
614     /**
615      * save message in folder
616      * 
617      * @param {String} folderField
618      */
619     onSaveInFolder: function (folderField) {
620         this.onRecordUpdate();
621         
622         var account = Tine.Tinebase.appMgr.get('Felamimail').getAccountStore().getById(this.record.get('account_id')),
623             folderName = account.get(folderField);
624         
625         if (! folderName || folderName == '') {
626             Ext.MessageBox.alert(
627                 this.app.i18n._('Failed'), 
628                 String.format(this.app.i18n._('{0} account setting empty.'), folderField)
629             );
630         } else if (this.isValid()) {
631             this.loadMask.show();
632             this.recordProxy.saveInFolder(this.record, folderName, {
633                 scope: this,
634                 success: function(record) {
635                     this.fireEvent('update', Ext.util.JSON.encode(this.record.data));
636                     this.purgeListeners();
637                     this.window.close();
638                 },
639                 failure: this.onRequestFailed,
640                 timeout: 150000 // 3 minutes
641             });
642         } else {
643             Ext.MessageBox.alert(_('Errors'), _('Please fix the errors noted.'));
644         }
645     },
646     
647     /**
648      * toggle save note
649      * 
650      * @param {} button
651      * @param {} e
652      */
653     onToggleSaveNote: function (button, e) {
654         this.record.set('note', (! this.record.get('note')));
655     },
656     
657     /**
658      * search for contacts as recipients
659      */
660     onSearchContacts: function() {
661         Tine.Felamimail.RecipientPickerDialog.openWindow({
662             record: new this.recordClass(Ext.copyTo({}, this.record.data, ['subject', 'to', 'cc', 'bcc']), Ext.id()),
663             listeners: {
664                 scope: this,
665                 'update': function(record) {
666                     var messageWithRecipients = Ext.isString(record) ? new this.recordClass(Ext.decode(record)) : record;
667                     this.recipientGrid.syncRecipientsToStore(['to', 'cc', 'bcc'], messageWithRecipients, true, true);
668                 }
669             }
670         });
671     },
672     
673     /**
674      * executed after record got updated from proxy
675      * 
676      * @private
677      */
678     onRecordLoad: function() {
679         // interrupt process flow till dialog is rendered
680         if (! this.rendered) {
681             this.onRecordLoad.defer(250, this);
682             return;
683         }
684         
685         var title = this.app.i18n._('Compose email:');
686         if (this.record.get('subject')) {
687             title = title + ' ' + this.record.get('subject');
688         }
689         this.window.setTitle(title);
690         
691         this.getForm().loadRecord(this.record);
692         this.attachmentGrid.loadRecord(this.record);
693         
694         if (this.record.get('note') && this.record.get('note') == '1') {
695             this.button_saveEmailNote.toggle();
696         }
697         var ticketFn = this.onAfterRecordLoad.deferByTickets(this),
698             wrapTicket = ticketFn();
699         
700         this.fireEvent('load', this, this.record, ticketFn);
701         wrapTicket();
702     },
703
704     /**
705      * overwrite, just hide the loadMask
706      */
707     onAfterRecordLoad: function() {
708         if (this.loadMask) {
709             this.loadMask.hide();
710         }
711     },
712     
713     /**
714      * executed when record gets updated from form
715      * - add attachments to record here
716      * - add alias / from
717      * 
718      * @private
719      */
720     onRecordUpdate: function() {
721         this.record.data.attachments = [];
722         var attachmentData = null;
723         
724         this.attachmentGrid.store.each(function(attachment) {
725             this.record.data.attachments.push(Ext.ux.file.Upload.file.getFileData(attachment));
726         }, this);
727         
728         var accountId = this.accountCombo.getValue(),
729             account = this.accountCombo.getStore().getById(accountId),
730             emailFrom = account.get('email');
731             
732         this.record.set('from_email', emailFrom);
733         
734         Tine.Felamimail.MessageEditDialog.superclass.onRecordUpdate.call(this);
735
736         this.record.set('account_id', account.get('original_id'));
737         
738         // need to sync once again to make sure we have the correct recipients
739         this.recipientGrid.syncRecipientsToRecord();
740     },
741     
742     /**
743      * show error if request fails
744      * 
745      * @param {} response
746      * @param {} request
747      * @private
748      * 
749      * TODO mark field(s) invalid if for example email is incorrect
750      * TODO add exception dialog on critical errors?
751      */
752     onRequestFailed: function(response, request) {
753         this.saving = false;
754         this.loadMask.hide();
755         
756         Tine.Tinebase.ExceptionHandler.handleRequestException(response);
757     },
758
759     /**
760      * init attachment grid + add button to toolbar
761      */
762     initAttachmentGrid: function() {
763         if (! this.attachmentGrid) {
764         
765             this.attachmentGrid = new Tine.widgets.grid.FileUploadGrid({
766                 fieldLabel: this.app.i18n._('Attachments'),
767                 hideLabel: true,
768                 filesProperty: 'attachments',
769                 // TODO     think about that -> when we deactivate the top toolbar, we lose the dropzone for files!
770                 //showTopToolbar: false,
771                 anchor: '100% 95%'
772             });
773             
774             // add file upload button to toolbar
775             this.action_addAttachment = this.attachmentGrid.getAddAction();
776             this.action_addAttachment.plugins[0].dropElSelector = null;
777             this.action_addAttachment.plugins[0].onBrowseButtonClick = function() {
778                 this.southPanel.expand();
779             }.createDelegate(this);
780             
781             this.tbar.get(0).insert(1, Ext.apply(new Ext.Button(this.action_addAttachment), {
782                 scale: 'medium',
783                 rowspan: 2,
784                 iconAlign: 'top'
785             }));
786         }
787     },
788     
789     /**
790      * init account (from) combobox
791      * 
792      * - need to create a new store with an account record for each alias
793      */
794     initAccountCombo: function() {
795         var accountStore = Tine.Tinebase.appMgr.get('Felamimail').getAccountStore(),
796             accountComboStore = new Ext.data.ArrayStore({
797                 fields   : Tine.Felamimail.Model.Account
798             });
799         
800         var aliasAccount = null,
801             aliases = null,
802             id = null
803             
804         accountStore.each(function(account) {
805             aliases = [ account.get('email') ];
806
807             if (account.get('type') == 'system') {
808                 // add identities / aliases to store (for systemaccounts)
809                 var user = Tine.Tinebase.registry.get('currentAccount');
810                 if (user.emailUser && user.emailUser.emailAliases && user.emailUser.emailAliases.length > 0) {
811                     aliases = aliases.concat(user.emailUser.emailAliases);
812                 }
813             }
814             
815             for (var i = 0; i < aliases.length; i++) {
816                 id = (i == 0) ? account.id : Ext.id();
817                 aliasAccount = account.copy(id);
818                 if (i > 0) {
819                     aliasAccount.data.id = id;
820                     aliasAccount.set('email', aliases[i]);
821                 }
822                 aliasAccount.set('name', aliasAccount.get('name') + ' (' + aliases[i] +')');
823                 aliasAccount.set('original_id', account.id);
824                 accountComboStore.add(aliasAccount);
825             }
826         }, this);
827         
828         this.accountCombo = new Ext.form.ComboBox({
829             name: 'account_id',
830             ref: '../../accountCombo',
831             plugins: [ Ext.ux.FieldLabeler ],
832             fieldLabel: this.app.i18n._('From'),
833             displayField: 'name',
834             valueField: 'id',
835             editable: false,
836             triggerAction: 'all',
837             store: accountComboStore,
838             mode: 'local',
839             listeners: {
840                 scope: this,
841                 select: this.onFromSelect
842             }
843         });
844     },
845     
846     /**
847      * if 'account_id' is changed we need to update the signature
848      * 
849      * @param {} combo
850      * @param {} newValue
851      * @param {} oldValue
852      */
853      onFromSelect: function(combo, record, index) {
854         
855         // get new signature
856         var accountId = record.get('original_id');
857         var newSignature = Tine.Felamimail.getSignature(accountId);
858         var signatureRegexp = new RegExp('<br><br><span id="felamimail\-body\-signature">\-\-<br>.*</span>');
859         
860         // update signature
861         var bodyContent = this.htmlEditor.getValue();
862         bodyContent = bodyContent.replace(signatureRegexp, newSignature);
863         
864         this.htmlEditor.setValue(bodyContent);
865     },
866     
867     /**
868      * returns dialog
869      * 
870      * NOTE: when this method gets called, all initialisation is done.
871      * 
872      * @return {Object}
873      * @private
874      */
875     getFormItems: function() {
876         
877         this.initAttachmentGrid();
878         this.initAccountCombo();
879         
880         this.recipientGrid = new Tine.Felamimail.RecipientGrid({
881             record: this.record,
882             i18n: this.app.i18n,
883             hideLabel: true,
884             composeDlg: this,
885             autoStartEditing: !this.AddressLoadMask
886         });
887         
888         this.southPanel = new Ext.Panel({
889             region: 'south',
890             layout: 'form',
891             height: 150,
892             split: true,
893             collapseMode: 'mini',
894             header: false,
895             collapsible: true,
896             collapsed: (this.record.bodyIsFetched() && (! this.record.get('attachments') || this.record.get('attachments').length == 0)),
897             items: [this.attachmentGrid]
898         });
899
900         this.htmlEditor = new Tine.Felamimail.ComposeEditor({
901             fieldLabel: this.app.i18n._('Body'),
902             flex: 1  // Take up all *remaining* vertical space
903         });
904         
905         return {
906             border: false,
907             frame: true,
908             layout: 'border',
909             items: [
910                 {
911                 region: 'center',
912                 layout: {
913                     align: 'stretch',  // Child items are stretched to full width
914                     type: 'vbox'
915                 },
916                 listeners: {
917                     'afterlayout': this.fixLayout,
918                     scope: this
919                 },
920                 items: [
921                     this.accountCombo, 
922                     this.recipientGrid, 
923                 {
924                     xtype:'textfield',
925                     plugins: [ Ext.ux.FieldLabeler ],
926                     fieldLabel: this.app.i18n._('Subject'),
927                     name: 'subject',
928                     ref: '../../subjectField',
929                     enableKeyEvents: true,
930                     listeners: {
931                         scope: this,
932                         // update title on keyup event
933                         'keyup': function(field, e) {
934                             if (! e.isSpecialKey()) {
935                                 this.window.setTitle(
936                                     this.app.i18n._('Compose email:') + ' ' 
937                                     + field.getValue()
938                                 );
939                             }
940                         },
941                         'focus': function(field) {
942                             this.subjectField.focus(true, 100);
943                         }
944                     }
945                 }, this.htmlEditor
946                 ]
947             }, this.southPanel]
948         };
949     },
950
951     /**
952      * is form valid (checks if attachments are still uploading / recipients set)
953      * 
954      * @return {Boolean}
955      */
956     isValid: function() {
957         this.validationErrorMessage = Tine.Felamimail.MessageEditDialog.superclass.getValidationErrorMessage.call(this);
958         
959         var result = true;
960         
961         if (this.attachmentGrid.isUploading()) {
962             result = false;
963             this.validationErrorMessage = this.app.i18n._('Files are still uploading.');
964         }
965         
966         if (result) {
967             result = this.validateRecipients();
968         }
969         
970         
971         return (result && Tine.Felamimail.MessageEditDialog.superclass.isValid.call(this));
972     },
973     
974     /**
975      * generic apply changes handler
976      * - NOTE: overwritten to check here if the subject is empty and if the user wants to send an empty message
977      * 
978      * @param {Ext.Button} button
979      * @param {Event} event
980      * @param {Boolean} closeWindow
981      * 
982      * TODO add note editing textfield here
983      */
984     onApplyChanges: function(closeWindow) {
985         Tine.log.debug('Tine.Felamimail.MessageEditDialog::onApplyChanges()');
986         
987         this.loadMask.show();
988         
989         if (this.getForm().findField('subject').getValue() == '') {
990             Tine.log.debug('Tine.Felamimail.MessageEditDialog::onApplyChanges - empty subject');
991             Ext.MessageBox.confirm(
992                 this.app.i18n._('Empty subject'),
993                 this.app.i18n._('Do you really want to send a message with an empty subject?'),
994                 function (button) {
995                     Tine.log.debug('Tine.Felamimail.MessageEditDialog::doApplyChanges - button: ' + button);
996                     if (button == 'yes') {
997                         this.doApplyChanges(closeWindow);
998                     } else {
999                         this.loadMask.hide();
1000                     }
1001                 },
1002                 this
1003             );
1004         } else {
1005             Tine.log.debug('Tine.Felamimail.MessageEditDialog::doApplyChanges - call parent');
1006             this.doApplyChanges(closeWindow);
1007         }
1008         
1009         /*
1010         if (this.record.data.note) {
1011             // show message box with note editing textfield
1012             //console.log(this.record.data.note);
1013             Ext.Msg.prompt(
1014                 this.app.i18n._('Add Note'),
1015                 this.app.i18n._('Edit Email Note Text:'), 
1016                 function(btn, text) {
1017                     if (btn == 'ok'){
1018                         record.data.note = text;
1019                     }
1020                 }, 
1021                 this,
1022                 100, // height of input area
1023                 this.record.data.body 
1024             );
1025         }
1026         */
1027     },
1028     
1029     /**
1030      * checks recipients
1031      * 
1032      * @return {Boolean}
1033      */
1034     validateRecipients: function() {
1035         var result = true;
1036         
1037         if (this.record.get('to').length == 0 && this.record.get('cc').length == 0 && this.record.get('bcc').length == 0) {
1038             this.validationErrorMessage = this.app.i18n._('No recipients set.');
1039             result = false;
1040         }
1041         
1042         return result;
1043     },
1044     
1045     /**
1046      * get validation error message
1047      * 
1048      * @return {String}
1049      */
1050     getValidationErrorMessage: function() {
1051         return this.validationErrorMessage;
1052     },
1053     
1054     /**
1055      * fills the recipient grid with the records gotten from this.fetchRecordsOnLoad
1056      * @param {Array} contacts
1057      */
1058     fillRecipientGrid: function(contacts) {
1059         this.recipientGrid.addRecordsToStore(contacts, 'to');
1060         this.recipientGrid.setFixedHeight(true);
1061     },
1062     
1063     /**
1064      * fetches records to send an email to
1065      */
1066     fetchRecordsOnLoad: function(dialog, record, ticketFn) {
1067         var interceptor = ticketFn(),
1068             sf = Ext.decode(this.selectionFilter);
1069             
1070         Tine.log.debug('Fetching additional records...');
1071         Tine.Addressbook.contactBackend.searchRecords(sf, null, {
1072             scope: this,
1073             success: function(result) {
1074                 this.fillRecipientGrid(result.records);
1075                 interceptor();
1076             }
1077         });
1078         this.addressesLoaded = true;
1079     }
1080 });
1081
1082 /**
1083  * Felamimail Edit Popup
1084  * 
1085  * @param   {Object} config
1086  * @return  {Ext.ux.Window}
1087  */
1088 Tine.Felamimail.MessageEditDialog.openWindow = function (config) {
1089     var window = Tine.WindowFactory.getWindow({
1090         width: 700,
1091         height: 700,
1092         name: Tine.Felamimail.MessageEditDialog.prototype.windowNamePrefix + Ext.id(),
1093         contentPanelConstructor: 'Tine.Felamimail.MessageEditDialog',
1094         contentPanelConstructorConfig: config
1095     });
1096     return window;
1097 };