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 (#2843)
by Brendan
04:11
created

symphony/assets/js/src/symphony.duplicator.js   B

Complexity

Total Complexity 42
Complexity/F 2.21

Size

Lines of Code 342
Function Count 19

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
c 0
b 0
f 0
nc 8
dl 0
loc 342
rs 8.295
wmc 42
mnd 3
bc 43
fnc 19
bpm 2.2631
cpm 2.2105
noi 13

1 Function

Rating   Name   Duplication   Size   Complexity  
B $.fn.symphonyDuplicator 0 311 1

How to fix   Complexity   

Complexity

Complex classes like symphony/assets/js/src/symphony.duplicator.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
7
	/**
8
	 * Duplicators are advanced lists used throughout the
9
	 * Symphony backend to manage repeatable content.
10
	 *
11
	 * @name $.symphonyDuplicator
12
	 * @class
13
	 *
14
	 * @param {Object} options An object specifying containing the attributes specified below
15
	 * @param {String} [options.instances='> li:not(.template)'] Selector to find children to use as instances
16
	 * @param {String} [options.templates='> li.template'] Selector to find children to use as templates
17
	 * @param {String} [options.headers='> :first-child'] Selector to find the header part of each instance
18
	 * @param {String} [options.perselect=false] Default option for the selector
19
	 * @param {Boolean} [options.orderable=false] Can instances be ordered
20
	 * @param {Boolean} [options.collapsible=false] Can instances be collapsed
21
	 * @param {Boolean} [options.constructable=true] Allow construction of new instances
22
	 * @param {Boolean} [options.destructable=true] Allow destruction of instances
23
	 * @param {Integer} [optionss.minimum=0] Do not allow instances to be removed below this limit
24
	 * @param {Integer} [options.maximum=1000] Do not allow instances to be added above this limit,
25
	 * @param {Integer} [options.delay=250'] Time delay for animations
26
	 *
27
	 * @example
28
29
			$('.duplicator').symphonyDuplicator({
30
				orderable: true,
31
				collapsible: true
32
			});
33
	 */
34
	$.fn.symphonyDuplicator = function(options) {
35
		var objects = this,
36
			settings = {
37
				instances: '> li:not(.template)',
38
				templates: '> li.template',
39
				headers: '> :first-child',
40
				preselect: false,
41
				orderable: false,
42
				collapsible: false,
43
				constructable: true,
44
				destructable: true,
45
				save_state: true,
46
				minimum: 0,
47
				maximum: 1000,
48
				delay: 250
49
			};
50
51
		$.extend(settings, options);
52
53
	/*-----------------------------------------------------------------------*/
54
55
		// Language strings
56
		Symphony.Language.add({
57
			'Add item': false,
58
			'Remove item': false
59
		});
60
61
	/*-----------------------------------------------------------------------*/
62
63
		objects.each(function duplicators() {
64
			var duplicator = $(this),
65
				list = duplicator.find('> ol'),
66
				apply = $('<fieldset class="apply" />'),
67
				selector = $('<select />'),
68
				constructor = $('<button type="button" class="constructor" />'),
69
				instances, templates, items, headers;
70
71
			// Initialise duplicator components
72
			duplicator.addClass('duplicator').addClass('empty');
73
			instances = list.find(settings.instances).addClass('instance');
74
			templates = list.find(settings.templates).addClass('template');
75
			items = instances.add(templates);
76
			headers = items.find(settings.headers).addClass('frame-header');
77
			constructor.text(list.attr('data-add') || Symphony.Language.get('Add item'));
78
79
		/*---------------------------------------------------------------------
80
			Events
81
		---------------------------------------------------------------------*/
82
83
			// Construct instances
84
			apply.on('click.duplicator', '.constructor:not(.disabled)', function construct(event, speed) {
0 ignored issues
show
Unused Code introduced by
The parameter speed is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
85
				var instance = templates.filter('[data-type="' + $(this).parent().find('select').val() + '"]').clone(true),
86
					heightMin, heightMax;
0 ignored issues
show
Unused Code introduced by
The variable heightMax seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable heightMin seems to be never used. Consider removing it.
Loading history...
87
88
				event.preventDefault();
89
90
				// Prepare instance
91
				instance
92
					.trigger('constructstart.duplicator')
93
					.appendTo(list);
94
95
				// Duplicator is not empty
96
				duplicator.removeClass('empty');
97
98
				// Show instance
99
				instance
100
					.trigger('constructshow.duplicator');
101
					
102
				// Update collapsible sizes
103
				instance.trigger('updatesize.collapsible');
104
				instance.trigger('setsize.collapsible');
105
106
				setTimeout(function() {
107
					instance.trigger('animationend.duplicator');
108
				}, settings.delay);
109
			});
110
111
			// Destruct instances
112
			duplicator.on('click.duplicator', '.destructor:not(.disabled)', function destruct(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
113
				var instance = $(this).closest('.instance');
114
115
				// Remove instance
116
				instance
117
					.trigger('collapse.collapsible')
118
					.trigger('destructstart.duplicator')
119
					.addClass('destructed');
120
121
				setTimeout(function() {
122
					instance.trigger('animationend.duplicator');
123
				}, settings.delay);
124
			});
125
126
			// Finish animations
127
			duplicator.on('animationend.duplicator', '.instance', function finish() {
128
				var instance = $(this).removeClass('js-animate');
129
130
				// Trigger events
131
				if(instance.is('.destructed')) {
132
					instance.remove();
133
134
					// Check if duplicator is empty
135
					if(duplicator.find('.instance').length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing duplicator.find(".instance").length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
136
						duplicator.addClass('empty');
137
					}
138
139
					instance.trigger('destructstop.duplicator');
140
					duplicator.trigger('destructstop.duplicator', [instance]);
141
				}
142
				else {
143
					instance.trigger('constructstop.duplicator');
144
				}
145
146
				// Update collapsible states
147
				if(settings.collapsible) {
148
					instance.trigger('store.collapsible');
149
				}
150
			});
151
152
			// Lock constructor
153
			duplicator.on('constructstop.duplicator', '.instance', function lockConstructor() {
154
				if(duplicator.find('.instance').length >= settings.maximum) {
155
					constructor.addClass('disabled');
156
				}
157
			});
158
159
			// Unlock constructor
160
			duplicator.on('destructstart.duplicator', '.instance', function unlockConstructor() {
161
				if(duplicator.find('.instance').length <= settings.maximum) {
162
					constructor.removeClass('disabled');
163
				}
164
			});
165
166
			// Lock destructor
167
			duplicator.on('destructstart.duplicator', '.instance', function lockDestructor() {
168
				if(duplicator.find('.instance').length - 1 == settings.minimum) {
169
					duplicator.find('a.destructor').addClass('disabled');
170
				}
171
			});
172
173
			// Unlock destructor
174
			duplicator.on('constructstop.duplicator', '.instance', function unlockDestructor() {
175
				if(duplicator.find('.instance').length > settings.minimum) {
176
					duplicator.find('a.destructor').removeClass('disabled');
177
				}
178
			});
179
180
			// Lock unique instances
181
			duplicator.on('constructstop.duplicator', '.instance', function lockUnique(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
182
				var instance = $(this);
183
184
				if(instance.is('.unique')) {
185
					selector.find('option[value="' + instance.attr('data-type') + '"]').attr('disabled', true);
186
187
					// Preselect first available instance
188
					selector.find('option').prop('selected', false).filter(':not(:disabled):first').prop('selected', true);
189
190
					// All selected
191
					if(selector.find('option:not(:disabled)').length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing selector.find("option:not(:disabled)").length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
192
						selector.attr('disabled', 'disabled');
193
					}
194
				}
195
			});
196
197
			// Unlock unique instances
198
			duplicator.on('destructstart.duplicator', '.instance', function unlockUnique(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
199
				var instance = $(this),
200
					option;
201
202
				if(instance.is('.unique')) {
203
					option = selector.attr('disabled', false).find('option[value="' + instance.attr('data-type') + '"]').attr('disabled', false);
204
205
					// Preselect instance if it's the only active one
206
					if(selector.find('option:not(:disabled)').length == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing selector.find("option:not(:disabled)").length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
207
						option.prop('selected', true);
208
					}
209
				}
210
			});
211
212
			// Build field indexes
213
			duplicator.on('constructstop.duplicator refresh.duplicator', '.instance', function buildIndexes(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
214
				var instance = $(this),
215
					position = duplicator.find('.instance').index(instance);
216
217
				// Loop over named fields
218
				instance.find('*[name]').each(function() {
219
					var field = $(this),
220
						exp = /\[\-?[0-9]+\]/,
221
						name = field.attr('name');
222
223
					// Set index
224
					if(exp.test(name)) {
225
						field.attr('name', name.replace(exp, '[' + position + ']'));
226
					}
227
				});
228
			});
229
230
			// Refresh field indexes
231
			duplicator.on('orderstop.orderable', function refreshIndexes(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
232
				duplicator.find('.instance').trigger('refresh.duplicator');
233
			});
234
235
		/*---------------------------------------------------------------------
236
			Initialisation
237
		---------------------------------------------------------------------*/
238
239
			// Wrap content, if needed
240
			headers.each(function wrapContent() {
241
				var header = $(this);
242
243
				if (!header.next('.content').length) {
244
					header.nextAll().wrapAll( $('<div />').attr('class','content') );
245
				}
246
			});
247
248
			// Constructable interface
249
			if(settings.constructable === true) {
250
				duplicator.addClass('constructable');
251
				apply.append($('<div />').append(selector)).append(constructor);
252
				apply.appendTo(duplicator);
253
254
				// Populate selector
255
				templates.detach().each(function createTemplates() {
256
					var template = $(this),
257
						title = $.trim(template.find(settings.headers).attr('data-name'))
258
								|| $.trim(template.find(settings.headers).text()),
259
						value = $.trim(template.attr('data-type'));
260
261
					template.trigger('constructstart.duplicator');
262
263
					// Check type connection
264
					if(!value) {
265
						value = title;
266
						template.attr('data-type', value);
267
					}
268
269
					// Append options
270
					$('<option />', {
271
						text: title,
272
						value: value
273
					}).appendTo(selector);
274
275
					// Check uniqueness
276
					template.trigger('constructstop.duplicator');
277
				}).removeClass('template').addClass('instance');
278
			}
279
280
			// Select default
281
			if(settings.preselect != false) {
0 ignored issues
show
Best Practice introduced by
Comparing settings.preselect to false using the != operator is not safe. Consider using !== instead.
Loading history...
282
				selector.find('option[value="' + settings.preselect + '"]').prop('selected', true);
283
			}
284
285
			// Single template
286
			if(templates.length <= 1) {
287
				apply.addClass('single');
288
289
				// Single unique template
290
				if(templates.is('.unique')) {
291
					if(instances.length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing instances.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
292
						constructor.trigger('click.duplicator', [0]);
293
					}
294
					
295
					apply.hide();
296
				}
297
			}
298
299
			// Destructable interface
300
			if(settings.destructable === true) {
301
				duplicator.addClass('destructable');
302
				headers.append(
303
						$('<a />')
304
							.attr('class', 'destructor')
305
							.text(list.attr('data-remove') || Symphony.Language.get('Remove item'))
306
						);
307
			}
308
309
			// Collapsible interface
310
			if(settings.collapsible) {
311
				duplicator.symphonyCollapsible({
312
					items: '.instance',
313
					handles: '.frame-header',
314
					ignore: '.destructor',
315
					save_state: settings.save_state,
316
					delay: settings.delay
317
				});
318
			}
319
320
			// Orderable interface
321
			if(settings.orderable) {
322
				duplicator.symphonyOrderable({
323
					items: '.instance',
324
					handles: '.frame-header'
325
				});
326
			}
327
328
			// Catch errors
329
			instances.filter(':has(.invalid)').addClass('conflict');
330
331
			// Initialise existing instances
332
			instances.trigger('constructstop.duplicator');
333
			instances.find('input[name*="[label]"]').trigger('keyup.duplicator');
334
335
			// Check for existing instances
336
			if(instances.length > 0) {
337
				duplicator.removeClass('empty');
338
			}
339
		});
340
341
	/*-----------------------------------------------------------------------*/
342
343
		return objects;
344
	};
345
346
})(window.jQuery, window.Symphony);
347