GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#2835)
by
unknown
06:01
created

symphony/assets/js/src/symphony.suggestions.js   D

Complexity

Total Complexity 71
Complexity/F 2.73

Size

Lines of Code 470
Function Count 26

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
c 0
b 0
f 0
nc 3072
dl 0
loc 470
rs 4.0769
wmc 71
mnd 5
bc 71
fnc 26
bpm 2.7307
cpm 2.7307
noi 3

20 Functions

Rating   Name   Duplication   Size   Complexity  
A symphony.suggestions.js ➔ tokenize 0 15 3
D symphony.suggestions.js ➔ handleChange 0 32 9
B symphony.suggestions.js ➔ list 0 29 2
B Symphony.Interface.Suggestions.constructor 0 465 1
A symphony.suggestions.js ➔ schedule 0 8 2
A symphony.suggestions.js ➔ handleSelect 0 5 1
A symphony.suggestions.js ➔ handleOver 0 6 1
A symphony.suggestions.js ➔ select 0 21 4
A symphony.suggestions.js ➔ down 0 17 3
A symphony.suggestions.js ➔ handleOut 0 5 1
B symphony.suggestions.js ➔ insert 0 29 3
A symphony.suggestions.js ➔ createCalendar 0 6 1
B symphony.suggestions.js ➔ handleNavigation 0 32 6
A symphony.suggestions.js ➔ createSuggestions 0 20 1
B symphony.suggestions.js ➔ load 0 61 8
A symphony.suggestions.js ➔ init 0 22 1
A symphony.suggestions.js ➔ clear 0 4 1
A symphony.suggestions.js ➔ stayInFocus 0 17 2
A symphony.suggestions.js ➔ up 0 17 3
B symphony.suggestions.js ➔ listtoken 0 42 2

How to fix   Complexity   

Complexity

Complex classes like symphony/assets/js/src/symphony.suggestions.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
 * @package assets
3
 */
