Ext.namespace('Zarafa.core.ui'); /** * @class Zarafa.core.ui.MessageContentPanel * @extends Zarafa.core.ui.RecordContentPanel * @xtype zarafa.messagecontentpanel * * This extends the normal {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel} which is * used for displaying and editing {@link Zarafa.core.data.IPMRecord records}. This * class is used for Messages which can be {@link #sendRecord send} as well. * * FIXME: Provide default buttons which are common for all content panels with messages */ Zarafa.core.ui.MessageContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, { /** * The queue containing callback functions which can be used to validate the * {@link Zarafa.core.data.IPMRecord} which is about to be send. If the queue * completes successfully, the message can be send, otherwise it will be cancelled. * @property * @type Zarafa.core.data.CallbackQueue * @protected */ sendValidationQueue : undefined, /** * @cfg {String/Object} sendingText When {@link #showInfoMask} is true, then this text * will be shown when the message is being send. When an object is provided which contains * the 'msg' and 'title' fields respectively. */ sendingText : { msg : _('Sending') + '...' }, /** * @cfg {String/Object} sendingDoneText When {@link #showInfoMask} is true, then this text * will be shown when the message has been sent. When an object is provided which contains * the 'msg' and 'title' fields respectively. */ sendingDoneText :{ title: _('Sent'), msg : _('Sent successfully') }, /** * Indicates if the panel is currently busy sending data to the server. * @property * @type Boolean */ isSending : false, /** * @cfg {Boolean} closeOnSend Config option to close the panel when client recieves confirmation of message is sent. */ closeOnSend : false, /** * @constructor * @param config Configuration structure */ constructor : function(config) { config = config || {}; config.plugins = Ext.value(config.plugins, []); config.plugins.push({ ptype : 'zarafa.markasreadplugin', ignoreReadFlagTimer : true }); this.addEvents( /** * @event beforesendrecord * Fires when a record is about to be {@link #sendRecord send}. * This will allow Subclasses modify the {@link Zarafa.core.data.IPMRecord record} before * it is being send. This event will be followed by the {@link #beforesaverecord} and * {@link #saverecord} (which triggers the send action on the server) and ends with the * {@link #sendrecord} event when everything has been completed. * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is send * @param {Zarafa.core.data.IPMRecord} record The record which is going to be send * @return {Boolean} false if the record should not be send. */ 'beforesendrecord', /** * @event sendrecord * Fires after the record has been {@link #sendRecord send}. * This follows the {@link #saverecord} event which triggers the send action on the server. * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is send * @param {Zarafa.core.data.IPMRecord} record The record which has been send */ 'sendrecord', /** * @event aftersendrecord * Fires after the record has been sent successfully. * This follows the {@link #updaterecord} event when the server responded to the send action * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is send * @param {Zarafa.core.data.IPMRecord} record The record which has been send */ 'aftersendrecord' ); Zarafa.core.ui.MessageContentPanel.superclass.constructor.call(this, config); // Initialize the Send Validation queue // which is used for validating the message before sending this.createSendValidationQueue(); if (Ext.isString(this.sendingText)) { this.sendingText = { title : '', msg : this.sendingText }; } if (Ext.isString(this.sendingDoneText)) { this.sendingDoneText = { title : '', msg : this.sendingDoneText }; } if(this.record) { var store = this.record.getStore(); if (store) { store.on('update', this.syncUpdatesToShadowStore, this); } } }, /** * Event handler which is triggered when a {@link Zarafa.core.data.IPMRecord record} has been * updated from the server. * When any record which belongs to {@link Zarafa.core.data.IPMStore} gets updated, respective * {@link Zarafa.core.data.ShadowStore ShadowStore} record needs to be updated, if any. * * @param {Zarafa.core.data.IPMStore} store The store which performs the update * @param {Zarafa.core.data.IPMRecord} record The Record which has been updated * @param {String} operation The update operation being performed. * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}). * @private */ syncUpdatesToShadowStore : function(store, record, operation) { if (Zarafa.core.EntryId.compareEntryIds(record.get('entryid'), this.record.get('entryid'))) { if(operation === Ext.data.Record.COMMIT){ var receivedFlags = record.get('message_flags'); var categories = record.get('categories'); var commit = false; // Stop modification-tracking to prevent dirty mark this.record.setUpdateModificationsTracking(false); // As of now, message_flags and categories property is being processed only because some properties // seems to be missing in MailStore-record as compare to ShadowStore-record result into some weird // behavior with mail-formatting and meeting-request-accept functionalities. this.record.beginEdit(); if (this.record.get('message_flags') !== receivedFlags) { this.record.set('message_flags', receivedFlags); commit = true; } if (this.record.get('categories') !== categories) { this.record.set('categories', categories); commit = true; } this.record.endEdit(); if ( commit ){ this.record.commit(); } // Start modification-tracking back for future user changes this.record.setUpdateModificationsTracking(true); } } }, /** * Create and initialize the {@link #sendValidationQueue}. This will add various * validation steps which must be executed to determine if the message can be send. * @protected */ createSendValidationQueue : function() { // Create a callback queue to validate the record before sending this.sendValidationQueue = new Zarafa.core.data.CallbackQueue(); // Add a validation step to determine if there are recipients this.sendValidationQueue.add(this.validateEmptyRecipients, this); // Add a validation step to determine if a subject was provided this.sendValidationQueue.add(this.validateEmptySubject, this); // Add a validation step to determine if all recipients are resolved this.sendValidationQueue.add(this.validateResolvedRecipients, this); // Add a validation step to determine if all attachments are uploaded this.sendValidationQueue.add(this.validateAttachmentUpload, this); }, /** * If {@link showInfoMask} is enabled, this will display the {@link #savingText} to the user. * @protected * @overridden */ displayInfoMask : function() { if (this.showInfoMask === false) { return; } if (this.record.hasMessageAction('send') || this.record.getMessageAction('sendResponse')) { container.getNotifier().notify('info.sending', this.sendingText.title, this.sendingText.msg, { container : this.getEl() }); } else { Zarafa.core.ui.MessageContentPanel.superclass.displayInfoMask.apply(this, arguments); } }, /** * If {@link #showInfoMask} is enabled, and {@link #displayInfoMask} has been called, this * will remove the notification again. When saving has been successfull, a new notification * will be shown to display the {@link #savingDoneText}. * @param {Boolean} success false to disable the display of {@link #savingDoneText}. * @protected * @overridden */ hideInfoMask : function(success) { if (this.showInfoMask === false) { return; } if (this.record.hasMessageAction('send') || this.record.getMessageAction('sendResponse')) { if (success !== false) { container.getNotifier().notify('info.sent', this.sendingDoneText.title, this.sendingDoneText.msg); } } else { Zarafa.core.ui.MessageContentPanel.superclass.hideInfoMask.apply(this, arguments); } }, /** * Event handler which is fired when the the {@link Ext.data.Store store} for the {@link #record} * fires the {@link Ext.data.Store#beforesave} event. This will check if the event was really regarding * {@link #record} and will update the {@link #isSaving} or {@link #isSending} property and * {@link #displayInfoMask display the infobox}. * @param {Ext.data.Store} store The store which fired the event * @param {Object} data The object data which is being saved to the server * @private */ onBeforeSaveRecord : function(store, data) { if (data && ((data.update && data.update.indexOf(this.record) >= 0) || (data.create && data.create.indexOf(this.record) >= 0))) { this.isSending = this.record.hasMessageAction('send') || this.record.hasMessageAction('sendResponse'); } Zarafa.core.ui.MessageContentPanel.superclass.onBeforeSaveRecord.apply(this, arguments); }, /** * Fired when the {@link #updaterecord} event has been fired. If {@link #showSavingMask} is enabled, * this will display the {@link #savingText to indicate the saving is in progress. * * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel which fired the event * @param {String} action write Action that ocurred. Can be one of * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or * {@link Ext.data.Record.COMMIT COMMIT} * @param {Zarafa.core.data.IPMRecord} record The record which was updated * @private * @overridden */ onUpdateRecord : function(contentpanel, action, record) { Zarafa.core.ui.MessageContentPanel.superclass.onUpdateRecord.apply(this, arguments); var isSending = this.isSending; /** * Close panel when it message has been sent or saved at serverside * and here we have received confirmation */ if (action == Ext.data.Record.COMMIT) { var sendAction = record.hasMessageAction('send') || this.record.hasMessageAction('sendResponse'); if (sendAction) { this.fireEvent('aftersendrecord', this, this.record); this.isSending = false; } if (isSending && this.closeOnSend && sendAction) { this.close(); // We closed the panel, stop the event propagation as there is // no longer an UI that can be updated. return false; } } }, /** * Fired when the {@link #exceptionrecord} event has been fired. Will reset {@link #isSending}. * * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} * for description. * @private * @overridden */ onExceptionRecord : function(proxy, type, action, options, response, args) { Zarafa.core.ui.MessageContentPanel.superclass.onExceptionRecord.apply(this, arguments); if (type !== "open") { this.isSending = false; } }, /** * Save all changes made to the {@link #record} and send * the message to the specified recipients. */ sendRecord : function() { // If record is saving then wait till saving is done. register aftersaverecord event // on message content panel. if(this.isSaving === true) { this.on('aftersaverecord', this.onAfterSaveRecord, this, {single : true}); return; } if (this.recordComponentPlugin.allowWrite === false || this.isSending === true) { return; } if (this.fireEvent('beforesendrecord', this, this.record) === false) { return; } this.isSending = true; // Start the validation queue to determine if the record can be // send to the recipients correctly. If successfull, onCompleteValidateSendRecord // is called to send the actual record. this.sendValidationQueue.run(this.onCompleteValidateSendRecord, this); }, /** * Event handler which is called when send and save button is clicked * immediately one after another. Function registers {@link Zarafa.core.data.MAPIStore#write} * event which will call {@link #sendRecord} function when saving has been completed. * * This function is use to solve below problem. * * If we call {@link #sendRecord} straight away after {@link Zarafa.core.ui.RecordContentPanel #aftersaverecord} event then * {@link Ext.data.Store #write} event is triggerd on {@link Zarafa.core.data.ShadowStore ShadowStore} for this record, * It will call {@link Zarafa.core.data.MAPIRecord.clearMessageActions clearMessageActions}(i.e. send=true), * so when we get response of send request there will be no reference of the send request in record. * So we will not be able to perform {@link #aftersendrecord} event functionalities like, * Closing the dialog, Showing the 'Sent Successfully' notification, hiding the 'Sending...' notification. * * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is saved * @param {Zarafa.core.data.IPMRecord} record The record which has been saved */ onAfterSaveRecord : function(contentPanel, record) { var store = record.getStore(); this.mon(store, 'write', this.sendRecord, this, {single : true}); }, /** * Validation function for the {@link #sendValidationQueue} to check if the Message * can be send to the recipients. * * This validates if recipients were provided in the message. If not, then the * message cannot be send * * @param {Function} callback The callback to call to continue in the queue * @private */ validateEmptyRecipients : function(callback) { // Check if recipients have been provided var recipientStore = this.record.getRecipientStore(); if (!Ext.isDefined(recipientStore) || recipientStore.getCount() === 0) { container.getNotifier().notify('warning.sending', _('Kopano WebApp'), _('Please specify a recipient')); // The message cannot be send, cancel the callbacks callback(false); } else { // Check if the recipient_type is correct, there should at least be 1 TO/CC/BCC recipient var invalid = true; recipientStore.each(function(recip) { if (recip.get('recipient_type') !== Zarafa.core.mapi.RecipientType.MAPI_ORIG) { invalid = false; return false; } }, this); if (invalid) { container.getNotifier().notify('warning.sending', _('Kopano WebApp'), _('Please specify a recipient')); // The message cannot be send, cancel the callbacks callback(false); } else { // The message can be send, the next callback can be called. callback(true); } } }, /** * Validation function for the {@link #sendValidationQueue} to check if the Message * can be send to the recipients. * * This validates if the attachment is uploading while sending mail, if yes then the user is * asked if he still wants to send the message without current uploading attachment. * * @param {Function} callback The callback to call to continue in the queue * @private */ validateAttachmentUpload : function(callback) { var attachmentStore = this.record.getAttachmentStore(); var isAllAttachUploaded = true; attachmentStore.each(function(attach) { if(!attach.isUploaded()) { isAllAttachUploaded = false; // stop further iterations return false; } }, this); // Check if the attachment has been uploaded if (!isAllAttachUploaded){ var message = _('The attached files are not uploaded yet.'); message += '<br/>'; message += _('Do you want to send this message without attachments?'); Zarafa.common.dialogs.MessageBox.addCustomButtons({ title : _('Kopano WebApp'), icon: Ext.MessageBox.WARNING, msg : message, fn : function(button) { callback(button === 'sendanyway'); }, customButton : [{ text : _('Don\'t Send'), name : 'dontsend' }, { text : _('Send Anyway'), name : 'sendanyway' }] }); }else { callback(true); } }, /** * Validation function for the {@link #sendValidationQueue} to check if the Message * can be send to the recipients. * * This validates if the subject was provided in the message, if not then the user is * asked if he still wants to send the message. * * @param {Function} callback The callback to call to continue in the queue * @private */ validateEmptySubject : function(callback) { // Check if the subject has been provided if (Ext.isEmpty(this.record.get('subject'))){ Ext.MessageBox.confirm( _('Kopano WebApp'), _('Send this message without a subject?'), function (buttonClicked) { // If the user clicked "yes" then the // callback queue can continue, otherwise // it will have to abort. callback(buttonClicked == 'yes'); }, this); } else { // The subject is provided, we can continue callback(true); } }, /** * Validation function for the {@link #sendValidationQueue} to check if the Message * can be send to the recipients. * * This validates if all recipients were resolved in the message. If not, then * the the recipientStore will resolve everything. * * @param {Function} callback The callback to call to continue in the queue * @private */ validateResolvedRecipients : function(callback) { var recipientStore = this.record.getRecipientStore(); var unresolved = recipientStore.getUnresolvedRecipients(); var invalid = recipientStore.getInvalidRecipients(); if (!Ext.isEmpty(invalid)) { // Invalid recipients were found, show error to the user // and cancel the callbacks as we cannot send the message container.getNotifier().notify('warning.sending', '', _('Not all recipients could be resolved')); callback(false); } else if(!Ext.isEmpty(unresolved)) { // Unresolved recipients were found, try to resolve them, and have the event handler // call this function recursively so we can check again. this.mon(recipientStore, 'resolved', this.validateResolvedRecipients.createDelegate(this, [ callback ], false), this, { single : true }); recipientStore.resolve(unresolved, { cancelPreviousRequest : true }); } else { // No invalid or unresolved recipients, we can continue // the callback queue to send the message. callback(true); } }, /** * Callback function for the {@link #sendValidationQueue} which is called when the queue has been * completed. If the queue ended successfully then the {@link #record} will get the * {@link Zarafa.core.data.IPMRecord#addMessageAction 'send' message action} and will then * {@link #saveRecord save the message} in order to send. * @param {Boolean} success True if the queue ended successfully * @private */ onCompleteValidateSendRecord : function(success) { if (success) { this.record.addMessageAction('send', true); if (this.saveRecord() !== false) { this.fireEvent('sendrecord', this, this.record); } } else { this.isSending = false; } } }); Ext.reg('zarafa.messagecontentpanel', Zarafa.core.ui.MessageContentPanel);