+ AJAX assignment [Feature 61]
gruffen

gruffen commited on 2011-03-20 11:17:32
Showing 6 changed files, with 282 additions and 9 deletions.

... ...
@@ -85,6 +85,22 @@ div.grid_header img
85 85
 	white-space: nowrap;
86 86
 }
87 87
 
88
+.shd_assignees
89
+{
90
+	background-color:inherit;
91
+	padding-left: 1em;
92
+}
93
+
94
+.shd_assignees:hover
95
+{
96
+	background-color: #bbbbbb;
97
+}
98
+
99
+.shd_assign_button, .shd_assignees
100
+{
101
+	cursor: pointer;
102
+}
103
+
88 104
 /**********************************
89 105
 * Stuff used within the actual tickets 	*
90 106
 ***********************************/
... ...
@@ -374,3 +374,103 @@ CustomFields.prototype.infoswap = function ()
374 374
 
375 375
 	this.bCollapsed = !this.bCollapsed;
376 376
 }
377
+
378
+/* AJAX assignment */
379
+function AjaxAssign(oOptions)
380
+{
381
+	this.opt = oOptions;
382
+	this.bCollapsed = true;
383
+
384
+	// Insert the expand/collapse button
385
+	var maincontainer = document.getElementById(this.opt.sId);
386
+	var listcontainer = document.getElementById(this.opt.sListId);
387
+	var newhtml = document.createElement('img');
388
+	newhtml.setAttribute('id', 'assign_' + this.opt.sSelf);
389
+	newhtml.setAttribute('class', 'shd_assign_button');
390
+	newhtml.setAttribute('src', this.opt.sImagesUrl + "/" + this.opt.sImageCollapsed);
391
+	newhtml.setAttribute('onclick', this.opt.sSelf + '.click()');
392
+	maincontainer.insertBefore(newhtml, listcontainer);
393
+}
394
+
395
+AjaxAssign.prototype.click = function ()
396
+{
397
+	if (this.bCollapsed)
398
+		this.expand();
399
+	else
400
+		this.collapse();
401
+}
402
+
403
+AjaxAssign.prototype.expand = function ()
404
+{
405
+	this.bCollapsed = false;
406
+	var img = document.getElementById('assign_' + this.opt.sSelf);
407
+	img.setAttribute('src', this.opt.sImagesUrl + "/" + this.opt.sImageExpanded);
408
+
409
+	// Fetch the list of items
410
+	ajax_indicator(true);
411
+	getXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=assign;ticket=' + this.opt.iTicketId + ';' + sSessV + '=' + sSessI, this.expand_callback);
412
+}
413
+
414
+AjaxAssign.prototype.expand_callback = function (XMLDoc)
415
+{
416
+	// Receive the list of assignees
417
+	ajax_indicator(false);
418
+	var errors = XMLDoc.getElementsByTagName('error');
419
+	if (errors.length > 0)
420
+	{
421
+		alert(errors[0].childNodes[0].nodeValue);
422
+		this.collapse();
423
+	}
424
+	else
425
+	{
426
+		var assign_list = document.getElementById(this.opt.sListId);
427
+		assign_list.innerHTML = '';
428
+		assign_list.setAttribute('style', 'display:block');
429
+	
430
+		var elements = XMLDoc.getElementsByTagName('member');
431
+		// We could, in all honesty, sit and build the content normally with document.createElement.
432
+		// But really, this is quicker, not just for us but for the browser too.
433
+		var newhtml = '';
434
+		for (var i = 0, n = elements.length; i < n; i++)
435
+		{
436
+			newhtml += '<li class="shd_assignees" onclick="' + this.opt.sSelf + '.assign(' + elements[i].getAttribute('uid') + ');">';
437
+			newhtml += elements[i].childNodes[0].nodeValue + '</li>';
438
+		}
439
+
440
+		assign_list.innerHTML = newhtml;
441
+	}
442
+}
443
+
444
+AjaxAssign.prototype.assign = function (uid)
445
+{
446
+	// Click handler for the assignment list, to issue the assign
447
+	ajax_indicator(true);
448
+	getXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=assign2;ticket=' + this.opt.iTicketId + ';to_user=' + uid + ';' + sSessV + '=' + sSessI, this.assign_callback);
449
+}
450
+
451
+AjaxAssign.prototype.assign_callback = function(XMLDoc)
452
+{
453
+	// Click handler callback for assignment, to handle once the request has been made
454
+	ajax_indicator(false);
455
+	this.collapse();
456
+	var errors = XMLDoc.getElementsByTagName('error');
457
+	if (errors.length > 0)
458
+		alert(errors[0].childNodes[0].nodeValue);
459
+	else
460
+	{
461
+		var elements = XMLDoc.getElementsByTagName('assigned');
462
+		document.getElementById(this.opt.sAssignedSpan).innerHTML = elements[0].childNodes[0].nodeValue;
463
+	}
464
+	this.collapse();
465
+}
466
+
467
+AjaxAssign.prototype.collapse = function ()
468
+{
469
+	this.bCollapsed = true;
470
+	var assign_list = document.getElementById(this.opt.sListId);
471
+	assign_list.innerHTML = '';
472
+	assign_list.setAttribute('style', 'display:none');
473
+
474
+	var img = document.getElementById('assign_' + this.opt.sSelf);
475
+	img.setAttribute('src', this.opt.sImagesUrl + "/" + this.opt.sImageCollapsed);
476
+}
377 477
\ No newline at end of file
... ...
@@ -61,6 +61,8 @@ function shd_ajax()
61 61
 		'privacy' => 'shd_ajax_privacy',