4
5
(function($, Symphony) {
6
	'use strict';
7
8
	Symphony.Interface.Suggestions = function() {
9
10
		var context;
11
		var options;
12
13
		/**
14
		 * Initialise suggestions
15
		 */
16
		var init = function(element, selector, opts) {
17
			context = $(element);
18
			options = $.extend({}, options, opts);
19
20
			// Disable autocomplete
21
			context.find(selector).each(function() {
22
				this.autocomplete = 'off';
23
			});
24
25
			// Create suggestion lists
26
			createSuggestions(selector);
27
28
			// Interactions
29
			context.on('input.suggestions', selector, handleChange);
30
			context.on('click.suggestions', selector, handleChange);
31
			context.on('focus.suggestions', selector, handleChange);
32
			context.on('keyup.suggestions', selector, handleChange);
33
			context.on('mouseover.suggestions', '.suggestions li:not(.help):not(.calendar)', handleOver);
34
			context.on('mouseout.suggestions', '.suggestions li:not(.help):not(.calendar)', handleOut);
35
			context.on('mousedown.suggestions', '.suggestions li:not(.help):not(.calendar)', handleSelect);
36
			context.on('keydown.suggestions', selector, handleNavigation);
37
		};
38
39
	/*-------------------------------------------------------------------------
40
		Event handling
41
	-------------------------------------------------------------------------*/
42
43
		/**
44
		 * Load suggestions based on type while the user types.
45
		 */
46
		var handleChange = function(event) {
47
			var input = $(this),
48
				value = input.val(),
49
				suggestions = input.next('.suggestions'),
50
				types = suggestions.attr('data-search-types'),
51
				trigger = input.attr('data-trigger');
52
53
			// Stop when navigating the suggestion list
54
			if(jQuery.inArray(event.which, [13, 27, 38, 40]) !== -1) {
55
				return;
56
			}
57
58
			// Dates
59
			if(types && types.indexOf('date') !== -1) {
60
				schedule(input);
61
			}
62
63
			// Tokens
64
			else if(value && trigger) {
65
				tokenize(input, suggestions, value, trigger);
66
			}
67
68
			// Entries
69
			else if(value || (types && types.indexOf('static') !== -1)) {
70
				load(input, value);
71
			}
72
73
			// No input
74
			else {
75
				clear(suggestions);
76
			}
77
		};
78
79
		/**
80
		 * Handle mouse interactions on the suggestion list.
81
		 * In order to make this work with the keyboard as well, set the class
82
		 * `.active` to the current target.
83
		 *
84
		 * @param Event event
85
		 *  The mouseover event
86
		 */
87
		var handleOver = function(event) {
88
			var suggestion = $(event.target);
89
90
			suggestion.siblings('li:not(.help)').removeClass('active');
91
			suggestion.addClass('active');
92
		};
93
94
		/**
95
		 * Handle finished mouse interactions on the suggestion list and
96
		 * remove `.active` class set by `handleOver`.
97
		 *
98
		 * @param Event event
99
		 *  The mouseout event
100
		 */
101
		var handleOut = function(event) {
102
			var suggestion = $(event.target);
103
104
			suggestion.removeClass('active');
105
		};
106
107
		/**
108
		 * Handle keyboard navigation in the suggestion list.
109
		 *
110
		 * @param Event event
111
		 *  The keydown event
112
		 */
113
		var handleNavigation = function(event) {
114
			var input = $(this),
115
				active;
116
117
			// Down
118
			if(event.which == 40) {
119
				event.preventDefault();
120
				down(input);
121
			}
122
123
			// Up
124
			else if(event.which == 38) {
125
				event.preventDefault();
126
				up(input);
127
			}
128
129
			// Exit
130
			else if(event.which == 27) {
131
				event.preventDefault();
132
				input.blur();
133
			}
134
135
			// Enter
136
			else if(event.which == 13) {
137
				event.preventDefault();
138
				active = input.next('.suggestions').find('li:not(.help).active');
139
140
				if(active.length) {
141
					select(active, input);
142
				}
143
			}
144
		};
145
146
		/**
147
		 * Handle suggestion selection by click.
148
		 *
149
		 * @param Event event
150
		 *  The mousedown event
151
		 */
152
		var handleSelect = function(event) {
153
			var input = $(event.target).parent('.suggestions').prev('input');
154
155
			select($(event.target), input);
156
		};
157
158
	/*-------------------------------------------------------------------------
159
		Suggestions
160
	-------------------------------------------------------------------------*/
161
162
		var tokenize = function(input, suggestions, value, trigger) {
163
			var selectionStart = input[0].selectionStart || 0,
164
				before = value.substring(0, selectionStart).split(' '),
165
				after = value.substr(selectionStart).split(' '),
166
				token = before[before.length - 1],
167
				param = before[before.length - 1] + after[0];
168
169
			// Token found
170
			if(token && token.indexOf(trigger) === 0) {
171
				load(input, param);
172
			}
173
			else {
174
				clear(suggestions);
175
			}
176
		};
177
178
		var load = function(input, value) {
179
			var suggestions = input.next('.suggestions'),
180
				types = suggestions.attr('data-search-types'),
181
				trigger = input.attr('data-trigger'),
182
				query = value,
183
				prefix, data, url;
184
185
			// Prefix
186
			if(trigger) {
187
				prefix = trigger.substr(0, 1);
188
			}
189
190
			// Get value
191
			if(!query) {
192
				query = input.val();
193
			}
194
195
			if(prefix === '{') {
0 ignored issues
show
Bug introduced by
The variable prefix does not seem to be initialized in case trigger on line 186 is false. Are you sure this can never be the case?
Loading history...
196
				query = query.substr(1);
197
			}
198
199
			// Get data
200
			if(types && types.indexOf('parameters') !== -1) {
201
				url = Symphony.Context.get('symphony') + '/ajax/parameters/';
202
				data = {
203
					'query': query
204
				};
205
			}
206
			else {
207
				url = Symphony.Context.get('symphony') + '/ajax/query/';
208
				data = {
209
					'field_id': suggestions.attr('data-field-id'),
210
					'query': query,
211
					'types': types
212
				};
213
			}
214
215
			// Get custom url
216
			if(input.attr('data-url')) {
217
				url = input.attr('data-url');
218
			}
219
220
			// Load suggestions
221
			if(query !== suggestions.attr('data-last-query')) {
222
				suggestions.attr('data-last-query', query);
223
224
				$.ajax({
225
					type: 'GET',
226
					url: url,
227
					data: data,
228
					success: function(result) {
229
						if(types && types.indexOf('parameters') !== -1) {
230
							listtoken(input, suggestions, result);
231
						}
232
						else {
233
							list(suggestions, result);
234
						}
235
					}
236
				});
237
			}
238
		};
239
240
		var listtoken = function(input, suggestions, result) {
241
			var clone = suggestions.clone(),
242
				help = clone.find('.help:first'),
243
				trigger = input.attr('data-trigger'),
244
				prefix;
245
246
			// Prefix
247
			if(trigger) {
248
				prefix = trigger.substr(0, 1);
249
			}
250
251
			// Clear existing suggestions
252
			clear(clone);
253
254
			// Add suggestions
255
			$.each(result, function(index, value) {
256
				if (index === 'status') {
257
					return;
258
				}
259
260
				if (prefix === '{') {
0 ignored issues
show
Bug introduced by
The variable prefix does not seem to be initialized in case trigger on line 247 is false. Are you sure this can never be the case?
Loading history...
261
					value = '{' + value + '}';
0 ignored issues
show
Comprehensibility Best Practice introduced by
This re-assigns to the parameter value. Re-assigning to parameters often makes code less readable, consider introducing a new variable instead.
Loading history...
262
				}
263
264
				var suggestion = $('<li />', {
265
					text: value
266
				});
267
268
				if ($.isFunction(options.editSuggestion)) {
269
					options.editSuggestion(suggestion, index, value, result);
270
				}
271
272
				if (help.length) {
273
					suggestion.insertBefore(help);
274
				}
275
				else {
276
					clone.append(suggestion);
277
				}
278
			});
279
280
			suggestions.replaceWith(clone);
281
		};
282
283
		var list = function(suggestions, result) {
284
			var clone = suggestions.clone(),
285
				help = clone.find('.help:first');
286
287
			// Clear existing suggestions
288
			clear(clone);
289
290
			// Add suggestions
291
			if(result.entries) {
292
				$.each(result.entries, function(index, data) {
293
					var suggestion = $('<li />', {
294
						text: data.value
295
					});
296
297
					if ($.isFunction(options.editSuggestion)) {
298
						options.editSuggestion(suggestion, index, data, result);
299
					}
300
301
					if (help.length) {
302
						suggestion.insertBefore(help);
303
					}
304
					else {
305
						clone.append(suggestion);
306
					}
307
				});
308
309
				suggestions.replaceWith(clone);
310
			}
311
		};
312
313
		var schedule = function(input) {
314
			var suggestions = input.next('.suggestions'),
315
				calendar = suggestions.find('.calendar');
316
317
			if(!calendar.length) {
318
				createCalendar(suggestions);
319
			}
320
		};
321
322
		var select = function(value, input) {
323
			var types = input.attr('data-search-types');
324
			var text = value.text();
325
326
			if(types && types.indexOf('parameters') !== -1) {
327
				insert(text, input);
328
			}
329
			else {
330
				text = $.trim(text.replace(/,/g, '\\,').replace(/&/g, '%26'));
331
				input.attr('data-value', value.attr('data-value'));
332
				input.val(text);
333
				input.addClass('updated');
334
				input.change();
335
			}
336
337
			if ($.isFunction(options.valueSelected)) {
338
				options.valueSelected(value, input);
339
			}
340
341
			clear(input.next('.suggestions'));
342
		};
343
344
		var insert = function(suggestion, input) {
345
			var value = input.val(),
346
				selectionStart = input[0].selectionStart || 0,
347
				beforeSelection = value.substring(0, selectionStart).split(' '),
348
				afterSelection = value.substr(selectionStart).split(' '),
349
				before = '',
350
				after = '';
351
352
			// Get text before parameter
353
			if(beforeSelection.length > 1) {
354
				beforeSelection.pop();
355
				before = beforeSelection.join(' ') + ' ';
356
			}
357
358
			// Get text after parameter
359
			if(afterSelection.length > 1) {
360
				afterSelection.shift();
361
				after = ' ' + afterSelection.join(' ');
362
			}
363
364
			// Insert suggestion
365
			input.val(before + suggestion + after);
366
367
			// Set cursor
368
			var length = before.length + suggestion.length;
369
			input[0].selectionStart = length;
370
			input[0].selectionEnd = length;
371
			input.focus();
372
		};
373
374
		var clear = function(suggestions) {
375
			suggestions.removeAttr('data-last-query');
376
			suggestions.find('li:not(.help)').remove();
377
		};
378
379
		var up = function(input) {
380
			var suggestions = input.next('.suggestions'),
381
				active = suggestions.find('li:not(.help).active').removeClass('active'),
382
				prev = active.prev('li:not(.help):visible');
383
384
			// First
385
			if(active.length === 0 || prev.length === 0) {
386
				suggestions.find('li:not(.help)').last().addClass('active');
387
			}
388
389
			// Next
390
			else {
391
				prev.addClass('active');
392
			}
393
			
394
			stayInFocus(suggestions);
395
		};
396
397
		var down = function(input) {
398
			var suggestions = input.next('.suggestions'),
399
				active = suggestions.find('li:not(.help).active').removeClass('active'),
400
				next = active.next('li:not(.help):visible');
401
402
			// First
403
			if(active.length === 0 || next.length === 0) {
404
				suggestions.find('li:not(.help)').first().addClass('active');
405
			}
406
407
			// Next
408
			else {
409
				next.addClass('active');
410
			}
411
			
412
			stayInFocus(suggestions);
413
		};
414
415
	/*-------------------------------------------------------------------------
416
		Utilities
417
	-------------------------------------------------------------------------*/
418
419
		var createSuggestions = function(selector) {
420
			var inputs = context.find(selector);
421
422
			inputs.each(function() {
423
				var input = $(this),
424
					suggestions = input.next('.suggestions'),
425
					list, types;
426
427
				if(!suggestions.length) {
428
					list = $('<ul class="suggestions" />');
429
430
					types = input.attr('data-search-types');
431
					if(types) {
432
						list.attr('data-search-types', types);
433
					}
434
435
					list.insertAfter(input);
436
				}
437
			});
438
		};
439
440
		var createCalendar = function(suggestions) {
441
			var calendar = new Symphony.Interface.Calendar();
442
443
			suggestions.prepend('<li class="calendar" data-format="YYYY-MM-DD" />');
444
			calendar.init(suggestions.parents('label'));
445
		};
446
447
		var stayInFocus = function(suggestions) {
448
			var active = suggestions.find('li.active'),
449
				distance;
450
451
			// Get distance
452
			if(!active.is(':visible:first')) {
453
				distance = ((active.prevAll().length + 1) * active.outerHeight()) - 180;
454
			}
455
			else {
456
				distance = 0;
457
			}
458
459
			// Focus
460
			suggestions.animate({
461
				'scrollTop': distance
462
			}, 150);
463
		};
464
465
	/*-------------------------------------------------------------------------
466
		API
467
	-------------------------------------------------------------------------*/
468
469
		return {
470
			init: init
471
		};
472
	}();
473
474
})(window.jQuery, window.Symphony);
475