Completed
Push — master ( 8967bc...e88c19 )
by Rain
02:47
created

dev/Knoin/Knoin.js   F

Complexity

Total Complexity 92
Complexity/F 3.07

Size

Lines of Code 487
Function Count 30

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 0
c 3
b 0
f 0
nc 1
dl 0
loc 487
rs 3.12
wmc 92
mnd 6
bc 66
fnc 30
bpm 2.2
cpm 3.0666
noi 2

17 Functions

Rating   Name   Duplication   Size   Complexity  
A Knoin.js ➔ extendAsViewModel 0 16 3
A Knoin.js ➔ vmRunHook 0 6 1
A Knoin.js ➔ removeSettingsViewModel 0 4 1
A Knoin.js ➔ routeOn 0 4 1
B Knoin.js ➔ startScreens 0 43 1
F Knoin.js ➔ screenOnRoute 0 124 9
B Knoin.js ➔ setHash 0 20 5
A Knoin.js ➔ disableSettingsViewModel 0 4 1
A Knoin.js ➔ screen 0 4 3
A Knoin.js ➔ hideLoading 0 5 1
A Knoin.js ➔ showScreenPopup 0 18 4
A Knoin.js ➔ routeOff 0 4 1
A Knoin.js ➔ isPopupVisible 0 4 3
A Knoin.js ➔ constructorEnd 0 7 2
A Knoin.js ➔ addSettingsViewModel 0 11 1
C Knoin.js ➔ buildViewModel 0 94 9
A Knoin.js ➔ hideScreenPopup 0 7 4

How to fix   Complexity   

Complexity

Complex classes like dev/Knoin/Knoin.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
import _ from '_';
3
import $ from '$';
4
import ko from 'ko';
5
import hasher from 'hasher';
6
import crossroads from 'crossroads';
7
8
import {runHook} from 'Common/Plugins';
9
import {$html, aViewModels as VIEW_MODELS, popupVisibilityNames} from 'Common/Globals';
10
11
import {
12
	isFunc, isArray, isUnd, pString, log,
13
	createCommand, delegateRun, isNonEmptyArray
14
} from 'Common/Utils';
15
16
let
17
	currentScreen = null,
18
	defaultScreenName = '';
19
20
const SCREENS = {};
21
22
/**
23
 * @returns {void}
24
 */
25
export function hideLoading()
26
{
27
	$('#rl-content').addClass('rl-content-show');
28
	$('#rl-loading').hide().remove();
29
}
30
31
/**
32
 * @param {Object} context
33
 * @returns {void}
34
 */
35
export function constructorEnd(context)
36
{
37
	if (isFunc(context.__constructor_end))
38
	{
39
		context.__constructor_end();
40
	}
41
}
42
43
/**
44
 * @param {string|Array} name
45
 * @param {Function} ViewModelClass
46
 * @returns {void}
47
 */
48
export function extendAsViewModel(name, ViewModelClass)
49
{
50
	if (ViewModelClass)
51
	{
52
		if (isArray(name))
53
		{
54
			ViewModelClass.__names = name;
55
		}
56
		else
57
		{
58
			ViewModelClass.__names = [name];
59
		}
60
61
		ViewModelClass.__name = ViewModelClass.__names[0];
62
	}
63
}
64
65
/**
66
 * @param {Function} SettingsViewModelClass
67
 * @param {string} template
68
 * @param {string} labelName
69
 * @param {string} route
70
 * @param {boolean=} isDefault = false
71
 * @returns {void}
72
 */
73
export function addSettingsViewModel(SettingsViewModelClass, template, labelName, route, isDefault = false)
74
{
75
	SettingsViewModelClass.__rlSettingsData = {
76
		Label: labelName,
77
		Template: template,
78
		Route: route,
79
		IsDefault: !!isDefault
80
	};
81
82
	VIEW_MODELS.settings.push(SettingsViewModelClass);
83
}
84
85
/**
86
 * @param {Function} SettingsViewModelClass
87
 * @returns {void}
88
 */
89
export function removeSettingsViewModel(SettingsViewModelClass)
90
{
91
	VIEW_MODELS['settings-removed'].push(SettingsViewModelClass);
92
}
93
94
/**
95
 * @param {Function} SettingsViewModelClass
96
 * @returns {void}
97
 */
