“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”
— Martin Fowler
A while back I was tinkering with creating a universal Help infrastructure for my Service Portal widgets, and one of my example images used my My Delegates widget for the demonstration.
While the concept of delegation is an out-of-the-box feature, the widget is a custom component that I built to allow Service Portal users to manage their delegates. It’s really just a portalized version of the same functionality available inside the UI, but there was no way to do that within the Service Portal itself, so I threw together a simple widget to do so. Here is the HTML:
<snh-panel rect="rect" title="'${My Delegates}'">
<div style="width: 100%; padding: 5px 50px;">
<table class="table table-hover table-condensed">
<thead>
<tr>
<th style="text-align: center;">Delegate</th>
<th style="text-align: center;">Approvals</th>
<th style="text-align: center;">Assignments</th>
<th style="text-align: center;">CC notifications</th>
<th style="text-align: center;">Meeting invitations</th>
<th style="text-align: center;">Remove</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in c.data.listItems track by item.id | orderBy: 'delegate'" ng-hide="item.removed">
<td data-th="Delegate">
<sn-avatar class="avatar-small-medium" primary="item.id" show-presence="true"/>
{{item.delegate}}
</td>
<td data-th="Approvals" style="text-align: center;"><input type="checkbox" ng-model="item.approvals"/></td>
<td data-th="Assignments" style="text-align: center;"><input type="checkbox" ng-model="item.assignments"/></td>
<td data-th="CC notifications" style="text-align: center;"><input type="checkbox" ng-model="item.notifications"/></td>
<td data-th="Meeting invitations" style="text-align: center;"><input type="checkbox" ng-model="item.invitations"/></td>
<td data-th="Remove" style="text-align: center;"><img src="/images/delete_row.gif" ng-click="removePerson($index)" alt="Click here to remove this person as a delegate" title="Click here to remove this person from the list" style="cursor: pointer;"/></td>
</tr>
</tbody>
</table>
<p>To add a delegate to the list, select a person from below:</p>
<sn-record-picker id="snrp" field="data.personToAdd" ng-change="addSelected()" table="'sys_user'" display-field="'name'" display-fields="'title,department,location,email'" value-field="'sys_id'" search-fields="'name'" page-size="20"></sn-record-picker>
<br/>
<p>To remove a delegate from the list, click on the Remove icon.</p>
</div>
<div style="width: 100%; padding: 5px 50px; text-align: center;">
<button ng-click="saveDelegates()" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to save your changes">Save</button>
<button ng-click="returnToProfile()" class="btn ng-binding ng-scope" role="button" title="Click here to cancel your changes">Cancel</button>
</div>
</snh-panel>
Basically, it is just a table of delegates followed by an sn-record-picker from which you can choose additional people to add to the list. The source of the data is the same as that used by the internal delegate maintenance form, which you can see gathered up in server-side script here:
(function() {
data.userID = gs.getUser().getID();
if (input) {
data.listItems = input.listItems || fetchList();
if (input.personToAdd && input.personToAdd.value > '') {
addPersonToList(input.personToAdd.value);
}
if (input.button == 'save') {
saveList();
}
} else {
if (!data.listItems) {
data.listItems = fetchList();
}
}
function fetchList() {
var list = [];
var gr = new GlideRecord('sys_user_delegate');
gr.addQuery('user', data.userID);
gr.orderBy('delegate.name');
gr.query();
while (gr.next()) {
var thisDelegate = {};
thisDelegate.sys_id = gr.getValue('sys_id');
thisDelegate.id = gr.getValue('delegate');
thisDelegate.delegate = gr.getDisplayValue('delegate');
thisDelegate.approvals = (gr.getValue('approvals') == 1);
thisDelegate.assignments = (gr.getValue('assignments') == 1);
thisDelegate.notifications = (gr.getValue('notifications') == 1);
thisDelegate.invitations = (gr.getValue('invitations') == 1);
list.push(thisDelegate);
}
return list;
}
function saveList() {
for (var i=0; i<data.listItems.length; i++) {
var thisDelegate = data.listItems[i];
if (thisDelegate.removed) {
if (thisDelegate.sys_id != 'new') {
var gr = new GlideRecord('sys_user_delegate');
gr.get(thisDelegate.sys_id);
gr.deleteRecord();
}
} else {
var gr = new GlideRecord('sys_user_delegate');
if (thisDelegate.sys_id != 'new') {
gr.get(thisDelegate.sys_id);
} else {
gr.initialize();
gr.user = data.userID;
gr.delegate = thisDelegate.id;
gr.starts = new Date();
}
gr.approvals = thisDelegate.approvals;
gr.assignments = thisDelegate.assignments;
gr.notifications = thisDelegate.notifications;
gr.invitations = thisDelegate.invitations;
gr.update();
}
}
gs.addInfoMessage('Your Delegate information has been updated.');
}
function addPersonToList(selected) {
var existing = -1;
for (var i=0; i<data.listItems.length && existing == -1; i++) {
if (data.listItems[i].id == selected) {
existing = i;
}
}
if (existing == -1) {
var thisDelegate = {};
thisDelegate.sys_id = 'new';
thisDelegate.id = selected;
var gr = new GlideRecord('sys_user');
gr.get(selected);
thisDelegate.delegate = gr.getDisplayValue('name');
thisDelegate.approvals = true;
thisDelegate.assignments = true;
thisDelegate.notifications = true;
thisDelegate.invitations = true;
data.listItems.push(thisDelegate);
} else {
data.listItems[existing].removed = false;
}
input.personToAdd = {};
}
})();
All of the changes are held in the session until you decide to Save or Cancel, and if you elect to save, then things are updated on the database at that time. On the client side of things, we just have functions to add and remove people from the list, and to handle the two buttons:
function($scope, $window) {
var c = this;
$scope.addSelected = function() {
$scope.server.update().then(function(response) {
$('#snrp').select2("val","");
});
};
$scope.removePerson = function(i) {
c.data.listItems[i].removed = true;
};
$scope.saveDelegates = function() {
c.data.button = 'save';
$scope.server.update().then(function(response) {
reloadPage();
});
};
$scope.returnToProfile = function() {
reloadPage();
};
function reloadPage() {
$window.location.reload();
}
}
That’s the whole thing in all of its glory. If you want a copy of your own, here’s a little Update Set.
Update: There is an even better version here.