api/js/framework/fw_desktop.js   F
last analyzed

Complexity

Total Complexity 62
Complexity/F 2

Size

Lines of Code 489
Function Count 31

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 207
dl 0
loc 489
rs 3.44
c 0
b 0
f 0
wmc 62
mnd 31
bc 31
fnc 31
bpm 1
cpm 2
noi 0

How to fix   Complexity   

Complexity

Complex classes like api/js/framework/fw_desktop.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
/**
2
 * EGroupware desktop framework
3
 *
4
 * @package framework
5
 * @author Hadi Nategh <[email protected]>
6
 * @author Andreas Stoeckel <[email protected]>
7
 * @copyright Stylite AG 2014
8
 * @description Create jdots framework
9
 */
10
11
/*egw:uses
12
	vendor.bower-asset.jquery.dist.jquery;
13
	framework.fw_base;
14
	framework.fw_browser;
15
	framework.fw_ui;
16
	framework.fw_classes;
17
	egw_inheritance.js;
18
*/
19
20
/**
21
 *
22
 * @param {DOMWindow} window
23
 */
24
(function(window)
25
{
26
	"use strict";
27
28
	/**
29
	 *
30
	 * @type @exp;fw_ui_sidemenu_entry@call;extend
31
	 */
32
	var desktop_ui_sidemenu_entry = fw_ui_sidemenu_entry.extend({
33
34
		/**
35
		 * Override fw_ui_sidemenu_entry class constructor
36
		 *
37
		 * @returns {undefined}
38
		 */
39
		init: function()
40
		{
41
			this._super.apply(this,arguments);
42
43
			this.setBottomLine(this.parent.entries);
44
			//Make the base Div sortable. Set all elements with the style "egw_fw_ui_sidemenu_entry_header"
45
			//as handle
46
			if(jQuery(this.elemDiv).data('uiSortable'))
47
			{
48
				jQuery(this.elemDiv).sortable("destroy");
49
			}
50
			jQuery(this.elemDiv).sortable({
51
				handle: ".egw_fw_ui_sidemenu_entry_header",
52
				distance: 15,
53
				start: function(event, ui)
54
				{
55
					var parent = ui.item.context._parent;
56
					parent.isDraged = true;
57
					parent.parent.startDrag.call(parent.parent);
58
				},
59
				stop: function(event, ui)
60
				{
61
					var parent = ui.item.context._parent;
62
					parent.parent.stopDrag.call(parent.parent);
63
					parent.parent.refreshSort.call(parent.parent);
64
				},
65
				opacity: 0.7,
66
				axis: 'y'
67
			});
68
		},
69
70
		/**
71
		 * setBottomLine marks this element as the bottom element in the application list.
72
		 * This adds the egw_fw_ui_sidemenu_entry_content_bottom/egw_fw_ui_sidemenu_entry_header_bottom CSS classes
73
		 * which should care about adding an closing bottom line to the sidemenu. These classes are removed from
74
		 * all other entries in the side menu.
75
		 * @param {type} _entryList is a reference to the list which contains the sidemenu_entry entries.
76
		 */
77
	   setBottomLine: function(_entryList)
78
	   {
79
		   //If this is the last tab in the tab list, the bottom line must be closed
80
		   for (var i = 0; i < _entryList.length; i++)
81
		   {
82
			   jQuery(_entryList[i].contentDiv).removeClass("egw_fw_ui_sidemenu_entry_content_bottom");
83
			   jQuery(_entryList[i].headerDiv).removeClass("egw_fw_ui_sidemenu_entry_header_bottom");
84
		   }
85
		   jQuery(this.contentDiv).addClass("egw_fw_ui_sidemenu_entry_content_bottom");
86
		   jQuery(this.headerDiv).addClass("egw_fw_ui_sidemenu_entry_header_bottom");
87
	   }
88
	});
89
90
	/**
91
	 *
92
	 * @type @exp;fw_ui_sidemenu@call;extend
93
	 */
94
	var desktop_ui_sidemenu = fw_ui_sidemenu.extend(
95
	{
96
		init: function(_baseDiv, _sortCallback)
97
		{
98
			this._super.apply(this,arguments);
99
			this.sortCallback = _sortCallback;
100
		},
101
		/**
102
		 *
103
		 * @returns {undefined}
104
		 */
105
		startDrag: function()
106
		{
107
			if (this.activeEntry)
108
			{
109
				jQuery(this.activeEntry.marker).show();
110
				jQuery(this.elemDiv).sortable("refresh");
111
			}
112
		},
113
114
		/**
115
		 *
116
		 * @returns {undefined}
117
		 */
118
		stopDrag: function()
119
		{
120
			if (this.activeEntry)
121
			{
122
				jQuery(this.activeEntry.marker).hide();
123
				jQuery(this.elemDiv).sortable("refresh");
124
			}
125
		},
126
127
		/**
128
		 * Called by the sidemenu elements whenever they were sorted. An array containing
129
		 * the sidemenu_entries ui-objects is generated and passed to the sort callback
130
		 */
131
		refreshSort: function()
132
		{
133
			//Step through all children of elemDiv and add all markers to the result array
134
			var resultArray = new Array();
135
			this._searchMarkers(resultArray, this.elemDiv.childNodes);
136
137
			//Call the sort callback with the array containing the sidemenu_entries
138
			this.sortCallback(resultArray);
139
		},
140
141
		/**
142
		 * Adds an entry to the sidemenu.
143
		 * @param {type} _name specifies the title of the new sidemenu entry
144
		 * @param {type} _icon specifies the icon displayed aside the title
145
		 * @param {type} _callback specifies the function which should be called when a callback is clicked
146
		 * @param {type} _tag extra data
147
		 * @param {type} _app application name
148
		 *
149
		 * @returns {desktop_ui_sidemenu_entry}
150
		 */
151
		addEntry: function(_name, _icon, _callback, _tag, _app)
152
		{
153
		   //Create a new sidemenu entry and add it to the list
154
		   var entry = new desktop_ui_sidemenu_entry(this, this.baseDiv, this.elemDiv, _name, _icon,
155
			   _callback, _tag, _app);
156
		   this.entries[this.entries.length] = entry;
157
158
		   return entry;
159
		}
160
	});
161
162
	/**
163
	 * jdots framework object defenition
164
	 * here we can add framework methods and also override fw_base methods if it is neccessary
165
	 * @type @exp;fw_base@call;extend
166
	 */
167
	window.fw_desktop = fw_base.extend({
168
		/**
169
		 * jdots framework constructor
170
		 *
171
		 * @param {string} _sidemenuId sidebar menu div id
172
		 * @param {string} _tabsId tab area div id
173
		 * @param {string} _splitterId splitter div id
174
		 * @param {string} _webserverUrl specifies the egroupware root url
175
		 * @param {function} _sideboxSizeCallback
176
		 * @param {int} _sideboxStartSize sidebox start size
177
		 * @param {int} _sideboxMinSize sidebox minimum size
178
		 */
179
		init:function (_sidemenuId, _tabsId, _webserverUrl, _sideboxSizeCallback, _splitterId, _sideboxStartSize, _sideboxMinSize)
180
		{
181
			// call fw_base constructor, in order to build basic DOM elements
182
			this._super.apply(this,arguments);
183
184
			this.splitterDiv = document.getElementById(_splitterId);
185
			if (this.sidemenuDiv && this.tabsDiv && this.splitterDiv)
186
			{
187
				//Wrap a scroll area handler around the applications
188
				this.scrollAreaUi = new egw_fw_ui_scrollarea(this.sidemenuDiv);
189
190
				// Create toggleSidebar menu
191
				this.toggleSidebarUi = new egw_fw_ui_toggleSidebar('#egw_fw_basecontainer', this._toggleSidebarCallback,this);
192
193
				//Create the sidemenu, the tabs area and the splitter
194
				this.sidemenuUi = new desktop_ui_sidemenu(this.scrollAreaUi.contentDiv,
195
					this.sortCallback);
196
				this.tabsUi = new egw_fw_ui_tabs(this.tabsDiv);
197
				this.splitterUi = new egw_fw_ui_splitter(this.splitterDiv,
198
					EGW_SPLITTER_VERTICAL, this.splitterResize,
199
					[
200
						{
201
							"size": _sideboxStartSize,
202
							"minsize": _sideboxMinSize,
203
							"maxsize": screen.availWidth - 50
204
						}
205
					], this);
206
207
				var egw_script = document.getElementById('egw_script_id');
208
				var apps = egw_script ? egw_script.getAttribute('data-navbar-apps') : null;
209
				this.loadApplications(JSON.parse(apps));
210
			}
211
212
			_sideboxSizeCallback(_sideboxStartSize);
213
214
			// warn user about using IE not compatibilities
215
			// we need to wait until common translations are loaded
216
			egw.langRequireApp(window, 'common', function()
217
			{
218
				if (navigator && navigator.userAgent.match(/Trident|msie/ig))
219
				{
220
					egw.message(egw.lang('Browser %1 %2 is not recommended. You may experience issues and not working features. Please use the latest version of Chrome, Firefox or Edge. Thank You!', 'IE',''), 'info', 'browser:ie:warning');
221
				}
222
			});
223
		},
224
225
		/**
226
		 *
227
		 * @param {array} apps
228
		 */
229
		loadApplications: function (apps)
230
		{
231
			var restore = this._super.apply(this, arguments);
232
233
			//Generate an array with all tabs which shall be restored sorted in by
234
			//their active state
235
236
			//Fill in the sorted_restore array...
237
			var sorted_restore = [];
238
			for (this.appName in restore)
239
			sorted_restore[sorted_restore.length] = restore[this.appName];
240
241
			//...and sort it
242
			sorted_restore.sort(function (a, b) {
243
				return ((a.active < b.active) ? 1 : ((a.active == b.active) ? 0 : -1));
244
			});
245
246
			//Now actually restore the tabs by passing the application, the url, whether
247
			//this is an legacyApp (null triggers the application default), whether the
248
			//application is hidden (only the active tab is shown) and its position
249
			//in the tab list.
250
			for (var i = 0; i < sorted_restore.length; i++)
251
				this.applicationTabNavigate(
252
					sorted_restore[i].app, sorted_restore[i].url, i != 0,
253
					sorted_restore[i].position, sorted_restore[i]['status']);
254
255
			//Set the current state of the tabs and activate TabChangeNotification.
256
			this.serializedTabState = egw.jsonEncode(this.assembleTabList());
257
			this.notifyTabChangeEnabled = true;
258
259
			this.scrollAreaUi.update();
260
			// Disable loader, if present
261
			jQuery('#egw_fw_loading').hide();
262
263
		},
264
265
		/**
266
		 *
267
		 * @param {type} _app
268
		 * @returns {undefined}
269
		 */
270
		setActiveApp: function(_app)
271
		{
272
			var result = this._super.apply(this, arguments);
273
274
			if (_app == _app.parentFw.activeApp)
275
			{
276
				//Set the sidebox width if a application specific sidebox width is set
277
				// do not trigger resize if the sidebar is already in toggle on mode and
278
				// the next set state is the same
279
				if (_app.sideboxWidth !== false &&  egw.preference('toggleSidebar',_app.appName) == 'off')
280
				{
281
					this.sideboxSizeCallback(_app.sideboxWidth);
282
					this.splitterUi.constraints[0].size = _app.sideboxWidth;
283
				}
284
				_app.parentFw.scrollAreaUi.update();
285
				_app.parentFw.scrollAreaUi.setScrollPos(0);
286
			}
287
			//Resize the scroll area...
288
			this.scrollAreaUi.update();
289
290
			//...and scroll to the top
291
			this.scrollAreaUi.setScrollPos(0);
292
293
			// Handles toggleSidebar initialization
294
			if (typeof framework != 'undefined')
295
			{
296
				framework.getToggleSidebarState();
297
				framework.activeApp.browser.callResizeHandler();
298
			}
299
300
			return result;
301
		},
302
303
		/**
304
		 * Function called whenever the sidemenu entries are sorted
305
		 * @param {type} _entriesArray
306
		 */
307
		sortCallback: function(_entriesArray)
308
		{
309
			//Create an array with the names of the applications in their sort order
310
			var name_array = [];
311
			for (var i = 0; i < _entriesArray.length; i++)
312
			{
313
				name_array.push(_entriesArray[i].tag.appName);
314
			}
315
316
			//Send the sort order to the server via ajax
317
			var req = egw.jsonq('EGroupware\\Api\\Framework\\Ajax::ajax_appsort', [name_array]);
318
		},
319
320
		/**
321
		 * Splitter resize callback
322
		 * @param {type} _width
323
		 * @param {string} _toggleMode if mode is "toggle" then resize happens without changing splitter preference
324
		 * @returns {undefined}
325
		 */
326
		splitterResize: function(_width, _toggleMode)
327
		{
328
			if (this.tag.activeApp)
329
			{
330
331
				if (_toggleMode !== "toggle")
332
				{
333
					egw.set_preference(this.tag.activeApp.internalName, 'jdotssideboxwidth', _width);
334
335
					//If there are no global application width values, set the sidebox width of
336
					//the application every time the splitter is resized
337
					if (this.tag.activeApp.sideboxWidth !== false)
338
					{
339
						this.tag.activeApp.sideboxWidth = _width;
340
					}
341
				}
342
			}
343
			this.tag.sideboxSizeCallback(_width);
344
345
			// Notify app about change
346
			if(this.tag.activeApp && this.tag.activeApp.browser != null)
347
			{
348
				this.tag.activeApp.browser.callResizeHandler();
349
			}
350
		},
351
352
		/**
353
		 *
354
		 */
355
		resizeHandler: function()
356
		{
357
			// Tabs overflow needs to be checked again
358
			this.checkTabOverflow();
359
			//Resize the browser area of the applications
360
			for (var app in this.applications)
361
			{
362
				if (this.applications[app].browser != null)
363
				{
364
					this.applications[app].browser.resize();
365
				}
366
			}
367
			//Update the scroll area
368
			this.scrollAreaUi.update();
369
		},
370
		/**
371
		 * Sets the sidebox data of an application
372
		 * @param {object} _app the application whose sidebox content should be set.
373
		 * @param {object} _data an array/object containing the data of the sidebox content
374
		 * @param {string} _md5 an md5 hash of the sidebox menu content: Only if this hash differs between two setSidebox calles, the sidebox menu will be updated.
375
		 */
376
		setSidebox: function(_app, _data, _md5)
377
		{
378
			this._super.apply(this,arguments);
379
380
			if (typeof _app == 'string') _app = this.getApplicationByName(_app);
381
			//Set the sidebox width if a application specific sidebox width is set
382
			if (_app && _app == _app.parentFw.activeApp && _app.sideboxWidth !== false )
383
			{
384
				this.splitterUi.constraints[0].size = _app.sideboxWidth;
385
			}
386
			this.getToggleSidebarState();
387
		},
388
389
		/**
390
		 *
391
		 * @param {app object} _app
392
		 * @param {int} _pos
393
		 * Checks whether the application already owns a tab and creates one if it doesn't exist
394
		 */
395
		createApplicationTab: function(_app, _pos)
396
		{
397
			this._super.apply(this, arguments);
398
		},
399
400
		/**
401
		 * Runs after et2 is loaded
402
		 *
403
		 */
404
		et2_loadingFinished: function() {
405
			this.checkTabOverflow();
406
			var $logout = jQuery('#topmenu_logout');
407
			var self = this;
408
			if (!$logout.hasClass('onLogout'))
409
			{
410
				$logout.on('click', function(e){
411
					e.preventDefault();
412
					self.callOnLogout(e);
413
					window.framework.redirect(this.href);
414
				});
415
				$logout.addClass('onLogout');
416
			}
417
		},
418
419
		/**
420
		 * Check to see if the tab header will overflow and want to wrap.
421
		 * Deal with it by setting some smaller widths on the tabs.
422
		 */
423
		checkTabOverflow: function()
424
		{
425
			var width = 0;
426
			var outer_width = jQuery(this.tabsUi.contHeaderDiv).width();
427
			var spans = jQuery(this.tabsUi.contHeaderDiv).children('span');
428
			spans.css('max-width','');
429
			spans.each(function() { width += jQuery(this).outerWidth(true);});
430
			if(width > outer_width)
431
			{
432
				var max_width = Math.floor(outer_width / this.tabsUi.contHeaderDiv.childElementCount) -
433
					(spans.outerWidth(true) - spans.width());
434
				spans.css('max-width',max_width + 'px');
435
			}
436
		},
437
438
		/**
439
		 * @param {function} _opened
440
		 * Sends sidemenu entry category open/close information to the server using an AJAX request
441
		 */
442
		categoryOpenCloseCallback: function(_opened)
443
		{
444
			egw.set_preference(this.tag.appName, 'jdots_sidebox_'+this.catName, _opened);
445
		},
446
447
		categoryAnimationCallback: function()
448
		{
449
			this.tag.parentFw.scrollAreaUi.update();
450
		},
451
452
		/**
453
		 * toggleSidebar callback function, handles preference and resize
454
		 * @param {string} _state state can be on/off
455
		 */
456
		_toggleSidebarCallback: function (_state)
457
		{
458
			var splitterWidth = egw.preference('jdotssideboxwidth',this.activeApp.appName) || this.activeApp.sideboxWidth;
459
			if (_state === "on")
460
			{
461
				this.splitterUi.resizeCallback(70,'toggle');
462
				egw.set_preference(this.activeApp.appName, 'toggleSidebar', 'on');
463
			}
464
			else
465
			{
466
				this.splitterUi.resizeCallback(splitterWidth);
467
				egw.set_preference(this.activeApp.appName, 'toggleSidebar', 'off');
468
			}
469
		},
470
471
		/**
472
		 * function to get the stored toggleSidebar state and set the sidebar accordingly
473
		 */
474
		getToggleSidebarState: function()
475
		{
476
			var toggleSidebar = egw.preference('toggleSidebar',this.activeApp.appName);
477
			this.toggleSidebarUi.set_toggle(toggleSidebar?toggleSidebar:"off", this._toggleSidebarCallback, this);
478
		},
479
480
		toggle_avatar_menu: function ()
481
		{
482
			var $menu = jQuery('#egw_fw_topmenu');
483
			var $body = jQuery('body');
484
			if (!$menu.is(":visible"))
485
			{
486
				$body.on('click', function(e){
487
					if (e.target.id != 'topmenu_info_user_avatar' && jQuery(e.target).parents('#topmenu_info_user_avatar').length < 1)
488
					{
489
						jQuery(this).off(e);
490
						$menu.toggle();
491
					}
492
				});
493
			}
494
			else
495
			{
496
				$body.off('click');
497
			}
498
			$menu.toggle();
499
		},
500
501
		callOnLogout: function(e) {
502
			var apps = Object.keys(framework.applications);
503
			for(var i in apps)
504
			{
505
				if (app[apps[i]] && typeof app[apps[i]].onLogout === "function")
506
				{
507
					app[apps[i]].onLogout.call(e);
508
				}
509
			}
510
		}
511
	});
512
})(window);
513