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/backend.views.js   F

Complexity

Total Complexity 131
Complexity/F 1.66

Size

Lines of Code 875
Function Count 79

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
c 0
b 0
f 0
nc 32768
dl 0
loc 875
rs 2.1818
wmc 131
mnd 3
bc 129
fnc 79
bpm 1.6329
cpm 1.6582
noi 5

9 Functions

Rating   Name   Duplication   Size   Complexity  
B Symphony.View.add(ꞌ/publish/:context*:ꞌ) 0 102 1
A Symphony.View.add(ꞌ/blueprints/pages/:action:/:id:/:status:ꞌ) 0 3 1
A Symphony.View.add(ꞌ/blueprints/events/:action:/:name:/:status:/:*:ꞌ) 0 55 1
B Symphony.View.add(ꞌ/system/extensions/:context*:ꞌ) 0 29 1
B Symphony.View.add(ꞌ/:context*:ꞌ) 0 200 2
B Symphony.View.add(ꞌ/blueprints/sections/:action:/:id:/:status:ꞌ) 0 255 4
B Symphony.View.add(ꞌ/blueprints/datasources/:action:/:id:/:status:/:*:ꞌ) 0 145 2
A Symphony.View.add(ꞌ/:context*:/newꞌ) 0 3 1
B Symphony.View.add(ꞌ/system/authors/:action:/:id:/:status:ꞌ) 0 43 4

How to fix   Complexity   

Complexity

Complex classes like symphony/assets/js/src/backend.views.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
 * Symphony backend views
3
 *
4
 * @package assets
5
 */