98
export function disableSettingsViewModel(SettingsViewModelClass)
99
{
100
	VIEW_MODELS['settings-disabled'].push(SettingsViewModelClass);
101
}
102
103
/**
104
 * @returns {void}
105
 */
106
export function routeOff()
107
{
108
	hasher.changed.active = false;
109
}
110
111
/**
112
 * @returns {void}
113
 */
114
export function routeOn()
115
{
116
	hasher.changed.active = true;
117
}
118
119
/**
120
 * @param {string} screenName
121
 * @returns {?Object}
122
 */
123
export function screen(screenName)
124
{
125
	return ('' !== screenName && !isUnd(SCREENS[screenName])) ? SCREENS[screenName] : null;
126
}
127
128
/**
129
 * @param {Function} ViewModelClassToHide
130
 * @returns {void}
131
 */
132
export function hideScreenPopup(ViewModelClassToHide)
133
{
134
	if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom)
135
	{
136
		ViewModelClassToHide.__vm.modalVisibility(false);
137
	}
138
}
139
140
/**
141
 * @param {string} hookName
142
 * @param {Function} ViewModelClass
143
 * @param {mixed=} params = null
144
 */
145
export function vmRunHook(hookName, ViewModelClass, params = null)
146
{
147
	_.each(ViewModelClass.__names, (name) => {
148
		runHook(hookName, [name, ViewModelClass.__vm, params]);
149
	});
150
}
151
152
/**
153
 * @param {Function} ViewModelClass
154
 * @param {Object=} vmScreen
155
 * @returns {*}
156
 */
157
export function buildViewModel(ViewModelClass, vmScreen)
158
{
159
	if (ViewModelClass && !ViewModelClass.__builded)
160
	{
161
		let vmDom = null;
0 ignored issues
show
Unused Code introduced by
The assignment to vmDom seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
162
		const
163
			vm = new ViewModelClass(vmScreen),
164
			position = vm.viewModelPosition(),
165
			vmPlace = $('#rl-content #rl-' + position.toLowerCase());
166
167
		ViewModelClass.__builded = true;
168
		ViewModelClass.__vm = vm;
169
170
		vm.onShowTrigger = ko.observable(false);
171
		vm.onHideTrigger = ko.observable(false);
172
173
		vm.viewModelName = ViewModelClass.__name;
174
		vm.viewModelNames = ViewModelClass.__names;
175
176
		if (vmPlace && 1 === vmPlace.length)
177
		{
178
			vmDom = $('<div></div>').addClass('rl-view-model').addClass('RL-' + vm.viewModelTemplate()).hide();
179
			vmDom.appendTo(vmPlace);
180
181
			vm.viewModelDom = vmDom;
182
			ViewModelClass.__dom = vmDom;
183
184
			if ('Popups' === position)
185
			{
186
				vm.cancelCommand = vm.closeCommand = createCommand(vm, () => {
187
					hideScreenPopup(ViewModelClass);
188
				});
189
190
				vm.modalVisibility.subscribe((value) => {
191
					if (value)
192
					{
193
						vm.viewModelDom.show();
194
						vm.storeAndSetKeyScope();
195
196
						popupVisibilityNames.push(vm.viewModelName);
197
						vm.viewModelDom.css('z-index', 3000 + popupVisibilityNames().length + 10);
198
199
						if (vm.onShowTrigger)
200
						{
201
							vm.onShowTrigger(!vm.onShowTrigger());
202
						}
203
204
						delegateRun(vm, 'onShowWithDelay', [], 500);
205
					}
206
					else
207
					{
208
						delegateRun(vm, 'onHide');
209
						delegateRun(vm, 'onHideWithDelay', [], 500);
210
211
						if (vm.onHideTrigger)
212
						{
213
							vm.onHideTrigger(!vm.onHideTrigger());
214
						}
215
216
						vm.restoreKeyScope();
217
218
						vmRunHook('view-model-on-hide', ViewModelClass);
219
220
						popupVisibilityNames.remove(vm.viewModelName);
221
						vm.viewModelDom.css('z-index', 2000);
222
223
						_.delay(() => vm.viewModelDom.hide(), 300);
224
					}
225
				});
226
			}
227
228
			vmRunHook('view-model-pre-build', ViewModelClass, vmDom);
229
230
			ko.applyBindingAccessorsToNode(vmDom[0], {
231
				translatorInit: true,
232
				template: () => ({name: vm.viewModelTemplate()})
233
			}, vm);
234
235
			delegateRun(vm, 'onBuild', [vmDom]);
236
			if (vm && 'Popups' === position)
237
			{
238
				vm.registerPopupKeyDown();
239
			}
240
241
			vmRunHook('view-model-post-build', ViewModelClass, vmDom);
242
		}
243
		else
244
		{
245
			log('Cannot find view model position: ' + position);
246
		}
247
	}
248
249
	return ViewModelClass ? ViewModelClass.__vm : null;
250
}
251
252
/**
253
 * @param {Function} ViewModelClassToShow
254
 * @param {Array=} params
255
 * @returns {void}
256
 */