62 62
 		'urgency' => 'shd_ajax_urgency',
63 63
 		'quote' => 'shd_ajax_quote',
64
+		'assign' => 'shd_ajax_assign',
65
+		'assign2' => 'shd_ajax_assign2',
64 66
 	);
65 67
 
66 68
 	$context['ajax_return'] = array();
... ...
@@ -88,7 +90,7 @@ function shd_ajax()
88 90
 					$value = (array) $value;
89 91
 					foreach ($value as $thisvalue)
90 92
 						echo '
91
-	<', $key, '><![CDATA[', $thisvalue, ']]></', $key, '>';
93
+	<', $key, '><![CD', 'ATA[', $thisvalue, ']', ']></', $key, '>';
92 94
 				}
93 95
 			}
94 96
 		}
... ...
@@ -358,4 +360,132 @@ function shd_ajax_quote()
358 360
 
359 361
 	$context['ajax_raw'] = '<quote>' . $message . '</quote>';
360 362
 }
363
+
364
+/**
365
+ *	Returns the list of possible assignees for a ticket for AJAX assignment purposes.
366
+ *
367
+ *	Operations:
368
+ *	- Session check
369
+ * 	- Permissions check (that you can assign a ticket to someone else); if you can't assign a ticket to someone else, bail.
370
+ *	- Get the list of information for a ticket (which implicitly checks ticket access); if you can't see the ticket, bail.
371
+ *	- Get the list of who can be assigned a ticket.
372
+ *	- Return that via AJAX.
373
+*/
374
+function shd_ajax_assign()
375
+{
376
+	global $context, $smcFunc, $txt, $sourcedir, $user_profile;
377
+
378
+	checkSession('get');
379
+
380
+	if (!shd_allowed_to('shd_assign_ticket_any'))
381
+		return $context['ajax_return'] = array('error' => $txt['shd_cannot_assign']);
382
+
383
+	if (!empty($context['ticket_id']))
384
+	{
385
+		$query = shd_db_query('', '
386
+			SELECT hdt.private, hdt.id_member_started, id_member_assigned, 1 AS valid
387
+			FROM {db_prefix}helpdesk_tickets AS hdt
388
+			WHERE {query_see_ticket}
389
+				AND hdt.id_ticket = {int:ticket}',
390
+			array(
391
+				'ticket' => $context['ticket_id'],
392
+			)
393
+		);
394
+		if ($smcFunc['db_num_rows']($query) != 0)
395
+			list($private, $ticket_starter, $ticket_assigned, $valid) = $smcFunc['db_fetch_row']($query);
396
+		$smcFunc['db_free_result']($query);
397
+	}
398
+	if (empty($valid))
399
+		return $context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
400
+
401
+	require_once($sourcedir . '/sd_source/SimpleDesk-Assign.php');
402
+	$assignees = shd_get_possible_assignees($private, $ticket_starter);
403
+	array_unshift($assignees, 0); // add the unassigned option in at the start
404
+
405
+	if (empty($assignees))
406
+		return $context['ajax_return'] = array('error' => $txt['shd_no_staff_assign']);
407
+
408
+	// OK, so we have the general values we need. Let's get user names, and get ready to kick this back to the user. We'll build the XML here though.
409
+	loadMemberData($assignees);
410
+
411
+	$context['ajax_raw'] = '<response>';
412
+	foreach ($assignees as $assignee)
413
+		$context['ajax_raw'] .= '
414
+<member uid="' . $assignee . '"' . ($ticket_assigned == $assignee ? ' assigned="yes"' : '') . '><![CD' . 'ATA[' .(empty($assignee) ? '<span class="error">' . $txt['shd_unassigned'] . '</span>' : $user_profile[$assignee]['member_name']) . ']' . ']></member>';
415
+
416
+	$context['ajax_raw'] .= '
417
+</response>';
418
+}
419
+
420
+/**
421
+ *	Action a new assignment via AJAX.
422
+ *
423
+ *	Operations:
424
+ *	- Session check
425
+ * 	- Permissions check (that you can assign a ticket to someone else); if you can't assign a ticket to someone else, bail.
426
+ *	- Get the list of information for a ticket (which implicitly checks ticket access); if you can't see the ticket, bail.
427
+ *	- Get the list of who can be assigned a ticket; if requested user not on that list, bail.
428
+ *	- Update and build return status, and return via AJAX.
429
+ */
430
+
431
+function shd_ajax_assign2()
432
+{
433
+	global $context, $smcFunc, $txt, $sourcedir, $user_profile;
434
+
435
+	checkSession('get');
436
+
437
+	if (!shd_allowed_to('shd_assign_ticket_any'))
438
+		return $context['ajax_return'] = array('error' => $txt['shd_cannot_assign']);
439
+
440
+	if (!empty($context['ticket_id']))
441
+	{
442
+		$query = shd_db_query('', '
443
+			SELECT hdt.private, hdt.id_member_started, id_member_assigned, subject, 1 AS valid
444
+			FROM {db_prefix}helpdesk_tickets AS hdt
445
+			WHERE {query_see_ticket}
446
+				AND hdt.id_ticket = {int:ticket}',
447
+			array(
448
+				'ticket' => $context['ticket_id'],
449
+			)
450
+		);
451
+		if ($smcFunc['db_num_rows']($query) != 0)
452
+			list($private, $ticket_starter, $ticket_assigned, $subject, $valid) = $smcFunc['db_fetch_row']($query);
453
+		$smcFunc['db_free_result']($query);
454
+	}
455
+	if (empty($valid))
456
+		return $context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
457
+
458
+	if (!isset($_GET['to_user']) || !is_numeric($_GET['to_user']))
459
+		return $context['ajax_return'] = array('error' => $txt['shd_assigned_not_permitted'] . 'line459');
460
+
461
+	$_GET['to_user'] = isset($_GET['to_user']) ? (int) $_GET['to_user'] : 0;
462
+
463
+	require_once($sourcedir . '/sd_source/SimpleDesk-Assign.php');
464
+	$assignees = shd_get_possible_assignees($private, $ticket_starter);
465
+	array_unshift($assignees, 0); // add the unassigned option in at the start
466
+
467
+	if (!in_array($_GET['to_user'], $assignees))
468
+		return $context['ajax_return'] = array('error' => $txt['shd_assigned_not_permitted']);
469
+
470
+	if (!empty($_GET['to_user']))
471
+		loadMemberData($_GET['to_user']);
472
+
473
+	$user_name = shd_profile_link(empty($_GET['to_user']) ? '<span class="error">' . $txt['shd_unassigned'] . '</span>' : $user_profile[$_GET['to_user']]['member_name'], $_GET['to_user']);
474
+
475
+	// If it's being assigned to the current assignee, don't bother actually requesting the change.
476
+	if ($_GET['to_user'] != $ticket_assigned)
477
+	{
478
+		$log_params = array(
479
+			'subject' => $subject,
480
+			'ticket' => $context['ticket_id'],
481
+			'user_id' => $_GET['to_user'],
482
+			'user_name' => $user_name,
483
+		);
484
+		shd_log_action('assign', $log_params);
485
+		shd_commit_assignment($context['ticket_id'], $_GET['to_user'], true);
486
+	}
487
+
488
+	return $context['ajax_return'] = array('assigned' => $user_name);
489
+}
490
+
361 491
 ?>
