Test Failed
Push — master ( e111e4...359636 )
by
unknown
24:17
created

js/pico.js   F

Complexity

Total Complexity 67
Complexity/F 1.97

Size

Lines of Code 558
Function Count 34

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 67
eloc 226
mnd 33
bc 33
fnc 34
dl 0
loc 558
rs 3.04
bpm 0.9705
cpm 1.9705
noi 0
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like js/pico.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
 * CMS Pico - Create websites using Pico CMS for Nextcloud.
3
 *
4
 * @copyright Copyright (c) 2019, Daniel Rudolf (<[email protected]>)
5
 *
6
 * @license GNU AGPL version 3 or any later version
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License as
10
 * published by the Free Software Foundation, either version 3 of the
11
 * License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
/** global: OC */
23
/** global: OCA */
24
/** global: jQuery */
25
26
(function (document, $, OC, OCA) {
27
	'use strict';
28
29
	if (!OCA.CMSPico) {
30
		/** @namespace OCA.CMSPico */
31
		OCA.CMSPico = {};
32
	}
33
34
	/**
35
	 * @class
36
	 *
37
	 * @param {jQuery}        $element
38
	 * @param {Object}        [options]
39
	 * @param {string}        [options.route]
40
	 * @param {jQuery|string} [options.template]
41
	 * @param {jQuery|string} [options.loadingTemplate]
42
	 * @param {jQuery|string} [options.errorTemplate]
43
	 */
44
	OCA.CMSPico.List = function ($element, options) {
45
		this.initialize($element, options);
46
	};
47
48
	/**
49
	 * @lends OCA.CMSPico.List.prototype
50
	 */
51
	OCA.CMSPico.List.prototype = {
52
		/** @member {jQuery} */
53
		$element: $(),
54
55
		/** @member {string} */
56
		route: '',
57
58
		/** @member {jQuery} */
59
		$template: $(),
60
61
		/** @member {jQuery} */
62
		$loadingTemplate: $(),
63
64
		/** @member {jQuery} */
65
		$errorTemplate: $(),
66
67
		/**
68
		 * @constructs
69
		 *
70
		 * @param {jQuery}        $element
71
		 * @param {Object}        [options]
72
		 * @param {string}        [options.route]
73
		 * @param {jQuery|string} [options.template]
74
		 * @param {jQuery|string} [options.loadingTemplate]
75
		 * @param {jQuery|string} [options.errorTemplate]
76
		 */
77
		initialize: function ($element, options) {
78
			this.$element = $element;
79
80
			options = $.extend({
81
				route: $element.data('route'),
82
				template: $element.data('template'),
83
				loadingTemplate: $element.data('loadingTemplate'),
84
				errorTemplate: $element.data('errorTemplate')
85
			}, options);
86
87
			this.route = options.route;
88
			this.$template = $(options.template);
89
			this.$loadingTemplate = $(options.loadingTemplate);
90
			this.$errorTemplate = $(options.errorTemplate);
91
92
			var signature = 'OCA.CMSPico.List.initialize()';
93
			if (!this.route) throw signature + ': No route given';
94
			if (!this.$template.length) throw signature + ': No valid list template given';
95
			if (!this.$loadingTemplate.length) throw signature + ': No valid loading template given';
96
			if (!this.$errorTemplate.length) throw signature + ': No valid error template given';
97
		},
98
99
		/**
100
		 * @public
101
		 */
102
		reload: function () {
103
			this._api('GET');
104
		},
105
106
		/**
107
		 * @public
108
		 * @abstract
109
		 *
110
		 * @param {Object} data
111
		 */
112
		update: function (data) {},
113
114
		/**
115
		 * @protected
116
		 *
117
		 * @param {string}         method
118
		 * @param {string}         [item]
119
		 * @param {Object}         [data]
120
		 * @param {function(data)} [callback]
121
		 */
122
		_api: function (method, item, data, callback) {
123
			var that = this,
124
				url = this.route + (item ? ((this.route.substr(-1) !== '/') ? '/' : '') + item : '');
125
126
			this._content(this.$loadingTemplate);
127
128
			$.ajax({
129
				method: method,
130
				url: OC.generateUrl(url),
131
				data: data || {}
132
			}).done(function (data, textStatus, jqXHR) {
133
				if (callback === undefined) {
134
					that.update(data);
135
				} else if (typeof callback === 'function') {
136
					callback(data);
137
				}
138
			}).fail(function (jqXHR, textStatus, errorThrown) {
139
				that._error((jqXHR.responseJSON || {}).error);
140
			});
141
		},
142
143
		/**
144
		 * @protected
145
		 *
146
		 * @param {jQuery}  $template
147
		 * @param {object}  [vars]
148
		 * @param {boolean} [replaceContent]
149
		 *
150
		 * @returns {jQuery}
151
		 */
152
		_content: function ($template, vars, replaceContent) {
153
			var $baseElement = $($template.data('replaces') || $template.data('appendTo') || this.$element),
154
				$content = $template.octemplate(vars || {}) || $();
155
156
			$baseElement.find('.has-tooltip').tooltip('hide');
157
			$content.find('.has-tooltip').tooltip();
158
159
			if ((replaceContent !== undefined) ? replaceContent : $template.data('replaces')) {
160
				$baseElement.empty();
161
			}
162
163
			$content.appendTo($baseElement);
164
165
			return $content;
166
		},
167
168
		/**
169
		 * @protected
170
		 *
171
		 * @param {string} [message]
172
		 */
173
		_error: function (message) {
174
			var $error = this._content(this.$errorTemplate, { message: message || '' }),
175
				that = this;
176
177
			if (message) {
178
				$error.find('.error-details').show();
179
			}
180
181
			$error.find('.action-reload').on('click.CMSPicoAdminList', function (event) {
182
				event.preventDefault();
183
				that.reload();
184
			});
185
		}
186
	};
187
188
	/**
189
	 * @class
190
	 *
191
	 * @param {jQuery} $element
192
	 * @param {Object} [options]
193
	 * @param {string} [options.route]
194
	 */
195
	OCA.CMSPico.Form = function ($element, options) {
196
		this.initialize($element, options);
197
	};
198
199
	/**
200
	 * @lends OCA.CMSPico.Form.prototype
201
	 */
202
	OCA.CMSPico.Form.prototype = {
203
		/** @member {jQuery} */
204
		$element: $(),
205
206
		/** @member {string} */
207
		route: '',
208
209
		/**
210
		 * @constructs
211
		 *
212
		 * @param {jQuery} $element
213
		 * @param {Object} [options]
214
		 * @param {string} [options.route]
215
		 */
216
		initialize: function ($element, options) {
217
			this.$element = $element;
218
219
			options = $.extend({
220
				route: $element.data('route')
221
			}, options);
222
223
			this.route = options.route;
224
225
			var signature = 'OCA.CMSPico.Form.initialize()';
226
			if (!this.route) throw signature + ': No route given';
227
		},
228
229
		/**
230
		 * @public
231
		 * @abstract
232
		 */
233
		prepare: function () {},
234
235
		/**
236
		 * @public
237
		 * @abstract
238
		 */
239
		submit: function () {},
240
241
		/**
242
		 * @protected
243
		 *
244
		 * @param {jQuery}              $element
245
		 * @param {string|number|Array} [value]
246
		 *
247
		 * @returns {jQuery|string|number|Array}
248
		 */
249
		_val: function ($element, value) {
250
			if (value !== undefined) {
251
				return $element.is(':input') ? $element.val(value) : $element.text(value);
252
			}
253
254
			return $element.is(':input') ? $element.val() : $element.text();
255
		}
256
	};
257
258
	/**
259
	 * @class
260
	 *
261
	 * @param {jQuery}   $template
262
	 * @param {object}   options
263
	 * @param {string}   options.title
264
	 * @param {object}   [options.templateData]
265
	 * @param {object[]} [options.buttons]
266
	 */
267
	OCA.CMSPico.Dialog = function ($template, options) {
268
		this.initialize($template, options);
269
	};
270
271
	/**
272
	 * @lends OCA.CMSPico.Dialog
273
	 */
274
	$.extend(OCA.CMSPico.Dialog, {
275
		/**
276
		 * @type {number}
277
		 * @constant
278
		 */
279
		BUTTON_ABORT: 1,
280
281
		/**
282
		 * @type {number}
283
		 * @constant
284
		 */
285
		BUTTON_SUBMIT: 2,
286
287
		/**
288
		 * @type {number}
289
		 * @protected
290
		 */
291
		dialogId: 0
292
	});
293
294
	/**
295
	 * @lends OCA.CMSPico.Dialog.prototype
296
	 */
297
	OCA.CMSPico.Dialog.prototype = {
298
		/** @member {jQuery} */
299
		$element: $(),
300
301
		/** @member {string} */
302
		dialogId: '',
303
304
		/** @member {jQuery} */
305
		$template: $(),
306
307
		/** @member {string} */
308
		title: '',
309
310
		/** @member {object} */
311
		templateData: {},
312
313
		/** @member {object[]} */
314
		buttons: [],
315
316
		/** @member {object} */
317
		events: {},
318
319
		/** @member {boolean} */
320
		opened: false,
321
322
		/**
323
		 * @constructs
324
		 *
325
		 * @param {jQuery}   $template
326
		 * @param {object}   options
327
		 * @param {string}   options.title
328
		 * @param {object}   [options.templateData]
329
		 * @param {object[]} [options.buttons]
330
		 */
331
		initialize: function ($template, options) {
332
			this.$template = $template;
333
334
			options = $.extend({
335
				title: '',
336
				templateData: {},
337
				buttons: []
338
			}, options);
339
340
			this.title = options.title;
341
			this.templateData = options.templateData;
342
			this.buttons = options.buttons;
343
			this.events = {};
344
345
			this.dialogId = 'picocms-dialog-' + ++OCA.CMSPico.Dialog.dialogId;
346
347
			var signature = 'OCA.CMSPico.Dialog.initialize()';
348
			if (!this.title) throw signature + ': No dialog title given';
349
		},
350
351
		/**
352
		 * @protected
353
		 *
354
		 * @returns {object[]}
355
		 */
356
		_getButtons: function () {
357
			var buttons = [],
358
				that = this;
359
360
			for (var i = 0; i < this.buttons.length; i++) {
361
				if (this.buttons[i].type !== undefined) {
362
					switch (this.buttons[i].type) {
363
						case OCA.CMSPico.Dialog.BUTTON_ABORT:
364
							buttons.push($.extend({
365
								text: t('cms_pico', 'Abort'),
366
								click: function (event) {
367
									that.close();
368
								}
369
							}, this.buttons[i]));
370
							break;
371
372
						case OCA.CMSPico.Dialog.BUTTON_SUBMIT:
373
							buttons.push($.extend({
374
								text: t('cms_pico', 'Save'),
375
								defaultButton: true,
376
								click: function (event) {
377
									that.submit();
378
									that.close();
379
								}
380
							}, this.buttons[i]));
381
							break;
382
					}
383
				} else {
384
					buttons.push(this.buttons[i]);
385
				}
386
			}
387
388
			return buttons;
389
		},
390
391
		/**
392
		 * @public
393
		 */
394
		open: function () {
395
			if (this.opened) {
396
				// nothing to do
397
				return;
398
			}
399
400
			this.$element = this.$template.octemplate($.extend({}, this.templateData, {
401
				id: this.dialogId,
402
				title: this.title
403
			}));
404
405
			$('#app-content').append(this.$element);
406
407
			var that = this;
408
			this.$element.ocdialog({
409
				buttons: this._getButtons(),
410
				close: function () {
411
					that.opened = false;
412
					that.close();
413
				}
414
			});
415
416
			this._trigger('open');
417
			this.opened = true;
418
		},
419
420
		/**
421
		 * @public
422
		 */
423
		submit: function () {
424
			this._trigger('submit');
425
		},
426
427
		/**
428
		 * @public
429
		 */
430
		close: function () {
431
			if (this.opened) {
432
				this.$element.ocdialog('close');
433
				return;
434
			}
435
436
			this._trigger('close');
437
		},
438
439
		/**
440
		 * @public
441
		 *
442
		 * @param {string}   eventName
443
		 * @param {function} callback
444
		 */
445
		on: function (eventName, callback) {
446
			var event = this._parseEventName(eventName);
447
			if (event === false) {
448
				$.error('Invalid event name: ' + eventName);
449
				return;
450
			}
451
452
			if (this.events[event[0]] === undefined) {
453
				this.events[event[0]] = {};
454
			}
455
456
			this.events[event[0]][event[1]] = callback;
457
		},
458
459
		/**
460
		 * @public
461
		 *
462
		 * @param {string} eventName
463
		 */
464
		off: function (eventName) {
465
			var event = this._parseEventName(eventName);
466
			if (event === false) {
467
				$.error('Invalid event name: ' + eventName);
468
				return false;
469
			}
470
471
			if ((this.events[event[0]] !== undefined) && (this.events[event[0]][event[1]] !== undefined)) {
472
				delete this.events[event[0]][event[1]];
473
				return true;
474
			}
475
476
			return false;
477
		},
478
479
		/**
480
		 * @protected
481
		 *
482
		 * @param {string} eventType
483
		 */
484
		_trigger: function (eventType) {
485
			if (!this.events[eventType]) {
486
				return;
487
			}
488
489
			var that = this;
490
			$.each(this.events[eventType], function (id, callback) {
491
				callback.apply(that);
492
			});
493
		},
494
495
		/**
496
		 * @protected
497
		 *
498
		 * @param {string} eventName
499
		 *
500
		 * @returns {[string, string]|false}
501
		 */
502
		_parseEventName: function (eventName) {
503
			var pos = eventName.indexOf('.');
504
			pos = (pos !== -1) ? pos : eventName.length;
505
506
			var type = eventName.substr(0, pos),
507
				id = eventName.substr(pos + 1);
508
509
			if (!type || !id) {
510
				return false;
511
			}
512
513
			return [ type, id ];
514
		}
515
	};
516
517
	/** @namespace OCA.CMSPico.Util */
518
	OCA.CMSPico.Util = {
519
		/**
520
		 * @param {string} string
521
		 *
522
		 * @returns string
523
		 */
524
		unescape: function (string) {
525
			return string
526
				.replace(/&amp;/g, '&')
527
				.replace(/&lt;/g, '<')
528
				.replace(/&gt;/g, '>')
529
				.replace(/&quot;/g, '"')
530
				.replace(/&#039;/g, "'");
531
		},
532
533
		/**
534
		 * @param {jQuery} $element
535
		 *
536
		 * @returns {object}
537
		 */
538
		serialize: function ($element) {
539
			var dataArray = $element.serializeArray(),
540
				dataObject = {};
541
542
			$element.find('input[type="button"]').each(function () {
543
				var $button = $(this);
544
				dataArray.push({ name: $button.prop('name'), value: $button.val() });
545
			});
546
547
			$.each(dataArray, function (_, data) {
548
				var key = data.name,
549
					matches = key.match(/^([a-z_][a-z0-9_]*)\[(\d*|[a-z0-9_]+)\]/i);
550
551
				if (matches === null) {
552
					dataObject[key] = data.value;
553
				} else {
554
					if (typeof dataObject[matches[1]] !== 'object') {
555
						dataObject[matches[1]] = {};
556
					}
557
558
					var result = dataObject[matches[1]],
559
						subKey = matches[2];
560
561
					key = key.substr(matches[0].length);
562
					matches = key.match(/^\[(\d*|[a-z0-9_]+)\]/i);
563
564
					while (matches !== null) {
565
						if (typeof result[matches[1]] !== 'object') {
566
							result[matches[1]] = {};
567
						}
568
569
						result = result[subKey];
570
						subKey = matches[1];
571
572
						key = key.substr(matches[0].length);
573
						matches = key.match(/^\[(\d*|[a-z0-9_]+)\]/i);
574
					}
575
576
					result[subKey] = data.value;
577
				}
578
			});
579
580
			return dataObject;
581
		}
582
	};
583
})(document, jQuery, OC, OCA);
584