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