257
export function showScreenPopup(ViewModelClassToShow, params = [])
258
{
259
	if (ViewModelClassToShow)
260
	{
261
		buildViewModel(ViewModelClassToShow);
262
263
		if (ViewModelClassToShow.__vm && ViewModelClassToShow.__dom)
264
		{
265
			delegateRun(ViewModelClassToShow.__vm, 'onBeforeShow', params || []);
266
267
			ViewModelClassToShow.__vm.modalVisibility(true);
268
269
			delegateRun(ViewModelClassToShow.__vm, 'onShow', params || []);
270
271
			vmRunHook('view-model-on-show', ViewModelClassToShow, params || []);
272
		}
273
	}
274
}
275
276
/**
277
 * @param {Function} ViewModelClassToShow
278
 * @returns {boolean}
279
 */
280
export function isPopupVisible(ViewModelClassToShow)
281
{
282
	return ViewModelClassToShow && ViewModelClassToShow.__vm ? ViewModelClassToShow.__vm.modalVisibility() : false;
283
}
284
285
/**
286
 * @param {string} screenName
287
 * @param {string} subPart
288
 * @returns {void}
289
 */
290
export function screenOnRoute(screenName, subPart)
291
{
292
	let
293
		vmScreen = null,
294
		isSameScreen = false,
295
		cross = null;
296
297
	if ('' === pString(screenName))
298
	{
299
		screenName = defaultScreenName;
300
	}
301
302
	if ('' !== screenName)
303
	{
304
		vmScreen = screen(screenName);
305
		if (!vmScreen)
306
		{
307
			vmScreen = screen(defaultScreenName);
308
			if (vmScreen)
309
			{
310
				subPart = screenName + '/' + subPart;
311
				screenName = defaultScreenName;
312
			}
313
		}
314
315
		if (vmScreen && vmScreen.__started)
316
		{
317
			isSameScreen = currentScreen && vmScreen === currentScreen;
318
319
			if (!vmScreen.__builded)
320
			{
321
				vmScreen.__builded = true;
322
323
				if (isNonEmptyArray(vmScreen.viewModels()))
324
				{
325
					_.each(vmScreen.viewModels(), (ViewModelClass) => {
326
						buildViewModel(ViewModelClass, vmScreen);
327
					});
328
				}
329
330
				delegateRun(vmScreen, 'onBuild');
331
			}
332
333
			_.defer(() => {
334
				// hide screen
335
				if (currentScreen && !isSameScreen)
336
				{
337
					delegateRun(currentScreen, 'onHide');
338
					delegateRun(currentScreen, 'onHideWithDelay', [], 500);
339
340
					if (currentScreen.onHideTrigger)
341
					{
342
						currentScreen.onHideTrigger(!currentScreen.onHideTrigger());
343
					}
344
345
					if (isNonEmptyArray(currentScreen.viewModels()))
346
					{
347
						_.each(currentScreen.viewModels(), (ViewModelClass) => {
348
							if (ViewModelClass.__vm && ViewModelClass.__dom && 'Popups' !== ViewModelClass.__vm.viewModelPosition())
349
							{
350
								ViewModelClass.__dom.hide();
351
								ViewModelClass.__vm.viewModelVisibility(false);
352
353
								delegateRun(ViewModelClass.__vm, 'onHide');
354
								delegateRun(ViewModelClass.__vm, 'onHideWithDelay', [], 500);
355
356
								if (ViewModelClass.__vm.onHideTrigger)
357
								{
358
									ViewModelClass.__vm.onHideTrigger(!ViewModelClass.__vm.onHideTrigger());
359
								}
360
							}
361
						});
362
					}
363
				}
364
				// --
365
366
				currentScreen = vmScreen;
367
368
				// show screen
369
				if (currentScreen && !isSameScreen)
370
				{
371
					delegateRun(currentScreen, 'onShow');
372
					if (currentScreen.onShowTrigger)
373
					{
374
						currentScreen.onShowTrigger(!currentScreen.onShowTrigger());
375
					}
376
377
					runHook('screen-on-show', [currentScreen.screenName(), currentScreen]);
378
379
					if (isNonEmptyArray(currentScreen.viewModels()))
380
					{
381
						_.each(currentScreen.viewModels(), (ViewModelClass) => {
382
383
							if (ViewModelClass.__vm && ViewModelClass.__dom && 'Popups' !== ViewModelClass.__vm.viewModelPosition())
384
							{
385
								delegateRun(ViewModelClass.__vm, 'onBeforeShow');
386
387
								ViewModelClass.__dom.show();
388
								ViewModelClass.__vm.viewModelVisibility(true);
389
390
								delegateRun(ViewModelClass.__vm, 'onShow');
391
								if (ViewModelClass.__vm.onShowTrigger)
392
								{
393
									ViewModelClass.__vm.onShowTrigger(!ViewModelClass.__vm.onShowTrigger());
394
								}
395
396
								delegateRun(ViewModelClass.__vm, 'onShowWithDelay', [], 200);
397
								vmRunHook('view-model-on-show', ViewModelClass);
398
							}
399
400
						});
401
					}
402
				}
403
				// --
404
405
				cross = vmScreen.__cross ? vmScreen.__cross() : null;
406
				if (cross)
407
				{
408
					cross.parse(subPart);
409
				}
410
			});
411
		}
412
	}
413
}
414
415
/**
416
 * @param {Array} screensClasses
417
 * @returns {void}
418
 */