6
7
(function($, Symphony) {
8
	'use strict';
9
10
/*--------------------------------------------------------------------------
11
	General backend view
12
--------------------------------------------------------------------------*/
13
14
Symphony.View.add('/:context*:', function() {
15
16
	// Initialise core plugins
17
	Symphony.Elements.contents.find('select.picker[data-interactive]').symphonyPickable();
18
	Symphony.Elements.contents.find('ul.orderable[data-interactive]').symphonyOrderable();
19
	Symphony.Elements.contents.find('table.selectable[data-interactive]').symphonySelectable();
20
	Symphony.Elements.wrapper.find('.filters-duplicator[data-interactive]').symphonyDuplicator();
21
	Symphony.Elements.wrapper.find('.tags[data-interactive]').symphonyTags();
22
	Symphony.Elements.wrapper.find('div.drawer').symphonyDrawer();
23
	Symphony.Elements.header.symphonyNotify();
24
25
	// Fix for Webkit browsers to initially show the options. #2127
26
	$('select[multiple=multiple]').scrollTop(0);
27
28
	// Initialise plugins inside duplicators
29
	Symphony.Elements.contents.find('.duplicator').on('constructshow.duplicator', '.instance', function() {
30
		// Enable tag lists inside duplicators
31
		$(this).find('.tags[data-interactive]').symphonyTags();
32
		// Enable parameter suggestions
33
		Symphony.Interface.Suggestions.init($(this), 'input[type="text"]');
34
	});
35
36
	// Navigation sizing
37
	Symphony.Elements.window.on('resize.admin nav.admin', function() {
38
		var content = Symphony.Elements.nav.find('ul.content'),
39
			structure = Symphony.Elements.nav.find('ul.structure'),
40
			width = content.width() + structure.width() + 20;
41
42
		// Compact mode
43
		if(width > window.innerWidth) {
44
			Symphony.Elements.nav.removeClass('wide');
45
		}
46
47
		// Wide mode
48
		else {
49
			Symphony.Elements.nav.addClass('wide');
50
		}
51
	}).trigger('nav.admin');
52
53
	// Accessible navigation
54
	Symphony.Elements.nav.on('focus.admin blur.admin', 'a', function() {
55
		$(this).parents('li').eq(1).toggleClass('current');
56
	});
57
58
	// Notifier sizing
59
	Symphony.Elements.window.on('resize.admin', function() {
60
		Symphony.Elements.header.find('.notifier').trigger('resize.notify');
61
	});
62
63
	// Table sizing
64
	Symphony.Elements.window.on('resize.admin table.admin', function() {
65
		var table = Symphony.Elements.contents.find('table:first');
66
67
		// Fix table size, if width exceeds the visibile viewport area.
68
		if(table.width() > Symphony.Elements.html.width()){
69
			table.addClass('fixed');
70
		}
71
		else {
72
			table.removeClass('fixed');
73
		}
74
	}).trigger('table.admin');
75
76
	// Orderable tables
77
	var oldSorting = null,
78
		orderable = Symphony.Elements.contents.find('table.orderable[data-interactive]');
79
80
	// Ignore tables with less than two rows
81
	orderable = orderable.filter(function() {
82
		return ($(this).find('tbody tr').length > 1);
83
	});
84
85
	// Initalise ordering
86
	orderable.symphonyOrderable({
87
			items: 'tr',
88
			handles: 'td'
89
		})
90
		.on('orderstart.orderable', function() {
91
			// Store current sort order
92
			oldSorting = orderable.find('input').map(function(e) { return this.name + '=' + (e + 1); }).get().join('&');
93
		})
94
		.on('orderstop.orderable', function() {
95
			var newSorting = orderable.find('input').map(function(e) { return this.name + '=' + (e + 1); }).get().join('&');
96
97
			// Store sort order, if changed
98
			if(oldSorting !== null && newSorting !== oldSorting) {
99
				// Update UI
100
				orderable.addClass('busy');
101
102
				// Update items
103
				orderable.trigger('orderupdate.admin');
104
105
				// Update old value
106
				oldSorting = newSorting;
107
108
				// Add XSRF token
109
				newSorting += '&' + Symphony.Utilities.getXSRF(true);
110
111
				// Send request
112
				$.ajax({
113
					type: 'POST',
114
					url: Symphony.Context.get('symphony') + '/ajax/reorder' + Symphony.Context.get('route'),
115
					data: newSorting,
116
					error: function() {
117
						Symphony.Message.post(Symphony.Language.get('Reordering was unsuccessful.'), 'error');
118
					},
119
					complete: function() {
120
						orderable.removeClass('busy').find('tr').removeClass('selected');
121
					}
122
				});
123
			}
124
		});
125
126
	// Suggest
127
	if(orderable.length) {
128
		Symphony.Elements.breadcrumbs.append('<p class="inactive"><span> – ' + Symphony.Language.get('drag to reorder') + '</span></p>');
129
	}
130
131
	// With Selected
132
	Symphony.Elements.contents.find('fieldset.apply').each(function() {
133
		var applicable = $(this),
134
			selection = Symphony.Elements.contents.find('table.selectable'),
135
			select = applicable.find('select'),
136
			button = applicable.find('button');
137
138
		// Set menu status
139
		if(selection.length > 0) {
140
			selection.on('select.selectable deselect.selectable check.selectable', 'tbody tr', function() {
141
142
				// Activate menu
143
				if(selection.has('.selected').length > 0) {
144
					applicable.removeClass('inactive');
145
					select.removeAttr('disabled');
146
				}
147
148
				// Deactivate menu
149
				else {
150
					applicable.addClass('inactive');
151
					select.attr('disabled', 'disabled');
152
				}
153
			});
154
155
			selection.find('tbody tr:first').trigger('check');
156
157
			// Respect menu state
158
			button.on('click.admin', function() {
159
				if(applicable.is('.inactive')) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if applicable.is(".inactive") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
160
					return false;
161
				}
162
			});
163
		}
164
	});
165
166
	// Confirm actions
167
	Symphony.Elements.contents.add(Symphony.Elements.context).on('click.admin', 'button.confirm', function() {
168
		var message = $(this).attr('data-message') || Symphony.Language.get('Are you sure you want to proceed?');
169
170
		return confirm(message);
171
	});
172
173
	// Confirm with selected actions
174
	Symphony.Elements.contents.find('> form').on('submit.admin', function() {
175
		var select = $('select[name="with-selected"]'),
176
			option = select.find('option:selected'),
177
			message = option.attr('data-message') ||  Symphony.Language.get('Are you sure you want to proceed?');
178
179
		// Needs confirmation
180
		if(option.is('.confirm')) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if option.is(".confirm") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
181
			return confirm(message);
182
		}
183
	});
184
185
	// Timestamp validation overwrite
186
	Symphony.Elements.header.find('.notifier .js-tv-overwrite').on('click.admin', function (e){
187
		var action = $(this).attr('data-action') || 'save';
188
		var hidden = Symphony.Elements.contents.find('input[name="action[ignore-timestamp]"]');
189
		hidden.prop('checked', true);
190
		e.preventDefault();
191
		e.stopPropagation();
192
		e.stopImmediatePropagation();
193
		// Click on the action button:
194
		// This is needed since we need to send the button's data in the POST
195
		Symphony.Elements.contents.find('> form').find('input, button')
196
			.filter('[name="action[' + action + ']"]').first().trigger('click');
197
		return false;
198
	});
199
200
	// Catch all JavaScript errors and write them to the Symphony Log
201
	Symphony.Elements.window.on('error.admin', function(event) {
202
		$.ajax({
203
			type: 'POST',
204
			url: Symphony.Context.get('symphony') + '/ajax/log/',
205
			data: {
206
				'error': event.originalEvent.message,
207
				'url': event.originalEvent.filename,
208
				'line': event.originalEvent.lineno,
209
				'xsrf': Symphony.Utilities.getXSRF()
210
			}
211
		});
212
	});
213
});
214
215
Symphony.View.add('/publish/:context*:', function() {
216
217
	// Filtering
218
	Symphony.Interface.Filtering.init();
219
220
	// Pagination
221
	Symphony.Elements.contents.find('.page').each(function() {
222
		var pagination = $(this),
223
			form = pagination.find('form'),
224
			jump = form.find('input'),
225
			active = jump.attr('data-active'),
226
			inactive = jump.attr('data-inactive'),
227
			helper = $('<span />').appendTo(form),
228
			width;
229
230
		// Measure placeholder text
231
		width = Math.max(helper.text(active).width(), helper.text(inactive).width());
232
		jump.width(width + 20);
233
		helper.remove();
234
235
		// Set current page
236
		jump.val(inactive);
237
238
		// Display "Go to page …" placeholder
239
		form.on('mouseover.admin', function() {
240
			if(!form.is('.active') && jump.val() == inactive) {
241
				jump.val(active);
242
			}
243
		});
244
245
		// Display current page placeholder
246
		form.on('mouseout.admin', function() {
247
			if(!form.is('.active') && jump.val() == active) {
248
				jump.val(inactive);
249
			}
250
		});
251
252
		// Edit page number
253
		jump.on('focus.admin', function() {
254
			if(jump.val() == active) {
255
				jump.val('');
256
			}
257
			form.addClass('active');
258
		});
259
260
		// Stop editing page number
261
		jump.on('blur.admin', function() {
262
263
			// Clear errors
264
			if(form.is('.invalid') || jump.val() === '') {
265
				form.removeClass('invalid');
266
				jump.val(inactive);
267
			}
268
269
			// Deactivate
270
			if(jump.val() == inactive) {
271
				form.removeClass('active');
272
			}
273
		});
274
275
		// Validate page number
276
		form.on('submit.admin', function() {
277
			if(parseInt(jump.val(), 10) > parseInt(jump.attr('data-max'), 10)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if parseInt(jump.val(), 10)...p.attr("data-max"), 10) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
278
				form.addClass('invalid');
279
				return false;
280
			}
281
		});
282
	});
283
284
	// Upload field destructors
285
	$('<em />', {
286
		text: Symphony.Language.get('Remove File'),
287
		on: {
288
			click: function(event) {
289
				event.preventDefault();
290
291
				var span = $(this).parent(),
292
					name = span.find('input').attr('name');
293
294
				span.empty().append('<input name="' + name + '" type="file">');
295
			}
296
		}
297
	}).appendTo('label.file:has(a) span.frame');
298
299
	// Calendars
300
	$('.field-date').each(function() {
301
		var field = $(this),
302
			datetime = Symphony.Context.get('datetime'),
303
			calendar;
304
305
		// Add calendar widget
306
		if(field.attr('data-interactive')) {
307
			calendar = new Symphony.Interface.Calendar();
308
			calendar.init(this);
309
		}
310
311
		// Add timezone offset information
312
		if(moment().utcOffset() !== datetime['timezone-offset']) {
313
			field.addClass('show-timezone');
314
		}
315
	});
316
});
317
318
Symphony.View.add('/:context*:/new', function() {
319
	Symphony.Elements.contents.find('input[type="text"], textarea').first().focus();
320
});
321
322
/*--------------------------------------------------------------------------
323
	Blueprints - Pages Editor
324
--------------------------------------------------------------------------*/
325
326
Symphony.View.add('/blueprints/pages/:action:/:id:/:status:', function() {
327
	// No core interactions yet
328
});
329
330
/*--------------------------------------------------------------------------
331
	Blueprints - Sections
332
--------------------------------------------------------------------------*/
333
334
Symphony.View.add('/blueprints/sections/:action:/:id:/:status:', function(action, id, status) {
335
	var duplicator = $('#fields-duplicator'),
336
		legend = $('#fields-legend'),
337
		expand, collapse, toggle;
338
339
	// Create toggle controls
340
	expand = $('<a />', {
341
		'class': 'expand',
342
		'text': Symphony.Language.get('Expand all')
343
	});
344
	collapse = $('<a />', {
345
		'class': 'collapse',
346
		'text': Symphony.Language.get('Collapse all')
347
	});
348
	toggle = $('<p />', {
349
		'class': 'help toggle'
350
	});
351
352
	// Add toggle controls
353
	toggle.append(expand).append('<br />').append(collapse).insertAfter(legend);
354
355
	// Toggle fields
356
	toggle.on('click.admin', 'a.expand, a.collapse', function toggleFields() {
357
358
		// Expand
359
		if($(this).is('.expand')) {
360
			duplicator.trigger('expandall.collapsible');
361
		}
362
363
		// Collapse
364
		else {
365
			duplicator.trigger('collapseall.collapsible');
366
		}
367
	});
368
369
	// Affix for toggle
370
	$('fieldset.settings > legend + .help').symphonyAffix();
371
372
	// Initialise field editor
373
	duplicator.symphonyDuplicator({
374
		orderable: true,
375
		collapsible: true,
376
		preselect: 'input'
377
	});
378
379
	// Load section list
380
	duplicator.on('constructshow.duplicator', '.instance', function() {
381
		var instance = $(this),
382
			sections = instance.find('.js-fetch-sections'),
383
			sectionsParent = sections.parent(),
384
			selected = [],
385
			options;
386
387
		if(sections.length) {
388
			options = sections.find('option').each(function() {
0 ignored issues
show
Unused Code introduced by
The variable options seems to be never used. Consider removing it.
Loading history...
389
				selected.push(this.value);
390
391
				if(!isNaN(this.value)) {
392
					$(this).remove();
393
				}
394
			});
395
396
			$.ajax({
397
				type: 'GET',
398
				dataType: 'json',
399
				url: Symphony.Context.get('symphony') + '/ajax/sections/',
400
				success: function(result) {
401
					// offline DOM manipulation
402
					sections.detach();
403
404
					if(result.sections.length) {
405
						sections.prop('disabled', false);
406
					}
407
					var options = $();
408
409
					if (!sections.attr('data-required')) {
410
						// Allow de-selection, if permitted
411
						options = options.add($('<option />', {
412
							text: Symphony.Language.get('None'),
413
							value: ''
414
						}));
415
					}
416
417
					// Append sections
418
					$.each(result.sections, function(index, section) {
419
						var optgroup = $('<optgroup />', {
420
							label: section.name
421
						});
422
						options = options.add(optgroup);
423
						// Append fields
424
						$.each(section.fields, function(index, field) {
425
							var option = $('<option />', {
426
								value: field.id,
427
								text: field.name
428
							}).appendTo(optgroup);
429
430
							if($.inArray(field.id, selected) > -1) {
431
								option.prop('selected', true);
432
							}
433
						});
434
					});
435
					sections.append(options);
436
					sectionsParent.append(sections);
437
					sections.trigger('change.admin');
438
				}
439
			});
440
		}
441
	});
442
	duplicator.find('.instance').trigger('constructshow.duplicator');
443
444
	// Focus first input
445
	duplicator.on('constructshow.duplicator expandstop.collapsible', '.instance', function() {
446
		var item = $(this);
447
		if (!item.hasClass('js-animate-all')) {
448
			$(this).find('input:visible:first').trigger('focus.admin');
449
		}
450
	});
451
452
	// Update name
453
	duplicator.on('blur.admin input.admin', '.instance input[name*="[label]"]', function() {
454
		var label = $(this),
455
			value = label.val();
456
457
		// Empty label
458
		if(value === '') {
459
			value = Symphony.Language.get('Untitled Field');
460
		}
461
462
		// Update title
463
		label.parents('.instance').find('.frame-header strong').text(value);
464
	});
465
466
	// Update location
467
	duplicator.on('change.admin', '.instance select[name*="[location]"]', function() {
468
		var select = $(this);
469
470
		select.parents('.instance').find('.frame-header').removeClass('main').removeClass('sidebar').addClass(select.val());
471
	});
472
473
	// Update requirements
474
	duplicator.on('change.admin', '.instance input[name*="[required]"]', function() {
475
		var checkbox = $(this),
476
			headline = checkbox.parents('.instance').find('.frame-header h4');
477
478
		// Is required
479
		if(checkbox.is(':checked')) {
480
			$('<span />', {
481
				class: 'required',
482
				text: '— ' + Symphony.Language.get('required')
483
			}).appendTo(headline);
484
		}
485
486
		// Is not required
487
		else {
488
			headline.find('.required').remove();
489
		}
490
	});
491
	duplicator.find('.instance input[name*="[required]"]').trigger('change.admin');
492
493
	// Update select field
494
	duplicator.on('change.admin', '.instance select[name*="[dynamic_options]"]', function() {
495
		$(this).parents('.instance').find('[data-condition=associative]').toggle($.isNumeric(this.value));
496
	}).trigger('change.admin');
497
498
	// Update tag field
499
	duplicator.on('change.admin', '.instance select[name*="[pre_populate_source]"]', function() {
500
		var selected = $(this).val(),
501
			show = false;
502
		
503
		if(selected) {
504
			selected = jQuery.grep(selected, function(value) {
505
				return value != 'existing';
506
			});
507
508
			show = (selected.length > 0);
509
		}
510
511
		$(this).parents('.instance').find('[data-condition=associative]').toggle(show);
512
	}).trigger('change.admin');
513
514
	// Remove field
515
	duplicator.on('destructstart.duplicator', function(event) {
516
		var target = $(event.target),
517
			item = target.clone(),
518
			title = item.find('.frame-header strong').text(),
519
			type = item.find('.frame-header span').text(),
520
			index = target.index(),
521
			id = new Date().getTime();
522
523
		// Offer undo option after removing a field
524
		Symphony.Elements.header.find('div.notifier').trigger('attach.notify', [
525
			Symphony.Language.get('The field “{$title}” ({$type}) has been removed.', {
526
				title: title,
527
				type: type
528
			}) + '<a id="' + id + '">' + Symphony.Language.get('Undo?') + '</a>', 'protected undo']
529
		);
530
531
		// Prepare field recovery
532
		$('#' + id).data('field', item).data('preceding', index - 1).on('click.admin', function() {
533
			var undo = $(this),
534
				message = undo.parent(),
535
				field = undo.data('field').hide(),
536
				list = $('#fields-duplicator');
537
538
			// Add field
539
			list.parent().removeClass('empty');
540
			field.trigger('constructstart.duplicator');
541
			list.find('.instance:eq(' + undo.data('preceding') + ')').after(field);
542
			field.trigger('constructshow.duplicator');
543
			field.slideDown('fast', function() {
544
				field.trigger('constructstop.duplicator');
545
			});
546
547
			// Clear system message
548
			message.trigger('detach.notify');
549
		});
550
	});
551
552
	// Discard undo options because the field context changed
553
	duplicator.on('orderstop.orderable', function() {
554
		Symphony.Elements.header.find('.undo').trigger('detach.notify');
555
	});
556
557
	// Highlight instances with the same location when ordering fields
558
	duplicator.on('orderstart.orderable', function(event, item) {
559
		var duplicator = $(this),
560
			header = item.find('.frame-header'),
561
			position = (header.is('.main') ? 'main' : 'sidebar');
562
563
		duplicator.find('li:has(.' + position + ')').not(item).addClass('highlight');
564
	});
565
566
	duplicator.on('orderstop.orderable', function() {
567
		$(this).find('li.highlight').removeClass('highlight');
568
	});
569
570
	// Restore collapsible states for new sections
571
	if(status === 'created') {
572
		var storageId = Symphony.Context.get('context-id');
573
		storageId = storageId.split('.');
574
		storageId.pop();
575
		storageId = 'symphony.collapsible.' + storageId.join('.') + '.0.collapsed';
576
577
		if(Symphony.Support.localStorage === true && window.localStorage[storageId]) {
578
			$.each(window.localStorage[storageId].split(','), function(index, value) {
579
				var collapsed = duplicator.find('.instance').eq(value);
580
				if(collapsed.has('.invalid').length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing collapsed.has(".invalid").length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
581
					collapsed.trigger('collapse.collapsible', [0]);
582
				}
583
			});
584
585
			window.localStorage.removeItem(storageId);
586
		}
587
	}
588
});
589
590
/*--------------------------------------------------------------------------
591
	Blueprints - Datasource Editor
592
--------------------------------------------------------------------------*/
593
594
Symphony.View.add('/blueprints/datasources/:action:/:id:/:status:/:*:', function(action) {
595
	if(!action) {
596
		return;
597
	}
598
599
	var context = $('#ds-context'),
600
		source = $('#ds-source'),
601
		name = Symphony.Elements.contents.find('input[name="fields[name]"]').attr('data-updated', 0),
602
		nameChangeCount = 0,
603
		params = Symphony.Elements.contents.find('select[name="fields[param][]"]'),
604
		pagination = Symphony.Elements.contents.find('.pagination'),
605
		paginationInput = pagination.find('input');
606
607
	// Update data source handle
608
	name.on('blur.admin input.admin', function updateDsHandle() {
609
		var current = (nameChangeCount++),
610
		value = name.val();
611
612
		setTimeout(function fetchDsHandle(nameChangeCount, current, value) {
613
			if(nameChangeCount == current) {
614
				$.ajax({
615
					type: 'GET',
616
					data: { 'string': value },
617
					dataType: 'json',
618
					url: Symphony.Context.get('symphony') + '/ajax/handle/',
619
					success: function(result) {
620
						if(nameChangeCount == current) {
621
							name.data('handle', result.handle);
622
							params.trigger('update.admin');
623
						}
624
					}
625
				});
626
			}
627
		}, 500, nameChangeCount, current, value);
628
	})
629
	// Enable the default value for Data Source name
630
	.symphonyDefaultValue({
631
		sourceElement: context
632
	});
633
634
	// Update output parameters
635
	params.on('update.admin', function updateDsParams() {
636
		var handle = name.data('handle') || Symphony.Language.get('untitled');
637
638
		// Process parameters
639
		if(parseInt(name.attr('data-updated')) !== 0) {
640
			params.find('option').each(function updateDsParam() {
641
				var param = $(this),
642
					field = param.attr('data-handle');
643
644
				// Set parameter
645
				param.text('$ds-' + handle + '.' + field);
646
			});
647
		}
648
649
		// Updated
650
		name.attr('data-updated', 1);
651
	}).trigger('update.admin');
652
653
	// Data source manager options
654
	Symphony.Elements.contents.find('.contextual select optgroup').each(function() {
655
		var optgroup = $(this),
656
			select = optgroup.parents('select'),
657
			label = optgroup.attr('data-label'),
658
			options = optgroup.remove().find('option').addClass('optgroup');
659
660
		// Show only relevant options based on context
661
		context.on('change.admin', function() {
662
			var option = $(this).find('option:selected'),
663
				context = option.attr('data-context') || 'section-' + option.val();
664
665
			if(context == label) {
666
				select.find('option.optgroup').remove();
667
				select.append(options.clone(true));
668
			}
669
		});
670
	});
671
672
	// Data source manager context
673
	context.on('change.admin', function() {
674
		var optgroup = context.find('option:selected').parent(),
675
			label = optgroup.attr('data-label') || optgroup.attr('label'),
676
			reference = context.find('option:selected').attr('data-context') || 'section-' + context.val(),
677
			components = Symphony.Elements.contents.find('.contextual');
678
679
		// Store context
680
		source.val(context.val());
681
682
		// Show only relevant interface components based on context
683
		components.addClass('irrelevant');
684
		components.filter('[data-context~=' + label + ']').removeClass('irrelevant');
685
		components.filter('[data-context~=' + reference + ']').removeClass('irrelevant');
686
687
		// Make sure parameter names are up-to-date
688
		Symphony.Elements.contents.find('input[name="fields[name]"]').trigger('blur.admin');
689
	}).trigger('change.admin');
690
691
	// Toggle pagination
692
	Symphony.Elements.contents.find('input[name*=paginate_results]').on('change.admin', function() {
693
		var disabled = !$(this).is(':checked');
694
		paginationInput.prop('disabled', disabled);
695
	}).trigger('change.admin');
696
697
	// Enabled fields on submit
698
	Symphony.Elements.contents.find('> form').on('submit.admin', function() {
699
		paginationInput.prop('disabled', false);
700
	});
701
702
	// Enable parameter suggestions
703
	Symphony.Elements.contents.find('.ds-param').each(function() {
704
		Symphony.Interface.Suggestions.init(this, 'input[type="text"]');
705
	});
706
707
	// Toggle filter help
708
	Symphony.Elements.contents.find('.filters-duplicator').on('input.admin change.admin', 'input', function toggleFilterHelp(event) {
709
		var item = $(event.target).parents('.instance'),
710
			value = event.target.value,
711
			filters = item.data('filters'),
712
			help = item.find('.help');
713
714
		// Handle values that don't contain predicates
715
		var filter = value.search(/:/)
716
			? $.trim(value.split(':')[0])
717
			: $.trim(value);
718
719
		// Store filters
720
		if(!filters) {
721
			filters = {};
722
			item.find('.tags li').each(function() {
723
				var val = $.trim(this.getAttribute('data-value'));
724
				if (val.search(/:/)) {
725
					val = val.slice(0, -1);
726
				}
727
				filters[val] = this.getAttribute('data-help');
728
			});
729
730
			item.data('filters', filters);
731
		}
732
733
		// Filter help
734
		if(filters[filter]) {
735
			help.html(filters[filter]);
736
		}
737
	});
738
});
739
740
/*--------------------------------------------------------------------------
741
	Blueprints - Event Editor
742
--------------------------------------------------------------------------*/
743
744
Symphony.View.add('/blueprints/events/:action:/:name:/:status:/:*:', function() {
745
	var context = $('#event-context'),
746
		source = $('#event-source'),
747
		filters = $('#event-filters'),
748
		name = Symphony.Elements.contents.find('input[name="fields[name]"]').attr('data-updated', 0),
749
		nameChangeCount = 0;
750
751
	// Update event handle
752
	name.on('blur.admin input.admin', function updateEventHandle() {
753
		var current = (nameChangeCount++);
754
755
		setTimeout(function checkEventHandle(nameChangeCount, current) {
756
			if(nameChangeCount == current) {
757
				Symphony.Elements.contents.trigger('update.admin');
758
			}
759
		}, 500, nameChangeCount, current);
760
	})
761
	// Enable the default value for Event name
762
	.symphonyDefaultValue({
763
		sourceElement: context
764
	});
765
766
	// Change context
767
	context.on('change.admin', function changeEventContext() {
768
		source.val(context.val());
769
		Symphony.Elements.contents.trigger('update.admin');
770
	}).trigger('change.admin');
771
772
	// Change filters
773
	filters.on('change.admin', function changeEventFilters() {
774
		Symphony.Elements.contents.trigger('update.admin');
775
	});
776
777
	// Update documentation
778
	Symphony.Elements.contents.on('update.admin', function updateEventDocumentation() {
779
		if(name.val() == '') {
780
			$('#event-documentation').empty();
781
		}
782
		else {
783
			$.ajax({
784
				type: 'GET',
785
				data: {
786
					'section': context.val(),
787
					'filters': filters.serializeArray(),
788
					'name': name.val()
789
				},
790
				dataType: 'html',
791
				url: Symphony.Context.get('symphony') + '/ajax/eventdocumentation/',
792
				success: function(documentation) {
793
					$('#event-documentation').replaceWith(documentation);
794
				}
795
			});
796
		}
797
	});
798
});
799
800
/*--------------------------------------------------------------------------
801
	System - Authors
802
--------------------------------------------------------------------------*/
803
804
Symphony.View.add('/system/authors/:action:/:id:/:status:', function(action, id, status) {
805
	var password = $('#password');
806
807
	// Add change password overlay
808
	if(!password.has('.invalid').length && id) {
809
		var overlay = $('<div class="password" />'),
810
			frame = $('<span class="frame centered" />'),
811
			button = $('<button />', {
812
				text: Symphony.Language.get('Change Password'),
813
				on: {
814
					click: function(event) {
815
						event.preventDefault();
816
						overlay.hide();
817
					}
818
				}
819
			}).attr('type', 'button');
820
821
		frame.append(button);
822
		overlay.append(frame);
823
		overlay.insertBefore(password);
824
	}
825
826
	// Focussed UI for password reset
827
	if(status == 'reset-password') {
828
		var fieldsets = Symphony.Elements.contents.find('fieldset'),
829
			essentials = fieldsets.eq(0),
830
			login = fieldsets.eq(1),
831
			legend = login.find('> legend');
832
833
		essentials.hide();
834
		login.children().not('legend, #password').hide();
835
836
		$('<p />', {
837
			class: 'help',
838
			text: Symphony.Language.get('Please reset your password')
839
		}).insertAfter(legend);
840
	}
841
842
	// Highlight confirmation promt
843
	Symphony.Elements.contents.find('input, select').on('change.admin input.admin', function() {
844
		$('#confirmation').addClass('highlight');
845
	});
846
});
847
848
/*--------------------------------------------------------------------------
849
	System - Extensions
850
--------------------------------------------------------------------------*/
851
Symphony.View.add('/system/extensions/:context*:', function() {
852
	Symphony.Language.add({
853
		'Enable': false,
854
		'Install': false,
855
		'Update': false
856
	});
857
858
	// Update controls contextually
859
	Symphony.Elements.contents.find('.actions select').on('focus.admin', function() {
860
		var selected = Symphony.Elements.contents.find('tr.selected'),
861
			canUpdate = selected.filter('.extension-can-update').length,
862
			canInstall = selected.filter('.extension-can-install').length,
863
			canEnable = selected.length - canUpdate - canInstall,
864
			control = Symphony.Elements.contents.find('.actions option[value="enable"]'),
865
			label = [];
866
867
		if(canEnable) {
868
			label.push(Symphony.Language.get('Enable'));
869
		}
870
		if(canUpdate) {
871
			label.push(Symphony.Language.get('Update'));
872
		}
873
		if(canInstall) {
874
			label.push(Symphony.Language.get('Install'));
875
		}
876
877
		control.text(label.join('/'));
878
	});
879
});
880
881
})(window.jQuery, window.Symphony);
882