Ext.namespace('Zarafa.core.data'); Ext.data.Api.actions.open = 'open'; /** * @class Zarafa.core.data.IPMStore * @extends Zarafa.core.data.MAPIStore * @xtype zarafa.ipmstore * * The {@link Zarafa.core.data.IPMStore IPMStore} represents the collection of messages. */ Zarafa.core.data.IPMStore = Ext.extend(Zarafa.core.data.MAPIStore, { /** * @cfg {Boolean} standalone If true, the {@link Zarafa.core.data.IPMStore IPMStore} * will not be hooked into the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}. This will prevent * listening to events coming from the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}. * Defaults to false. */ standalone : false, /** * @cfg {Boolean} serveronly If true, the {@link Zarafa.core.data.IPMStore IPMStore} * will be hooked into the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}, but will * only do that for events which are triggered by a serverside change (e.g. a write * event coming from the server, after a successful save). * Defaults to false. */ serveronly : false, /** * @constructor * @param config Configuration structure */ constructor : function(config) { config = config || {}; Ext.applyIf(config, { remoteGroup : true }); this.addEvents( /** * @event beforenotify * Event fired before a {@link #onNotify notification} is handled. * * @param {Zarafa.core.data.IPMStore} store The store which fired the event * @param {Zarafa.core.data.Notifications} notification The notification action * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification. * @param {Object} data The data which has been recieved from the PHP-side which must be applied * to the given records. * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received * @param {Boolean} success The success status, True if the notification was successfully recieved. * @return {Boolean} false to cancel the notification handling */ 'beforenotify', /** * @event notify * Event fired after a {@link #onNotify notification} is handled. * * @param {Zarafa.core.data.IPMStore} store The store which fired the event * @param {Zarafa.core.data.Notifications} notification The notification action * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification. * @param {Object} data The data which has been recieved from the PHP-side which must be applied * to the given records. * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received * @param {Boolean} success The success status, True if the notification was successfully recieved. */ 'notify' ); Zarafa.core.data.IPMStore.superclass.constructor.call(this, config); // Register store with the store manager if (!this.standalone) { Zarafa.core.data.IPMStoreMgr.register(this, this.serveronly); } }, /** * Initialize all events which Zarafa.core.data.IPMStore IPMStore} will listen to. * @private */ initEvents : function() { Zarafa.core.data.IPMStore.superclass.initEvents.call(this); if (!this.standalone) { if (!this.serveronly) { Zarafa.core.data.IPMStoreMgr.on('beforerecordsave', this.onExternalSave, this); } Zarafa.core.data.IPMStoreMgr.on('afterrecordwrite', this.onExternalWrite, this); } }, /** * Filter a list of {@link Zarafa.core.data.IPMRecord records} by checking if the record * belongs to this Store. This comparison is based on checking if the entryid of the given * records match the entryid of the records inside the store. If the records are being * {@link Zarafa.core.data.JsonReader#realize realized} by the {@link Zarafa.core.data.JsonReader JsonReader} * then we check if the parent_entryid matches this store. * * What will be returned is an object containing the records as present inside the store, * and the data objects which should be applied to them (the data from the records of the * store that triggered the event). * * @param {Zarafa.core.data.IPMRecord|Array} records The record or records to filter * @param {Ext.data.Api.actions} action The API action for which the updates are looked for. * @return {Object} Object containing the key 'records' containing the array of records inside * this store, and the key 'updatedRecords' containing the array of records which should be * applied to those records. * @private */ getRecordsForUpdateData : function(records, action) { var results = { records: [], updatedRecords : [] }; if (Ext.isDefined(records)) { if (!Array.isArray(records)) { records = [ records ]; } if (action === Ext.data.Api.actions.create) { for (var i = 0, len = records.length; i < len; i++) { var rec = records[i]; var storeRec = this.containsFolderInLastLoad(rec.get('parent_entryid')); if (rec.store !== this && storeRec) { results.records.push(rec.copy()); results.updatedRecords.push(rec); } } } else { for (var i = 0, len = records.length; i < len; i++) { var rec = records[i]; var storeRec = this.findBy(function (record, id) { if(rec.equals(record)) { return true; } }); if(storeRec < 0) { continue; } storeRec = this.getAt(storeRec); if (rec.store !== this && storeRec) { results.records.push(storeRec); results.updatedRecords.push(rec); } } } } return results; }, /** * Event handler which is raised when a {@link Zarafa.core.data.IPMRecord IPMRecord} is about * to be saved by a different {@link Zarafa.core.data.IPMStore IPMStore}. However the same * {@link Zarafa.core.data.IPMRecord IPMRecord} may have been listed in multiple * {@link Zarafa.core.data.IPMStore IPMStores} at the same time. Thus check if the saved * {@link Zarafa.core.data.IPMRecord IPMRecord} is also listed in this store, and if that is the * case, update it here as well. This way we don't have to wait until a reload from the * server occurs, and the {@link Zarafa.core.data.IPMRecord IPMRecord} is updated on all stores * instantly. * * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the * {@link Zarafa.core.data.IPMRecord record} belongs. * @param {Object} data An object containing the data that is to be saved. * The object will contain a key for each appropriate action, with an array * of updated data for each record. */ onExternalSave : function(store, data) { var result; if (store === this) { return; } result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.open], Ext.data.Api.actions.open); if (!Ext.isEmpty(result.records)) { this.onNotify(Zarafa.core.data.Notifications.objectModified, result.records, result.updatedRecords); } result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.update], Ext.data.Api.actions.update); if (!Ext.isEmpty(result.records)) { this.onNotify(Zarafa.core.data.Notifications.objectModified, result.records, result.updatedRecords); } result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.create], Ext.data.Api.actions.create); if (!Ext.isEmpty(result.records)) { this.onNotify(Zarafa.core.data.Notifications.objectCreated, result.records, result.updatedRecords); } result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.destroy], Ext.data.Api.actions.destroy); if (!Ext.isEmpty(result.records)) { this.onNotify(Zarafa.core.data.Notifications.objectDeleted, result.records, result.updatedRecords); } }, /** * Event handler which is raised when a {@link Zarafa.core.data.IPMRecord IPMRecord} has been * updated from the server. This {@link Zarafa.core.data.IPMRecord IPMRecord} may have been listed in * multiple {@link Zarafa.core.data.IPMStore IPMStores} at the same time. Thus we need to * check if this {@link Zarafa.core.data.IPMRecord IPMRecord} is also listed in this store, and if that * is the case, update it here as well. This way we don't have to wait until a reload from the * server occurs for this specific store. * * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the * {@link Zarafa.core.data.IPMRecord record} belongs. * @param {String} action [Ext.data.Api.actions.create|update|destroy|open] * @param {Object} data The 'data' picked-out out of the response for convenience. * @param {Ext.Direct.Transaction} res * @param {Zarafa.core.data.IPMrecord} record The most recent version of the record * which came from the server. */ onExternalWrite : function(store, action, data, res, record) { if (store === this) { return; } var obj = {}; if (Array.isArray(record)) { obj[action] = record; } else { obj[action] = [ record ]; } this.onExternalSave(store, obj); }, /** * Checks whether any of the folders that were included in the parameters during the last load, * matches the supplied entryid argument. * @param {String|Array} entryidList Entryid of the folder * @return {Boolean} Returns true when entryid matches, false when it does not. */ containsFolderInLastLoad: function(entryidList) { if (!this.lastOptions) { return false; } if (!Array.isArray(entryidList)) { entryidList = [ entryidList ]; } var folderList = this.lastOptions.folder; if (Ext.isDefined(folderList)) { if (!Array.isArray(folderList)) { folderList = [ folderList ]; } for (var i = 0, len = folderList.length; i < len; i++) { for (var j = 0, jlen = entryidList.length; j < jlen; j++) { if (Zarafa.core.EntryId.compareEntryIds(folderList[i].get('entryid'), entryidList[j])) { return true; } } } } return false; }, /** * Notification handler which is called automatically by the * {@link Zarafa.core.data.IPMNotificationResponseHandler NotificationResponseHandler} * when a notification has been recieved for a {@link Zarafa.core.data.IPMRecord record} which * has been loaded by this store. * * @param {Zarafa.core.data.Notifications} action The notification action * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification. * @param {Object} data The data which has been recieved from the PHP-side which must be applied * to the given records. * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received * @param {Boolean} success The success status, True if the notification was successfully recieved. */ onNotify : function(action, records, data, timestamp, success) { if (this.fireEvent('beforenotify', this, action, records, data, timestamp, success) !== false) { var handler = this['onNotify' + Ext.util.Format.capitalize(action)]; if (Ext.isFunction(handler)) { handler.call(this, action, records, data, timestamp, success); } /* * Fire the notify event only for record of this store. * Notify event is only for records, not for store it self. */ if (!Ext.isEmpty(records) && records[0] instanceof Ext.data.Record) { this.fireEvent('notify', this, action, records, data, timestamp, success); } } }, /** * Notification handler called by {@link #onNotify} when * a {@link Zarafa.core.data.Notifications#objectDeleted objectDeleted} * notification has been recieved. * * This will remove the message from the store. * * @param {Zarafa.core.data.Notifications} action The notification action * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification. * @param {Object} data The data which has been recieved from the PHP-side which must be applied * to the given records. * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received * @param {Boolean} success The success status, True if the notification was successfully recieved. * @private */ onNotifyObjectdeleted : function(action, records, data, timestamp, success) { if (!Array.isArray(records)) { records = [ records ]; } // To prevent the Deletion to be saved back to the server again, // we mark all the records which we are about to delete as phantom. for (var i = 0, len = records.length; i < len; i++) { records[i].setEventPropagation(false); records[i].phantom = true; } this.remove(records); }, /** * Notification handler called by {@link #onNotify} when * a {@link Zarafa.core.data.Notifications#objectModified objectModified} * notification has been recieved. * * This will update the message in the store. * * @param {Zarafa.core.data.Notifications} action The notification action * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification. * @param {Object} data The data which has been recieved from the PHP-side which must be applied * to the given records. * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received * @param {Boolean} success The success status, True if the notification was successfully recieved. * @private */ onNotifyObjectmodified : function(action, records, data, timestamp, success) { var reloadStore = false; if (!Array.isArray(records)) { records = [ records ]; } // Temporarily disable event propagation, every store (which contains the provided records) // will receive this notification. So we have to disable event propagation to prevent // bouncing events around. for (var i = 0, len = records.length; i < len; i++) { var record = records[i]; var singleData = (Array.isArray(data)) ? data[i] : data; record.setEventPropagation(false); var recordMessageClass = record.get('message_class'); if (singleData instanceof Ext.data.Record) { // whenever message class is changed that means we will need to recreate record as the old record // will be having different set of fields, for that we simply reload the store if(recordMessageClass !== singleData.get('message_class')) { // If changed message class is part of original message class, then original message record // will already have all the properties, so for that case we don't need reload if(recordMessageClass.indexOf(singleData.get('message_class')) !== 0) { reloadStore = true; } } // Merge the changes into the record without using the JSONReader. record.applyData(singleData); // Commit the changes, it is a merge, so there are no local changes. record.commit(); } else if(Object.keys(singleData).length !== 0) { // whenever message class is changed that means we will need to recreate record as the old record // will be having different sets of fields, for that we simply reload the store var updateMessageClass = singleData['message_class'] || singleData['props']['message_class']; if(recordMessageClass !== updateMessageClass) { // If changed message class is part of original message class, then original message record // will already have all the properties, so for that case we don't need reload if(recordMessageClass.indexOf(updateMessageClass) !== 0) { reloadStore = true; } } // Simply merge the record using the JsonReader, this will cause a 'update' event to be fired with // a COMMIT action. Because it is a commit, this store will not mark the record as dirty. this.reader.update(record, singleData); } record.setEventPropagation(true); } if(reloadStore) { this.reload(); } }, /** * Notification handler called by {@link #onNotify} when * a {@link Zarafa.core.data.Notifications#objectCreated objectCreated} * notification has been recieved. * * Because it is unknown if the added record must be visible, or where * in the Store the record must be shown, we simply reload the entire * store to get all updates. * * @param {Zarafa.core.data.Notifications} action The notification action * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification. * @param {Object} data The data which has been recieved from the PHP-side which must be applied * to the given records. * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received * @param {Boolean} success The success status, True if the notification was successfully recieved. * @private */ onNotifyObjectcreated : function(action, records, data, timestamp, success) { // If this notification came at the same time as we reloaded this folder, // we already obtained created item, and we don't need to reload. if (!timestamp || this.lastExecutionTime(Zarafa.core.Actions['list']) < timestamp) { this.reload(); } }, /** * Destroys the store. */ destroy : function() { if (!this.standalone) { Zarafa.core.data.IPMStoreMgr.un('afterrecordwrite', this.onExternalWrite, this); if (!this.serveronly) { Zarafa.core.data.IPMStoreMgr.un('beforerecordsave', this.onExternalSave, this); } Zarafa.core.data.IPMStoreMgr.unregister(this, this.serveronly); } Zarafa.core.data.IPMStore.superclass.destroy.call(this); } }); Ext.reg('zarafa.ipmstore', Zarafa.core.data.IPMStore);