419
export function startScreens(screensClasses)
420
{
421
	_.each(screensClasses, (CScreen) => {
422
		if (CScreen)
423
		{
424
			const
425
				vmScreen = new CScreen(),
426
				screenName = vmScreen ? vmScreen.screenName() : '';
427
428
			if (vmScreen && '' !== screenName)
429
			{
430
				if ('' === defaultScreenName)
431
				{
432
					defaultScreenName = screenName;
433
				}
434
435
				SCREENS[screenName] = vmScreen;
436
			}
437
		}
438
	});
439
440
	_.each(SCREENS, (vmScreen) => {
441
		if (vmScreen && !vmScreen.__started && vmScreen.__start)
442
		{
443
			vmScreen.__started = true;
444
			vmScreen.__start();
445
446
			runHook('screen-pre-start', [vmScreen.screenName(), vmScreen]);
447
			delegateRun(vmScreen, 'onStart');
448
			runHook('screen-post-start', [vmScreen.screenName(), vmScreen]);
449
		}
450
	});
451
452
	const cross = crossroads.create();
453
	cross.addRoute(/^([a-zA-Z0-9\-]*)\/?(.*)$/, screenOnRoute);
454
455
	hasher.initialized.add(cross.parse, cross);
456
	hasher.changed.add(cross.parse, cross);
457
	hasher.init();
458
459
	_.delay(() => $html.removeClass('rl-started-trigger').addClass('rl-started'), 100);
460
	_.delay(() => $html.addClass('rl-started-delay'), 200);
461
}
462
463
/**
464
 * @param {string} sHash
0 ignored issues
show
Documentation introduced by
The parameter sHash does not exist. Did you maybe forget to remove this comment?
Loading history...
465
 * @param {boolean=} silence = false
466
 * @param {boolean=} replace = false
467
 * @returns {void}
468
 */
469
export function setHash(hash, silence = false, replace = false)
470
{
471
	hash = '#' === hash.substr(0, 1) ? hash.substr(1) : hash;
472
	hash = '/' === hash.substr(0, 1) ? hash.substr(1) : hash;
473
474
	const cmd = replace ? 'replaceHash' : 'setHash';
475
476
	if (silence)
477
	{
478
		hasher.changed.active = false;
479
		hasher[cmd](hash);
480
		hasher.changed.active = true;
481
	}
482
	else
483
	{
484
		hasher.changed.active = true;
485
		hasher[cmd](hash);
486
		hasher.setHash(hash);
487
	}
488
}
489