Ext.namespace('Zarafa.core'); /** * @class Zarafa.core.ContextModel * @extends Zarafa.core.data.StatefulObservable * * A context model is the main class containing * the data which is being used within a * {@link Zarafa.core.Context context} */ Zarafa.core.ContextModel = Ext.extend(Zarafa.core.data.StatefulObservable, { /** * The currently active datamode, this is updated through {@link #setDataMode} and when * this field changes, the {@link #datamodechange} event will be fired. * When this context is {@link #stateful stateful}, this option will be * saved in the settings. * @property * @type Mixed */ current_data_mode : undefined, /** * @cfg {Zarafa.core.data.IPMStore} store The store * which contains all {@link Zarafa.core.data.IPMRecord records} * which must be shown within this {@link Zarafa.core.Context context}, */ store: undefined, /** * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders The folders * which have been loaded by this context model. */ folders: undefined, /** * The object containing all entryids to folders which are opened by the * context. This object is used to save to the settings so it can be restored * in a new session. * When this context is {@link #stateful stateful}, this option will be * saved in the settings. * @property * @type Object */ last_used_folders : undefined, /** * @cfg {Zarafa.core.data.IPMRecord[]} selectedRecords The records * which are currently selected within this {@link Zarafa.core.Context context}. */ selectedRecords : undefined, /** * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord[]} defaultFolder default folder for the contextModel. */ defaultFolder: undefined, /** * @cfg {Boolean} statefulRecordSelection True if per-folder the last selected records * must be stored ({@link #lastSelectedRecord} and {@link #lastPreviewedRecord}), which will be * automatically selected when the given folder has been selected again. */ statefulRecordSelection : false, /** * A key-value array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} and {@link Zarafa.core.data.IPMRecord Record} entryids. * In this array we store the last record which is being {@link #setPreviewRecord previewed} in this Context. * @property * @type Object */ lastPreviewedRecord : {}, /** * A key-value array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} and {{@link Zarafa.core.data.IPMRecord Records} entryids. * In this array we store the last record selection which was selected in thie Context. * @property * @type Object */ lastSelectedRecord : {}, /** * True if the model is currently busy in live scrolling. This is updated during * {@link #startLiveScroll} and {@link #stopLiveScroll}. * @property * @type Boolean * @private */ isBusyScrolling : false, /** * True if the Model is currently {@link #enable enabled}. * While true, calls to {@link #load} can operate (unless {@link #suspended suspended}. * @property * @type Boolean */ enabled : false, /** * True if the Model is currently {@link #suspendLoading suspended} from loading. * While true, calls to {@link #load} will not do anything. * @property * @type Boolean */ suspended : false, /** * If {@link #suspendLoading suspended} then this indicates if {@link #load} * has been called. This is used during {@link #resumeLoading resuming} to * determine if a call to {@link #load} should be made. The options for the * {@link #load} call will have been saved in {@link #suspendData}. */ suspendLoad : false, /** * If {@link #suspendLoading suspended} then this object contains the properties * which were last given to {@link #load}. When {@link #resumeLoading resuming} * this object will be used as argument to {@link #load}. * @property * @type Object */ suspendData : undefined, /** * @constructor * @param {Object} config Configuration object */ constructor : function(config) { config = config || {}; Ext.applyIf(config, { stateful : true }); this.addEvents( /** * @event previewrecordchange * Fires when a mail record is selected for preview. * @param {Zarafa.core.ContextModel} model this model. * @param {Zarafa.core.data.IPMRecord} record the record. */ 'previewrecordchange', /** * @event recordselectionchange * Fires when the selected records within the context has been changed. * @param {Zarafa.core.ContextModel} model this context model. * @param {Zarafa.core.data.IPMRecord[]} records The selected records */ 'recordselectionchange', /** * @event folderchange * Fires when the list of selected folders has changed. * @param {Zarafa.core.ContextModel} model this context model. * @param {Array} folders selected folders as an array of {Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects. */ 'folderchange', /** * @event modechange * Fires when the view mode is changed. * @param {Zarafa.core.ContextModel} model this model. * @param {Number} mode new data mode (view modes are set by every context). * @param {Number} oldMode previous data mode (view modes are set by every context). */ 'datamodechange', /** * @event beforelivescrollstart * Fires before the live scroll is being {@link #startLiveScroll started}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @param {Number} cursor the cursor contains the last index of record in grid. * @return {Boolean} false to prevent the live scroll from being started */ 'beforelivescrollstart', /** * @event livescrollstart * Fires when the live scroll is being {@link #startLiveScroll started}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @param {Number} cursor the cursor contains the last index of record in grid. */ 'livescrollstart', /** * @event beforelivescrollstop * Fired before the live scroll is being {@link #stopLiveScroll stopped}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @return {Boolean} False to prevent the live scroll from being stopped */ 'beforelivescrollstop', /** * @event livescrollstop * Fired when the live scroll is being {@link #stopLiveScroll stopped}. * @param {Zarafa.core.ContextModel} model The model which fired the event */ 'livescrollstop', /** * @event beforesearchstart * Fires before the Search is being {@link #startSearch started}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @param {Object} restriction The restriction which should be applied for the search * @param {Boolean} subfolders True if searching should also include the subfolders * @return {Boolean} false to prevent the search from being started */ 'beforesearchstart', /** * @event searchstart * Fires when the Search is being {@link #startSearch started}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @param {Object} restriction The restriction which should be applied for the search * @param {Boolean} subfolders True if searching should also include the subfolders */ 'searchstart', /** * @event searchupdate * Fired when the Search results have been {@link #onSearchUpdate updated}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @param {Ext.data.Store} store the Store in which the search takes place * @param {Object} searchInfo The searchInfo sent by the server */ 'searchupdate', /** * @event beforesearchstop * Fired before the search is being {@link #stopSearch stopped}. * @param {Zarafa.core.ContextModel} model The model which fired the event * @return {Boolean} False to prevent the search from being stopped */ 'beforesearchstop', /** * @event searchstop * Fired when the search is being {@link #stopSearch stopped}. * @param {Zarafa.core.ContextModel} model The model which fired the event */ 'searchstop', /** * @event searchfinish * Fired when the search is finished, this could come shortly after the * {@link #searchupdate} event. * @param {Zarafa.core.ContextModel} model The model which fired the event */ 'searchfinish', /** * @event searchexception * Fired when the server encountered an exception during the search * @param {Zarafa.core.ContextModel} model The model which fired the event * @param {Zarafa.core.data.IPMProxy} proxy object that received the error. * @param {String} type 'request' if an invalid response from server recieved, * 'remote' if valid response received from server but with succuessProperty === false. * @param {String} action Name of the action {@link Ext.data.Api.actions}. * @param {Object} options The options for the action that were specified in the request. * @param {Object} response response received from server depends on type. * @param {Mixed} args */ 'searchexception' ); Zarafa.core.ContextModel.superclass.constructor.call(this, config); if (this.statefulRecordSelection === true) { this.store.on('beforeload', this.onBeforeLoad, this); this.store.on('load', this.onLoad, this); } if (this.stateful) { this.initState(); } // Hook an event handler to the 'load' event of the // hierarchyStore. Call the event handler directly // in case the hierarchy has already been loaded. var hierarchyStore = container.getHierarchyStore(); hierarchyStore.on('load', this.onHierarchyLoad, this); this.onHierarchyLoad(hierarchyStore); Zarafa.core.data.IPFStoreMgr.on('afterrecordupdate', this.afterRecordUpdate, this); }, /** * Called during the {@link Zarafa.core.Context#enable enabling} of the {@link Zarafa.core.Context context}. * Secondly it will {@link #setFolders set the} {@link #folders folder} to this object to {@link #load} the {@link #store}. * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show. * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended} */ enable : function(folder, suspended) { // Enable ContextModel this.enabled = true; // By default suspend loading, both setFolders() and setDataMode() // might trigger a call to load() and we want to defer that to a single // call only. this.suspendLoading(true); // apply the folder, don't reload, as setDataMode will do that for us. if (folder) { this.setFolders(folder); } this.setDataMode(this.getCurrentDataMode(), true); if (suspended !== true) { this.resumeLoading(); } }, /** * Called during the {@link Zarafa.core.Context#disable disabling} of the {@link Zarafa.core.Context context}. * This will {@link #stopSearch stop the search} and clear all data in the {@link #store}. */ disable : function() { // Disable ContextModel this.enabled = false; this.stopLiveScroll(); this.store.cancelLoadRequests(); this.store.removeAll(true); }, /** * When a {@link #store} is about to be {@link Ext.data.Store#load load}, * we remove the currently previewed record. This is required because * during a store load, all records will be purged and thus the reference * from the previewrecord to the store will be lost. * * @param {Zarafa.core.data.IPMStore} store the Store which is going to be loaded * @param {Object} options The options object which is used for loading the store * @private */ onBeforeLoad : function (store, options) { if (options && (options.actionType === Zarafa.core.Actions['updatesearch'] || options.actionType === Zarafa.core.Actions['updatelist'])) { // don't do anything here, as we are just updating the search results or updating list using infinite scrolling. // so selection shouldn't be changed return; } // When reloading, we don't want to reset the previewrecord, // as we already have the desired record loaded in the previewpanel. if (options.reload !== true) { this.setPreviewRecord(undefined, false); } }, /** * When a {@link #store} has completed a {@link Ext.data.Store#load load} action, * we check if a record has been {@link #lastPreviewedRecord previewed} for this * folder before. If so, we automatically select that record. * * @param {Zarafa.core.data.IPMStore} store The store which has loaded * @param {Zarafa.core.data.IPMRecord/Array} records The records which have loaded * @param {Object} options The options object used for loading the store. * @private */ onLoad : function(store, records, options) { if(options && options.actionType === Zarafa.core.Actions['updatesearch']) { // don't do anything here, as we are just updating the search results // so selection shouldn't be changed return; } var previewRecord; var selectedRecords = []; if (!Ext.isEmpty(records)) { if (options && options.params) { var lastPreviewed = this.lastPreviewedRecord[options.params.entryid]; if (lastPreviewed) { previewRecord = store.getById(lastPreviewed); } var lastSelected = this.lastSelectedRecord[options.params.entryid]; if (lastSelected) { for (var i = 0, len = lastSelected.length; i < len; i++) { var record = store.getById(lastSelected[i]); if (record) { selectedRecords.push(record); } } } } /* * We are already selecting record which was selected earlier * so no need to save that in lastSelectedRecord. */ if (!Ext.isEmpty(selectedRecords)) { this.setSelectedRecords(selectedRecords, false); } /* * We are already selecting record which was previewed earlier * so no need to save that in lastPriviewedRecord. */ if (Ext.isDefined(previewRecord)) { this.setPreviewRecord(previewRecord, false); } } }, /** * Sets {@link #defaultFolder default folder} for the particular {@link Zarafa.core.Context context}. * This will help while opening new item dialog from other contexts * e.g. Create new Contact from Inbox, at this moment we need {@link #defaultFolder} to create the item. * * When the {@link Zarafa.core.Context context} was opened without any folders, * this also means we can now {@link #addFolder load} the {@link #defaultFolder}. * * @param {Zarafa.core.hierarchyStore} store that holds hierarchy data. * @private */ onHierarchyLoad : function(hierarchyStore) { // only continue when hierarchyStore has data if (hierarchyStore.getCount() === 0) { return; } // Check whether we have foldertype of the model and defaultFolders data or not. if (Ext.isEmpty(this.store) || Ext.isEmpty(this.store.preferredMessageClass) || !Ext.isEmpty(this.folders)) { return; } // assign default folder of this context model based on preffered message class from store, so getDefaultFolder can use this var folderType = Zarafa.core.MessageClass.getDefaultFolderTypeFromMessageClass(this.store.preferredMessageClass); this.defaultFolder = hierarchyStore.getDefaultFolder(folderType); // If we haven't any folders yet. We should obtain // the previously used folders or the default folder. var openfolders = []; if (!Ext.isEmpty(this.last_used_folders)) { for (var key in this.last_used_folders) { var store = hierarchyStore.getById(key); if (!store) { continue; } var folders = store.getSubStore('folders'); var statefolders = this.last_used_folders[key]; for (var i = 0; i < statefolders.length; i++) { var folder = folders.getById(statefolders[i]); if (!folder) { continue; } openfolders.push(folder); } } } if (Ext.isEmpty(openfolders) && this.defaultFolder) { openfolders.push(this.defaultFolder); } this.setFolders(openfolders); }, /** * Event handler which is triggered when a {@link Zarafa.core.data.IPFRecord record} has been * updated from the server. This function will check whehter action on the * {@link Zarafa.core.data.IPFRecord record} does affect on the current context model i.e. * {@link Zarafa.core.Actions.emptyFolder}, {@link Zarafa.core.Actions.readAllMsgs}, if it * affects then we should reload current context model. * * @param {Zarafa.core.data.IPFStore} IPFStore * @param {Zarafa.core.data.IPFRecord} 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 */ afterRecordUpdate : function(IPFStore, record, operation) { if(this.enabled) { var action = record.getMessageAction('action_type'); if (operation == 'commit' && Ext.isString(action)) { switch (action.toLowerCase()) { case 'emptyfolder': case 'readflags': // Reload store Ext.each(this.getFolders(), function(folder) { // If any folder of the current context model is updated // then reload the context model. if (record.equals(folder)) { this.reload(); return false; } }, this); break; } } } }, /** * Gets the store. * @return {Zarafa.core.data.IPMStore} store object. */ getStore : function() { return this.store; }, /** * Adds a folder to the selected folder list. * This function automatically causes the store to * reload its contents. This method triggers the * {@link #folderchange} event if the folder was not already in the * selected folder list. * * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to add. * @return {Zarafa.hierarchy.data.MAPIFolderRecord} the folder if it was added, * or undefined otherwise (i.e. it was already in the folder list). */ addFolder : function(folder) { var localFolder = this.getFolder(folder.get('entryid')); if (Ext.isDefined(localFolder)) { return undefined; } // Add the folder to the current folder list this.folders.push(folder); this.onFolderChange(this, this.folders); // Fire 'folderchange' event. this.fireEvent('folderchange', this, this.folders); this.load(); return folder; }, /** * Removes a folder from the selected folder list. * This function automatically causes the store to * reload its contents. This method triggers the * {@link #folderchange} event if the folder was previously in the list. * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to remove. * @return {Zarafa.hierarchy.data.MAPIFolderRecord} the folder if it was removed, * or undefined otherwise (i.e. it was not in the folder list). */ removeFolder : function(folder) { var localFolder = this.getFolder(folder.get('entryid')); if (!Ext.isDefined(localFolder)) { return undefined; } // Remove the folder from the list. this.folders.remove(localFolder); // A ContextModel must always have at least // 1 folder loaded. When the last folder is // being unloaded, loa the default one again. if (Ext.isEmpty(this.folders)) { this.folders.push(this.defaultFolder); } this.onFolderChange(this, this.folders); // Fire 'folderchange' event. this.fireEvent('folderchange', this, this.folders); this.load(); return localFolder; }, /** * Sets the selected folder list directly. * Fires the {@link #folderchange} event. * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders selected folders as an array of * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects. */ setFolders : function(folders) { var saveState = true; // Skip saving state when initial folders are set if (this.folders == folders || this.folders === undefined) { saveState = false; } if (Array.isArray(folders)) { this.folders = folders.clone(); } else if (Ext.isDefined(folders)) { this.folders = [ folders ]; } else { this.folders = []; } this.onFolderChange(this, this.folders); // Fire 'folderchange' event. this.fireEvent('folderchange', this, this.folders, saveState); this.load(); }, /** * Event handler which is executed right before the {@link #folderchange} * event is fired. This allows subclasses to update the folders. * * @param {Zarafa.core.ContextModel} model The model which fired the event. * @param {Array} folders selected folders as an array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects. * @private */ onFolderChange : function(model, folders) { // Stop the search // Stop the live scroll. this.stopLiveScroll(); // Load the sorting for the new folders if (this.stateful && !Ext.isEmpty(folders)) { var folder = folders[0]; var state = container.getHierarchyStore().getState(folder, 'list'); var sort = this.store.defaultSortInfo; if (state && state.sort) { sort = state.sort; } if (sort) { this.store.setDefaultSort(sort.field, sort.direction); } } }, /** * Returns a list of currently selected folders. * @return {Zarafa.hierarchy.data.MAPIFolderRecord[]} selected folders as an array of * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects. */ getFolders : function() { return this.folders || []; }, /** * Returns the default {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which is * used within the current selection of folders. If this {@link Zarafa.core.ContextModel ContextModel} is not enabled * then this function will return default folder of this context, and if not enabled then it will return * currently selected folder in the ContextModel. * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default folder */ getDefaultFolder : function() { if(this.enabled) { // ContextModel is enabled return currently selected folder var folders = this.getFolders(); // For ContextModel which doesn't handle multiple folder selection, folders will always be an array with one folder // so get that first folder if(!Ext.isEmpty(folders)) { return folders[0]; } } // ContextModel is not enabled so return default folder return this.defaultFolder; }, /** * Gets a folder from the selected folder list. * @param {String} id folder MAPI Id. * @return {Zarafa.hierarchy.data.MAPIFolderRecord} a folder object if found, undefined otherwise. * @private */ getFolder : function(id) { var ret; Ext.each(this.getFolders(), function(folder) { if (Zarafa.core.EntryId.compareEntryIds(folder.get('entryid'), id)) { ret = folder; return false; } }); return ret; }, /** * Checks if a folder exists in the selected folder list. * @param {Zarafa.hierarchy.data.MAPIFolderRecord/String} folder Either the MAPIFolder, or * the id from the MAPIFolder to be used as id. * @return {Boolean} true iff the given folder is selected. */ hasFolder : function(folder) { if (folder instanceof Zarafa.core.data.MAPIRecord) { folder = folder.get('entryid'); } return Ext.isDefined(this.getFolder(folder)); }, /* * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord} record which is associated to this context. * this is a base function to create record. each context will overwrite this function to * create record of that specific context. * @param {Zarafa.core.IPMFolder} folder (optional) The target folder in which the new record must be * created. If this is not provided the default folder will be used. * @return {Zarafa.core.data.IPMRecord} IPMRecord which is associated to this context. */ createRecord : Ext.emptyFn, /** * Set the {@link Zarafa.core.data.IPMRecord records} which have been * selected within the {@link Zarafa.core.Context context}. Raise the * {@link #recordselectionchange} to inform any listeners about the update. * * @param {Zarafa.core.data.IPMRecord[]} records The selected records * @param {Boolean} stateful (optinal) false to prevent the selected records to be saved * in the {@link #lastSelectedRecords}. */ setSelectedRecords : function(records, stateful) { if (!this.selectedRecords || !records || !this.selectedRecords.equals(records)) { this.selectedRecords = records; if (this.statefulRecordSelection === true && stateful !== false && !Ext.isEmpty(records)) { this.lastSelectedRecord[records[0].get('parent_entryid')] = Ext.pluck(records, 'id'); } this.fireEvent('recordselectionchange', this, records); } }, /** * Get the currently selected {@link Zarafa.core.data.IPMRecord records} * from this {@link Zarafa.core.Context context}. * * @return {Zarafa.core.data.IPMRecord[]} The selected records */ getSelectedRecords : function() { return this.selectedRecords; }, /** * Suspend all calls to {@link #load}. This will prevent the {@link #store} * to send load requests to the server until the model has been * {@link #resumeLoading resumed} again. * * This can be used to configure multiple options in the ContextModel * and defer the loading until all actions have been completed. */ suspendLoading : function() { this.suspended = true; }, /** * Resume the loading actions again if {@link #suspendLoading}. * This will allow the {@link #load} function to use the {@link #store} * again to send requests to the server. This will also call {@link #load} * with the last options that were given to it while being suspended. * * @param {Boolean} discard Discard the calls to load while being suspended. */ resumeLoading : function(discard) { if (this.suspended === true) { this.suspended = false; if (discard !== false && this.suspendLoad === true) { this.load(this.suspendData); } delete this.suspendData; } }, /** * Load the store using the given (optional) restriction. * @param {Object} data The data object to load the store with * @private */ load : function(data) { if (!this.enabled) { return; } if (this.suspended) { this.suspendLoad = true; this.suspendData = data; return; } if (Ext.isEmpty(this.folders)) { // No folders can be loaded, empty the store this.setPreviewRecord(undefined, false); this.store.removeAll(true); } else { // Load a new set of folders from the store. data = Ext.applyIf(data || {}, { folder : this.folders }); this.store.load(data); } }, /** * Reload the store using the previous configured options/folders. * but it check that action type is {@link Zarafa.core.Actions#updatelist 'updatelist'} then * call {@link #stopLiveScroll} */ reload : function() { var lastOptions = this.store.lastOptions; // If live scroll is performed and search is not then stop the live scroll. if(!this.isSearching() && this.isBusyScrolling){ this.stopLiveScroll(); } if (this.suspended) { this.suspendLoad = true; this.suspendData = lastOptions; } else { this.store.reload(); } }, /** * Update the current preview {@link Zarafa.core.data.IPMRecord} * This will fire the event {@link #previewrecordchange}. * * @param {Zarafa.core.data.IPMRecord} record The record which is set as preview * @param {Boolean} stateful (optinal) false to prevent the previewrecord to be saved * in the {@link #lastPreviewedRecord}. */ setPreviewRecord : function(record, stateful) { if (this.previewRecord !== record) { this.previewRecord = record; if (this.statefulRecordSelection === true && stateful !== false && Ext.isDefined(record)) { this.lastPreviewedRecord[record.get('parent_entryid')] = record.get('entryid'); } this.fireEvent('previewrecordchange', this, record); } }, /** * Obtain the currently selected {@link Ext.data.Record} which is * set as the preview record. * * @return {Zarafa.core.data.IPMRecord} */ getPreviewRecord : function() { return this.previewRecord; }, /** * Sets the current mode from the available data modes. * * Fires the {@link #datamodechange} event. * @param {Number} mode view mode (context should define modes and its numeric values). * @param {Boolean} init (optional) True when this function is called during initialization * and it should force the change of the data mode. */ setDataMode : function(mode, init) { if (init === true || this.current_data_mode !== mode) { var oldMode = this.current_data_mode; this.current_data_mode = mode; this.onDataModeChange(this, this.current_data_mode, oldMode); // fire mode change event this.fireEvent('datamodechange', this, this.current_data_mode, oldMode); } }, /** * Event handler which is executed right before the {@link #datamodechange} * event is fired. This allows subclasses to initialize the {@link #store}. * * @param {Zarafa.core.ContextModel} model The model which fired the event. * @param {Mixed} newMode The new selected DataMode. * @param {Mixed} oldMode The previously selected DataMode. * @private */ onDataModeChange : Ext.emptyFn, /** * Returns the currently active {@link #current_data_mode datamode} as configured * through {@link #setDataMode}. * @return {Mixed} The datamode id of the currently active datamode. */ getCurrentDataMode : function() { return this.current_data_mode; }, /** * {@link Ext.data.Store#clearGrouping clear the grouping} on the * {@link #store}. */ clearGrouping : function() { // Only clear grouping, when grouping was applied // in the first place. if (!Ext.isEmpty(this.store.groupField)) { this.store.clearGrouping(); } }, /** * {@link Ext.data.Store#groupBy group the properties} on the * {@link #store}. */ groupBy : function(property) { this.store.groupBy(property); }, /** * Check if the store is currently using searching. * @return {Boolean} True if the store is currently searching */ isSearching : function() { if(Ext.isDefined(this.getStore()) && Ext.isDefined(this.getStore().isBusySearching) && this.getStore().isBusySearching) { return true; } return false; }, /** * Check if the {@link Zarafa.hierarchy.data.MAPIStoreRecord store} * of the {@link #getDefaultFolder folder} in which we intend to search has support for * {@link Zarafa.hierarchy.data.MAPIStoreRecord#hasSearchSupport Search folders}. * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder (optional) If passed, this folder's store * will be checked for search folder support instead of the default folder's * @return {Boolean} True if search folders are supported */ supportsSearchFolder : function(folder) { folder = folder || this.getDefaultFolder(); if (Ext.isEmpty(folder)) { return false; } var mapiStore = folder.getMAPIStore(); if (!mapiStore) { return false; } return mapiStore.hasSearchSupport(); }, /** * Check if the model is currently using live scroll. * @return {Boolean} True if the model is currently searching */ isLiveScrolling : function() { return this.isBusyScrolling; }, /** * Fire the {@link #livescrollstart} event and set {@link #isBusyScrolling} to true. * @param {Number} cursor the cursor contains the last index of record in grid. */ startLiveScroll : function(cursor) { /* * don't do anything if live scroll is not enabled just simply return. */ var isEnableLiveScroll = container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_live_scroll'); if(!isEnableLiveScroll) { return; } if (this.fireEvent('beforelivescrollstart', this, cursor) !== false) { this.isBusyScrolling = true; this.store.on('exception', this.onLiveScrollException, this); if(this.fireEvent('livescrollstart', this, cursor) !== false) { var options = { restriction: {} }; options.restriction['start'] = cursor; options.restriction['limit'] = container.getSettingsModel().get('zarafa/v1/main/page_size'); this.store.liveScroll({ folder : [this.getDefaultFolder()], params : options, add : true }); } } }, /** * Stop {@link Zarafa.core.data.ListModuleStore#stopLiveScroll stopLiveScroll} in the {@link #store} * and fire the {@link #livescrollstop} event. */ stopLiveScroll : function() { if (this.isBusyScrolling && this.fireEvent('beforelivescrollstop', this) !== false) { this.store.stopLiveScroll(); this.store.un('exception', this.onLiveScrollException, this); this.isBusyScrolling = false; this.fireEvent('livescrollstop', this); } }, /** * Event handler for the {@link Zarafa.core.data.ListModuleStore#exception} event * of the {@link #store} which is fired when the server failed to update the live scroll. * * @param {Zarafa.core.data.IPMProxy} proxy object that received the error * and which fired exception event. * @param {String} type 'request' if an invalid response from server recieved, * 'remote' if valid response received from server but with succuessProperty === false. * @param {String} action Name of the action {@link Ext.data.Api.actions}. * @param {Object} options The options for the action that were specified in the request. * @param {Object} response response received from server depends on type. * @param {Mixed} args * @private */ onLiveScrollException : function(proxy, type, action, options, response, args) { this.store.un('exception', this.onLiveScrollException, this); this.stopLiveScroll(); }, /** * Fire the {@link #searchstart} event and start * {@link Zarafa.core.data.ListModuleStore#search searching} in the {@link #store}. * @param {Object} restriction The restriction which should be applied for the search * @param {Boolean} subfolders True if searching should also include the subfolders * this will only be used when searchfolders are supported */ startSearch : function(restriction, subfolders, options) { var useSearchFolder = this.supportsSearchFolder(options.folder); // We don't support subfolders when searchfolders are disabled if (!useSearchFolder) { subfolders = false; } if (this.fireEvent('beforesearchstart', this, restriction, subfolders) !== false) { this.store.isBusySearching = true; this.fireEvent('searchstart', this, restriction, subfolders); this.store.on('exception', this.onSearchException, this); if (useSearchFolder) { // only required when using a search folder this.store.on('beforeupdatesearch', this.onSearchUpdate, this); } else { // if we are not using search folder then also after completion of search we need to fire 'searchfinished' event this.store.on('load', function() { this.fireEvent('searchfinished', this); }, this, {single : true}); } var store = this.getActiveStore(); var forceCreateSearchFolder = false; if (Ext.isDefined(store.searchFolder[store.searchStoreUniqueId])) { var searchFolderEntryid = store.searchFolder[store.searchStoreUniqueId].get('entryid'); forceCreateSearchFolder = Zarafa.core.EntryId.compareEntryIds(searchFolderEntryid, store.searchFolderEntryId); } // send request to store to start search this.store.search({ // search in multiple folders not supported at the moment folder : options.folder, forceCreateSearchFolder : forceCreateSearchFolder, useSearchFolder : useSearchFolder, subfolders : subfolders, searchRestriction : restriction }); } }, /** * Stop {@link Zarafa.core.data.ListModuleStore#stopSearch searching} in the {@link #store} * and fire the {@link #searchstop} event. */ stopSearch : function() { if (this.isSearching() && this.fireEvent('beforesearchstop', this) !== false) { // send request to store to stop search this.store.stopSearch({}); this.store.un('exception', this.onSearchException, this); this.store.un('beforeupdatesearch', this.onSearchUpdate, this); this.store.isBusySearching = false; this.isBusyScrolling = false; this.fireEvent('searchstop', this); } }, /** * Event handler for the {@link Zarafa.core.data.ListModuleStore#beforeupdatesearch} event * of the {@link #store}. This will fire the {@link #searchupdate} event and will * check if the {@link Zarafa.core.mapi.Search#isSearchRunning search is still running}, * otherwise the {@link #searchfinished} event will be fired. * @param {Ext.data.Store} store the Store which fired the event * @param {Object} searchInfo The searchInfo sent by the server * @private */ onSearchUpdate : function(store, searchInfo) { this.fireEvent('searchupdate', this, store, searchInfo); if (!Zarafa.core.mapi.Search.isSearchRunning(searchInfo['searchState'])) { store.un('exception', this.onSearchException, this); store.un('beforeupdatesearch', this.onSearchUpdate, this); this.fireEvent('searchfinished', this); } }, /** * Event handler for the {@link Zarafa.core.data.ListModuleStore#exception} event * of the {@link #store} which is fired when the server failed to update the search. * This will fire the {@link #searchexception} event and stop the search. * * @param {Zarafa.core.data.IPMProxy} proxy object that received the error * and which fired exception event. * @param {String} type 'request' if an invalid response from server recieved, * 'remote' if valid response received from server but with succuessProperty === false. * @param {String} action Name of the action {@link Ext.data.Api.actions}. * @param {Object} options The options for the action that were specified in the request. * @param {Object} response response received from server depends on type. * @param {Mixed} args * @private */ onSearchException : function(proxy, type, action, options, response, args) { this.store.un('exception', this.onSearchException, this); this.store.un('beforeupdatesearch', this.onSearchUpdate, this); this.stopSearch(); this.fireEvent('searchexception', this, proxy, type, action, options, response, args); }, /** * Obtain the path in which the {@link #getState state} must be saved. * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates * a custom name. * @return {String} The unique name for this component by which the {@link #getState state} must be saved. */ getStateName : function() { var name = this.statefulName; if (!name) { if (this.store) { name = this.store.preferredMessageClass.match(/(?:IPM\.)?(.*)/)[1].toLowerCase(); } } return 'models/' + name; }, /** * Register the {@link #stateEvents state events} to the {@link #saveFolderChangeState} and the * {@link #saveDataModeState} callback functions. * @protected */ initStateEvents : function() { Zarafa.core.ContextModel.superclass.initStateEvents.call(this); this.on('datamodechange', this.saveDataModeState, this, { delay : 100 }); this.on('folderchange', this.saveFolderChangeState, this, { delay : 100 }); }, /** * Handler for 'folderchange' event, which calls {@link #saveState} only * when save is not false. * * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved. * @param {Array} folders the folders which are changed. * @param {Boolean} save determines if state should be saved. */ saveFolderChangeState : function(contextModel, folders, save) { if (save !== false) { this.saveState(); } }, /* * Handler for 'datamodechange' event, which calls {@link #saveState} only * when the datamode has been changed. * * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved. * @param {Number} current_data_mode the current data mode. * @param {Number} oldMode the old data mode. */ saveDataModeState : function(context, current_data_mode, oldMode) { if (current_data_mode != oldMode) { this.saveState(); } }, /** * When {@link #stateful} the State object which should be saved into the * {@link Ext.state.Manager}. * @return {Object} The state object * @protected */ getState : function() { var state = Zarafa.core.ContextModel.superclass.getState.call(this) || {}; var searching = this.isSearching(); // If a folders list exists, it should be used to update // the last_used_folders object. Otherwise we keep the value // as we don't want to override it with an empty object. if (this.folders) { this.last_used_folders = {}; for (var i = 0, len = this.folders.length; i < len; i++) { var folder = this.folders[i]; var storeentryid = folder.get('store_entryid'); var entryid = folder.get('entryid'); if (!Ext.isDefined(this.last_used_folders[storeentryid])) { this.last_used_folders[storeentryid] = []; } this.last_used_folders[storeentryid].push(entryid); } } return Ext.apply(state, searching ? {} : { current_data_mode : this.current_data_mode, last_used_folders : this.last_used_folders }); }, /** * Apply the given state to this object activating the properties which were previously saved in * {@link Ext.state.Manager}. Overridden to check if the state is valid. When this ContextModel * is not a {@link Zarafa.core.MultiFolderContextModel MultiFolderContextModel} it can never * have more than 1 folder set as 'last used folder', so when this is the case we will remove * this property from the state. * * @param {Object} state The state object */ applyState : function(state) { if ( !(this instanceof Zarafa.core.MultiFolderContextModel) ){ // Check if the state is valid if ( Ext.isDefined(state.last_used_folders) ){ if ( Object.keys(state.last_used_folders).length > 1 ){ delete state.last_used_folders; } else { Ext.iterate(state.last_used_folders, function(storeEntryid, folders, lastUsedFoldersObj){ if ( Array.isArray(folders) && folders.length>1 ){ delete lastUsedFoldersObj[storeEntryid]; } }, this); } } } Zarafa.core.ContextModel.superclass.applyState.call(this, state); } });