Test Failed
Pull Request — master (#9)
by
unknown
16:01 queued 05:44
created

client/zarafa/common/ui/grid/GridPanel.js   D

Complexity

Total Complexity 59
Complexity/F 2.95

Size

Lines of Code 544
Function Count 20

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 183
c 0
b 0
f 0
dl 0
loc 544
rs 4.08
wmc 59
mnd 39
bc 39
fnc 20
bpm 1.95
cpm 2.95
noi 4

How to fix   Complexity   

Complexity

Complex classes like client/zarafa/common/ui/grid/GridPanel.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
Ext.namespace('Zarafa.common.ui.grid');
2
3
/**
4
 * @class Zarafa.common.ui.grid.GridPanel
5
 * @extends Ext.grid.GridPanel
6
 * @xtype zarafa.gridpanel
7
 *
8
 * grommunio Web specific GridPanel which contain extra features and bugfixes
9
 * which could not be resolved by plugins or directly in extjs.
10
 */
11
Zarafa.common.ui.grid.GridPanel = Ext.extend(Ext.grid.GridPanel, {
12
	/**
13
	 * @cfg {String} ddText
14
	 * @hide
15
	 */
16
17
	/**
18
	 * The entryid of the {@link Zarafa.core.data.MAPIFolder folder} which is currently being displayed
19
	 * inside this panel. This is used by {@link #getStateName} to obtain the correct {@link #getState state}.
20
	 *
21
	 * @property
22
	 * @type String
23
	 */
24
	currentEntryId: undefined,
25
26
	/**
27
	 * The store entryid of the {@link Zarafa.core.data.MAPIFolder folder} which is currently being displayed
28
	 * inside this panel. This is used by {@link #getStateName} to obtain the correct {@link #getState state}.
29
	 *
30
	 * @property
31
	 * @type String
32
	 */
33
	currentStoreEntryId: undefined,
34
35
	/**
36
	 * The tooltip that will be used to show the full date when hovered over 'short' formatted dates
37
	 * @property
38
	 * @type {Zarafa.common.ui.grid.DateTooltip}
39
	 */
40
	dateTooltip: null,
41
42
	/**
43
	 * @constructor
44
	 * @param {Object} config Configuration object
45
	 */
46
	constructor: function(config)
47
	{
48
		config = config || {};
49
50
		Ext.applyIf(config, {
51
			deferRowRender: false
52
		});
53
54
		if(Ext.isEmpty(config.view)) {
55
			config.viewConfig = Ext.applyIf(config.viewConfig || {}, {
56
				autoFill: true,
57
				markDirty: false
58
			});
59
60
			Ext.applyIf(config, {
61
				view: new Zarafa.common.ui.grid.GridView(config.viewConfig)
62
			});
63
		}
64
65
		Zarafa.common.ui.grid.GridPanel.superclass.constructor.call(this, config);
66
	},
67
68
	/**
69
	 * Called when the GridPanel has been rendered.
70
	 * This activate the keymap on the element of this component after the normal operations of
71
	 * afterRender have been completed. It will activate by getting the xtype hierarchy from
72
	 * {@link #getXTypes} and format it into a string usable by the
73
	 * {@link Zarafa.core.KeyMapMgr KeyMapMgr}.
74
	 * @private
75
	 */
76
	afterRender: function()
77
	{
78
		Zarafa.common.ui.grid.GridPanel.superclass.afterRender.apply(this, arguments);
79
		var xtypes = this.getXTypes();
80
81
		// The first part leading up to zarafa.gridpanel will be stripped
82
		xtypes = xtypes.replace('component/box/container/panel/grid/zarafa.gridpanel','');
83
84
		// Then the "zarafa." will be stripped off from all the xtypes like "zarafa.somegrid".
85
		xtypes = xtypes.replace(/\/zarafa\./g,'.');
86
87
		// Finally we strip the string "grid" from all the xtypes. Otherwise each level will have
88
		// that "grid" mentioned in them. Also we add "grid" to the start as that sets
89
		// it apart from other components in the key mapping.
90
		xtypes = 'grid' + xtypes.replace(/grid/g, '');
91
		Zarafa.core.KeyMapMgr.activate(this, xtypes);
92
93
	  this.dateTooltip = new Zarafa.common.ui.grid.DateTooltip({
94
			target: this.getEl().dom
95
		});
96
	},
97
98
	/**
99
	 * Called when the GridPanel has been destroyed. Will destroy the {#dateTooltip}
100
	 * @private
101
	 */
102
	onDestroy: function()
103
	{
104
		Zarafa.common.ui.grid.GridPanel.superclass.onDestroy.apply(this, arguments);
105
106
		if ( this.dateTooltip ){
107
			this.dateTooltip.destroy();
108
		}
109
	},
110
111
	/**
112
	 * Initialize event handlers
113
	 * @private
114
	 */
115
	initEvents: function()
116
	{
117
		// First bind columnModel and then the store. The bindStore() function
118
		// will add some initialization which requires the model to be initialized.
119
		this.bindColumnModel(this.getColumnModel(), true);
120
		this.bindStore(this.getStore(), true);
121
122
		Zarafa.common.ui.grid.GridPanel.superclass.initEvents.call(this);
123
124
		// use our custom load mask
125
		if(this.loadMask) {
126
			// destroy loadmask created by superclass
127
			this.loadMask.destroy();
128
			this.loadMask = new Zarafa.common.ui.LoadMask(this.bwrap,
129
					Ext.apply( { store: this.store }, this.initialConfig.loadMask ) );
130
		}
131
132
		// Fixme: workaround fix for Firefox on Ubuntu and Firefox on MacOS
133
		if (Ext.isGecko && (Ext.isLinux || Ext.isMac)) {
134
			this.on('keydown', function (e) {
135
				var keyCode = e.keyCode;
136
				if (keyCode === Ext.EventObject.DOWN || keyCode === Ext.EventObject.UP) {
137
					this.selModel.onKeyPress(e, keyCode === Ext.EventObject.DOWN ? 'down' : 'up');
138
					e.preventDefault();
139
				}
140
			}, this);
141
		}
142
143
		this.mon(this.store, 'write', this.onWriteRecord, this);
144
145
		this.on('viewready', this.onViewReady, this);
146
147
		if (this.model && this.model.statefulRecordSelection) {
148
			this.mon(this.model, 'recordselectionchange', this.onRecordSelectionChange, this);
149
		}
150
151
		this.mon(this.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
152
	},
153
154
	/**
155
	 * Event handler which is fired when the recordselection in the {@link #model} has been changed.
156
	 * If no selection is currently active, this will automatically select the given records in the grid.
157
	 *
158
	 * @param {Zarafa.core.ContextModel} model this model.
159
	 * @param {Zarafa.core.data.IPMRecord[]} records The selected records
160
	 * @private
161
	 */
162
	onRecordSelectionChange: function(model, records)
163
	{
164
		if (!this.getSelectionModel().hasSelection() && !Ext.isEmpty(records)) {
165
			var index = model.getStore().indexOf(records[0]);
166
			this.getSelectionModel().selectRecords(records);
167
			this.getView().focusRow(index);
168
		}
169
	},
170
171
	/**
172
	 * Handler for 'write' event. If the action carried out with the write call was 'destroy' than we have to check
173
	 * a situation where the total loaded record is less than total page size and grid-scroll is not there.
174
	 * If the above mentioned situation is there than additional records needs to be fetched from server and
175
	 * synchronize {@link Zarafa.core.data.ListModuleStore store} with as many numbers of new {@link Zarafa.core.data.IPMRecords[] records} as deleted
176
	 * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
177
	 * @param {String} action The name if action that was carried out for this write call.
178
	 * @param {Object} Result The data arrives from server side.
179
	 * @param {Object} response as it was received from server.
180
	 * @param {Array} recordSet Records that are deleted.
181
	 */
182
	onWriteRecord: function(store, action, result, response, recordSet)
183
	{
184
		if(action === 'destroy') {
185
			if (!store.syncStore) {
186
				var gridScroller = this.getView().scroller;
187
				if(Ext.isDefined(gridScroller) && !gridScroller.isScrollable()) {
188
					if (store.totalLoadedRecord < store.totalLength) {
189
						var options = {
190
							add: true,
191
							actionType: Zarafa.core.Actions['list']
192
						};
193
194
						// load store with as many new records as deleted before scrollbar has disappeared
195
						// For that sets start and limit base on remaining number of records.
196
						// For example if we have 11 remaining records left then start = 11 and limit = page_size - 11
197
						Ext.applyIf(options, store.lastOptions);
198
						var limit = container.getSettingsModel().get('zarafa/v1/main/page_size');
199
						options.params.restriction.limit = limit - store.getCount();
200
						options.params.restriction.start = store.getCount();
201
						store.syncStore = true;
202
						if (store.loadMask) {
203
							store.loadMask.hide();
204
							store.loadMask = undefined;
205
						}
206
						store.load(options);
207
					}
208
				}
209
			}
210
211
			var deletedRecord = Array.isArray(recordSet) ? recordSet[recordSet.length - 1] : recordSet;
212
			// lastIndex is the index of deleted record from grid/store.
213
			// lastIndex is added by destroyRecord function of Ext.data.Store
214
			this.onRowRemoved(deletedRecord.lastIndex, deletedRecord);
215
		}
216
	},
217
218
	/**
219
	 * Called after a row has been removed for the GridView.
220
	 * This will check if the store has a next/previous row to select in the Grid
221
	 * @param rowIndex {Number} rowIndex the index of row was deleted from grid.
222
	 * @private
223
	 */
224
	onRowRemoved: function(rowIndex)
225
	{
226
		if (container.getShadowStore().isExecuting("open")) {
227
			container.getShadowStore().proxy.cancelRequests('open');
228
			return;
229
		}
230
231
		var itemCount = this.getStore().getCount();
232
		if (itemCount > 0) {
233
			var sm = this.getSelectionModel();
234
			var selections = sm.getSelections();
235
236
			if (!Ext.isEmpty(selections)) {
237
				return;
238
			}
239
240
			// check for the next item in store else select the previous item
241
			if(rowIndex < itemCount) {
242
				sm.selectRow(rowIndex);
243
			} else {
244
				sm.selectRow(rowIndex - 1);
245
			}
246
		}
247
	},
248
249
	/**
250
	 * Reconfigures the grid to use a different Store and Column Model and fires the 'reconfigure' event.
251
	 * The View will be bound to the new objects and refreshed.
252
	 * Be aware that upon reconfiguring a GridPanel, certain existing settings may become invalidated.
253
	 * For example the configured {@link #autoExpandColumn} may no longer exist in the new ColumnModel.
254
	 * Also, an existing {@link Ext.PagingToolbar PagingToolbar} will still be bound to the old Store,
255
	 * and will need rebinding. Any {@link #plugins} might also need reconfiguring with the new data
256
	 *
257
	 * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
258
	 * @param {Ext.grid.ColumnModel} model The new {@link Ext.grid.ColumnModel} object
259
	 */
260
	reconfigure: function(store, colModel)
261
	{
262
		// First bind columnModel and then the store. The bindStore() function
263
		// will add some initialization which requires the model to be initialized.
264
		this.bindColumnModel(colModel);
265
		this.bindStore(store);
266
267
		Zarafa.common.ui.grid.GridPanel.superclass.reconfigure.call(this, store, colModel);
268
	},
269
270
	/**
271
	 * Bind a new column model to this gridpanel. This will hook to all
272
	 * important events with configuration changes which must be stored into the {@link #getState state}.
273
	 *
274
	 * @param {Ext.grid.ColumnModel} model The model which must be bound to the panel
275
	 * @param {Boolean} initialize True if this is called from the constructor to initialize
276
	 * the panel for the first time.
277
	 * @private
278
	 */
279
	bindColumnModel: function(model, initialize)
280
	{
281
		var oldModel = this.getColumnModel();
282
283
		if (initialize === true || oldModel !== model) {
284
			if (oldModel) {
285
				this.mun(oldModel, 'beforeconfigchange', this.onBeforeConfigChange, this);
286
				this.mun(oldModel, 'configchange', this.onConfigChange, this);
287
			}
288
289
			if (model) {
290
				this.mon(model, 'beforeconfigchange', this.onBeforeConfigChange, this);
291
				this.mon(model, 'configchange', this.onConfigChange, this);
292
			}
293
		}
294
	},
295
296
	/**
297
	 * Bind a new store to this gridpanel. This will hook to all
298
	 * important events with configuration changes which must be stored into the {@link #getState state}..
299
	 *
300
	 * @param {Ext.data.Store} store The store which must be bound to the panel
301
	 * @param {Boolean} initialize True if this is called from the constructor to initialize
302
	 * the panel for the first time.
303
	 * @private
304
	 */
305
	bindStore: function(store, initialize)
306
	{
307
		var oldStore = this.getStore();
308
309
		if (initialize === true || oldStore !== store) {
310
			if (oldStore) {
311
				this.mun(oldStore, 'beforeload', this.onStoreBeforeLoad, this);
312
				this.mun(oldStore, 'load', this.onStoreLoad, this);
313
			}
314
315
			if (store) {
316
				this.mon(store, 'beforeload', this.onStoreBeforeLoad, this);
317
				this.mon(store, 'load', this.onStoreLoad, this);
318
				// In case the store was already loaded, just call the
319
				// event handler directly.
320
				if (store.lastOptions) {
321
					this.onStoreBeforeLoad(store, store.lastOptions);
322
				}
323
			}
324
		}
325
	},
326
327
	/**
328
	 * Event handler for the {@link #viewready} event. The {@link Ext.grid.GridPanel Extjs GridPanel},
329
	 * or more accurately the {@link Zarafa.common.ui.LoadMask} do not handle the case nicely when the store
330
	 * is already busy loading when the grid is being rendered. Because we do like such an optimization,
331
	 * we have to check at this time if the store is loading, and display the loadmask.
332
	 * @private
333
	 */
334
	onViewReady: function()
335
	{
336
		var show = this.store && (this.store.isExecuting(Zarafa.core.Actions['list']) === true
337
			|| this.store.isExecuting(Zarafa.core.Actions['search']) === true);
338
339
		if (this.loadMask && show) {
340
			this.loadMask.show();
341
		}
342
	},
343
344
	/**
345
	 * Called right before the {@link #store} sends a {@link Ext.data.Store#load} request to the server,
346
	 * this will check which entryId is loaded from the server and will {@link #applyState apply the state}
347
	 * for the new folder onto the panel. The 'options' argument will be updated according to the sorting
348
	 * requirements from the {@link Ext.state.Manager#get state}.
349
	 * @param {Ext.data.Store} store The store which fired the event
350
	 * @param {Object} options The options used for loading the new data from the store
351
	 * @private
352
	 */
353
	onStoreBeforeLoad: function(store, options)
354
	{
355
		/*
356
		 * if action type is updatelist and grid has no dummy row with warped
357
		 * loading mask, then show the loading mask on the grid row rather to
358
		 * show load mask on whole grid.
359
		 */
360
		if(options && (options.actionType === Zarafa.core.Actions['updatelist'])) {
361
			if(!this.isLoading) {
362
				this.getView().showGridRowLoadMask(this.loadMask.msg);
363
				this.isLoading = true;
364
			}
365
			return;
366
		}
367
		// No need to reset the column model when we are searching
368
		// We must use the column model the user set for the folder
369
		// in which he is searching
370
		if ( !Ext.isEmpty(store) && store.hasSearchResults ===true ){
371
			return;
372
		}
373
374
		var model = this.getColumnModel();
375
		var folder = options.folder;
376
		var entryId;
377
		var storeEntryId;
378
379
		if (!Ext.isEmpty(folder)) {
380
			if (Array.isArray(folder)) {
381
				entryId = folder[0].get('entryid');
382
				storeEntryId = folder[0].get('store_entryid');
383
			} else {
384
				entryId = folder.get('entryid');
385
				storeEntryId = folder.get('store_entryid');
386
			}
387
		}
388
389
		if (this.currentEntryId === entryId) {
390
			return;
391
		}
392
393
		this.currentEntryId = entryId;
394
		this.currentStoreEntryId = storeEntryId;
395
396
		model.setConfig(model.columns, false);
397
	},
398
399
	/**
400
	 * Called when {@link Ext.grid.GridPanel#store store} on the {@link #field} is
401
	 * loading new data. At this point we must check which folder entryid is being loaded.
402
	 *
403
	 * @param {Zarafa.core.data.MAPIStore} store The store which being loaded.
404
	 * @param {Ext.data.Record[]} records The records which have been loaded from the store
405
	 * @param {Object} options The loading options that were specified (see {@link Ext.data.Store#load load} for details)
406
	 * @private
407
	 */
408
	onStoreLoad: function(store, records, options)
409
	{
410
		/*
411
		 * if action type is updatelist and grid has dummy row which was warped
412
		 * with loading mask, then remove that dummy row from grid. also don't
413
		 * show the loading mask on whole grid.
414
		 */
415
		if(options && (options.actionType === Zarafa.core.Actions['updatelist'])) {
416
			if(this.isLoading){
417
				this.getView().removeGridRowLoadMask();
418
				this.isLoading = false;
419
			}
420
			return;
421
		}
422
	},
423
424
	/**
425
	 * Event handler for the {@link #beforeconfigchange} event which is fired at the start of
426
	 * {@link Zarafa.common.ui.grid.ColumnModel#setConfig}.
427
	 *
428
	 * @param {Ext.gridColumnModel} columnModel The model which is being configured
429
	 * @param {Object} config The configuration object
430
	 * @private
431
	 */
432
	onBeforeConfigChange: Ext.emptyFn,
433
434
	/**
435
	 * Called when {@link Ext.grid.ColumnModel#setConfig} has completed the configuration changes.
436
	 *
437
	 * @param {Ext.gridColumnModel} columnModel The model which is being configured
438
	 * @private
439
	 */
440
	onConfigChange: Ext.emptyFn,
441
442
	/**
443
	 * Event handler for the selectionchange event of the grid's selectionmodel.
444
	 * Will set the class 'zarafa-multiselection' on selected rows when more than
445
	 * one row is selected. This class can then be used to not show the
446
	 * 'add categories' button when hovering.
447
	 *
448
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model of this grid
449
	 */
450
	onGridSelectionChange: function(selectionModel)
451
	{
452
		var view = this.getView();
453
		var store = this.getStore();
454
455
		// First remove the multiple selection class from all rows
456
		for ( var i=0; i<store.getCount(); i++ ){
457
			view.removeRowClass(i, 'zarafa-multiselection');
458
		}
459
460
		var selection = selectionModel.getSelections();
461
		if ( selection.length > 1 ){
462
			// Multiple rows are selected. Make sure they all have the multiple selection class.
463
			Ext.each(selection, function(record){
464
				view.addRowClass(store.indexOf(record), 'zarafa-multiselection');
465
			}, this);
466
		}
467
	},
468
469
	/**
470
	 * Called to get grid's drag proxy text. Opposite to the superclass,
471
	 * this will not return {@link Ext.grid.GridPanel#ddText},
472
	 * but will use 'ngettext' to return a properly formatted plural sentence.
473
	 * @return {String} The text
474
	 */
475
	getDragDropText: function()
476
	{
477
		var count = this.selModel.getCount();
478
		return String.format(ngettext('{0} selected row', '{0} selected rows', count), count);
479
	},
480
481
	/**
482
	 * Obtain the path in which the {@link #getState state} must be saved.
483
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
484
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
485
	 * a custom name.
486
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
487
	 */
488
	getStateName: function()
489
	{
490
		var options = this.store?.lastOptions;
491
		var folder;
492
493
		if (options && options.folder) {
494
			folder = options.folder;
495
496
			if (Array.isArray(folder)) {
497
				folder = folder[0];
498
			}
499
		}
500
501
		return container.getHierarchyStore().getStateName(folder, 'list');
502
	},
503
504
	/**
505
	 * When {@link #stateful} the State object which should be saved into the
506
	 * {@link Ext.state.Manager}.
507
	 * @return {Object} The state object
508
	 * @protected
509
	 */
510
	getState: function()
511
	{
512
		var state = Zarafa.common.ui.grid.GridPanel.superclass.getState.call(this);
513
514
		// Sorting is handled by the ContextModel rather the by the grid,
515
		// hence we remove the sorting information from this location.
516
		var wrap = { sort: state.sort };
517
		delete state.sort;
518
		wrap[this.getColumnModel().name] = state;
519
520
		return wrap;
521
	},
522
523
	/**
524
	 * Apply the given state to this object activating the properties which were previously
525
	 * saved in {@link Ext.state.Manager}.
526
	 * @param {Object} state The state object
527
	 * @protected
528
	 */
529
	applyState: function(state)
530
	{
531
		if (state) {
532
			// Sorting is handled by the ContextModel rather then by the grid,
533
			// if the unwrap object contains 'sort' then this function would trigger
534
			// a reload of the store which is not what we need.
535
			var unwrap = { sort: state.sort };
536
			delete state.sort;
537
			Ext.apply(unwrap, state[this.getColumnModel().name]);
538
539
			Zarafa.common.ui.grid.GridPanel.superclass.applyState.call(this, unwrap);
540
		}
541
	}
542
});
543
544
Ext.reg('zarafa.gridpanel', Zarafa.common.ui.grid.GridPanel);
545