362 492
\ No newline at end of file
... ...
@@ -275,7 +275,7 @@ function shd_assign2()
275 275
  *	@see shd_assign()
276 276
  *	@see shd_assign2()
277 277
 */
278
-function shd_commit_assignment($ticket, $assignment)
278
+function shd_commit_assignment($ticket, $assignment, $is_ajax = false)
279 279
 {
280 280
 	global $smcFunc, $sourcedir, $context, $modSettings;
281 281
 
... ...
@@ -297,6 +297,10 @@ function shd_commit_assignment($ticket, $assignment)
297 297
 	require_once($sourcedir . '/sd_source/SimpleDesk-Notifications.php');
298 298
 	shd_notifications_notify_assign($ticket, $assignment);
299 299
 
300
+	// If we're doing it AJAXively, we just want this to do the job and then go back to AJAX workflow.
301
+	if ($is_ajax)
302
+		return;
303
+
300 304
 	if (!empty($context['shd_return_to']) && $context['shd_return_to'] == 'home')
301 305
 		redirectexit('action=helpdesk;sa=main');
302 306
 	else
... ...
@@ -446,6 +446,7 @@ function shd_view_ticket()
446 446
 	// This is always going to be a pain. But it should be possible to contextualise it nicely.
447 447
 	// And while this isn't quite as nicely formatted as a single nice array definition,
448 448
 	// imagine trying to debug the display and text options later if it were done with nested ternaries... *shudder*
449
+	$context['ajax_assign'] = false;
449 450
 	$assign_nav = array(
450 451
 		'url' => $scripturl . '?action=helpdesk;sa=assign;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'],
451 452
 		'icon' => 'assign',
... ...
@@ -457,6 +458,7 @@ function shd_view_ticket()
457 458
 	{
458 459
 		$assign_nav['display'] = shd_allowed_to('shd_staff') && !$context['ticket']['closed'] && !$context['ticket']['deleted'];
459 460
 		$assign_nav['text'] = empty($context['ticket']['id_member_assigned']) ? 'shd_ticket_assign' : 'shd_ticket_reassign';
461
+		$context['ajax_assign'] = true;
460 462
 	}
461 463
 	elseif (shd_allowed_to('shd_assign_ticket_own'))
462 464
 	{
... ...
@@ -71,18 +71,22 @@ function template_viewticket()
71 71
 							<strong><img src="', $settings['default_images_url'], '/simpledesk/details.png" alt="" class="shd_smallicon shd_icon_minihead" /> ', $txt['shd_ticket_details'], '</strong>
72 72
 							<hr />
73 73
 							<ul>
74
-								<li><img src="', $settings['default_images_url'], '/simpledesk/id.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_id'], ': ', $context['ticket']['display_id'], '</li>
75
-								<li><img src="', $settings['default_images_url'], '/simpledesk/user.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_user'], ': ', $context['ticket']['member']['link'], '</li>
76
-								<li><img src="', $settings['default_images_url'], '/simpledesk/time.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_date'], ': ', $context['ticket']['poster_time'], '</li>
77
-								<li>
74
+								<li id="item_id"><img src="', $settings['default_images_url'], '/simpledesk/id.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_id'], ': ', $context['ticket']['display_id'], '</li>
75
+								<li id="item_userstarted"><img src="', $settings['default_images_url'], '/simpledesk/user.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_user'], ': ', $context['ticket']['member']['link'], '</li>
76
+								<li id="item_whenstarted"><img src="', $settings['default_images_url'], '/simpledesk/time.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_date'], ': ', $context['ticket']['poster_time'], '</li>
77
+								<li id="item_urgency">
78 78
 									<img src="', $settings['default_images_url'], '/simpledesk/urgency.png" alt="" class="shd_smallicon" />
79 79
 									', $txt['shd_ticket_urgency'], ': <span id="urgency">', $context['ticket']['urgency']['label'], '</span>
80 80
 										<span id="urgency_increase">', (!empty($context['ticket']['urgency']['increase']) ? '<a id="urglink_increase" href="' . $scripturl . '?action=helpdesk;sa=urgencychange;ticket=' . $context['ticket']['id'] . ';change=increase;' . $context['session_var'] . '=' . $context['session_id'] . '" title="' . $txt['shd_urgency_increase'] . '"><img src="' . $settings['images_url'] . '/sort_up.gif" width="9px" alt="' . $txt['shd_urgency_increase'] . '" /></a>' : ''), '</span>
81 81
 										<span id="urgency_decrease">', (!empty($context['ticket']['urgency']['decrease']) ? '<a id="urglink_decrease" href="' . $scripturl . '?action=helpdesk;sa=urgencychange;ticket=' . $context['ticket']['id'] . ';change=decrease;' . $context['session_var'] . '=' . $context['session_id'] . '" title="' . $txt['shd_urgency_decrease'] . '"><img src="' . $settings['images_url'] . '/sort_down.gif" width="9px" alt="' . $txt['shd_urgency_decrease'] . '" /></a>' : ''), '</span>
82 82
 								</li>
83
-								<li><img src="', $settings['default_images_url'], '/simpledesk/staff.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_assignedto'], ': ', $context['ticket']['assigned']['link'], '</li>
84
-								<li><img src="', $settings['default_images_url'], '/simpledesk/status.png" alt="" class="shd_smallicon"/> ', $txt['shd_ticket_status'], ': ', $context['ticket']['status']['label'], '</li>
85
-								<li><img src="', $settings['default_images_url'], '/simpledesk/replies.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_num_replies'], ': <a href="#replies">', (empty($context['ticket']['display_recycle']) ? $context['ticket']['num_replies'] : (int) $context['ticket']['num_replies'] + (int) $context['ticket']['deleted_replies']), '</a></li>';
83
+								<li id="item_assigned">
84
+									<img src="', $settings['default_images_url'], '/simpledesk/staff.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_assignedto'], ': <span id="assigned_to">', $context['ticket']['assigned']['link'], '</span>
85
+									<ul id="assigned_list" style="display:none;">
86
+									</ul>
87
+								</li>
88
+								<li id="item_status"><img src="', $settings['default_images_url'], '/simpledesk/status.png" alt="" class="shd_smallicon"/> ', $txt['shd_ticket_status'], ': ', $context['ticket']['status']['label'], '</li>
89
+								<li id="item_replies"><img src="', $settings['default_images_url'], '/simpledesk/replies.png" alt="" class="shd_smallicon" /> ', $txt['shd_ticket_num_replies'], ': <a href="#replies">', (empty($context['ticket']['display_recycle']) ? $context['ticket']['num_replies'] : (int) $context['ticket']['num_replies'] + (int) $context['ticket']['deleted_replies']), '</a></li>';
86 90
 
87 91
 				if (!empty($context['display_private']))
88 92
 					echo '
... ...
@@ -206,6 +210,23 @@ function template_viewticket()
206 210
 	echo '
207 211
 		</div><br class="clear" />';
208 212
 
213
+	// And lastly, the Javascript for AJAX assignment. Since this is onload stuff, it needs to know the HTML already exists.
214
+	if (!empty($context['ajax_assign']))
215
+		echo '
216
+	<script type="text/javascript"><!-- // --><![CDATA[
217
+	var oAjaxAssign = new AjaxAssign({
218
+		sSelf: "oAjaxAssign",
219
+		sScriptUrl: smf_scripturl,
220
+		iTicketId: ' . $context['ticket_id'] . ',
221
+		sId: "item_assigned",
222
+		sListId: "assigned_list",
223
+		sAssignedSpan: "assigned_to",
224
+		sImagesUrl: "' . $settings['images_url'] . '",
225
+		sImageCollapsed: "expand.gif",
226
+		sImageExpanded: "collapse.gif"
227
+	});
228
+	// ]' . ']></script>';
229
+
209 230
 }
210 231
 
211 232
 /**
212 233