“Baby steps count, as long as you are going forward. You add them all up, and one day you look back and you’ll be surprised at where you might get to.” — Chris Gardner
Version 2.5 is essentially the exact same bundle as the previous version (2.4.1), with the only change being the inclusion of the corrected configuration editor. Still, it does address the issues related to scoped configuration scripts, so it’s probably worth pulling down and installing it, just to avoid running into those annoying problems one day in the future. There are no new features or components in this new version, but it does now include the latest of everything, so this is the one that you will want.
“Beginning in itself has no value; it is an end which makes beginning meaningful; we must end what we begun.” — Amit Kalantri
Last time, we added the Requested Item table to our Service Account dashboard so that we could see the pending requests, but we left off with a field name error and the desire to add a few item variables to the table using some Scripted Value Columns. Today, we will fix up that little error, and add some columns to both tables, hopefully wrapping things up, at least for this version of the dashboard.
In our field list for the new table, we had included the field name opened, when in actuality, the correct field name for the opened date/time is opened_at. That’s an easy fix, and now our field list looks like this:
number,opened_at,request.requested_for,stage
While we are in the configuration updating field lists, let’s also add the new link to the original request to the field list for the Service Account table, which will now look like this:
Also, since that new column will be a link to the sc_req_item table, let’s map that table to the ticket page by adding a new entry to the reference map.
That should take care of the errors and oversights. Now let’s take a look at adding some item variables to the pending request view. We put some catalog item variables on an example table not too long ago, so let’s just follow that same approach and maybe steal a little code from that guy so that we don’t end up reinventing an existing wheel. Here is the script that we built for that exercise.
var ScriptedCatalogValueProvider = Class.create();
ScriptedCatalogValueProvider.prototype = {
initialize: function() {
},
questionMap: {
cpu: 'e46305fbc0a8010a01f7d51642fd6737',
memory: 'e463064ac0a8010a01f7d516207cd5ab',
drive: 'e4630669c0a8010a01f7d51690673603',
os: 'e4630688c0a8010a01f7d516f68c1504'
},
getScriptedValue: function(item, config) {
var response = '';
var column = config.name;
if (this.questionMap[column]) {
response = this.getVariableValue(this.questionMap[column], item.sys_id);
}
return response;
},
getVariableValue: function(questionId, itemId) {
var response = '';
var mtomGR = new GlideRecord('sc_item_option_mtom');
mtomGR.addQuery('request_item', itemId);
mtomGR.addQuery('sc_item_option.item_option_new', questionId);
mtomGR.query();
if (mtomGR.next()) {
var value = mtomGR.getDisplayValue('sc_item_option.value');
if (value) {
response = this.getDisplayValue(questionId, value);
}
}
return response;
},
getDisplayValue: function(questionId, value) {
var response = '';
var choiceGR = new GlideRecord('question_choice');
choiceGR.addQuery('question', questionId);
choiceGR.addQuery('value', value);
choiceGR.query();
if (choiceGR.next()) {
response = choiceGR.getDisplayValue('text');
}
return response;
},
type: 'ScriptedCatalogValueProvider'
};
We can make a copy of this script and call ours ServiceAccountDashboardValueProvider. Most of this appears to be salvageable, but we will want to build our own questionMap using the columns that we will want to use for our use case. To find the sys_ids for the variables that we will want to use, we can pull up the Catalog Item to get to the list of variables, and then pull up each variable and use the context menu to snag the sys_id for each one.
Once we gather up all of the sys_ids, we will have a new map that looks like this:
That should be enough to make things work; however, in our case the types of variables involved will return the display value directly, so we do not need to go through that secondary process to look up the display value from the value. We can simply delete that unneeded function and return the value directly in this instance. That will make our new script look like this:
var ServiceAccountDashboardValueProvider = Class.create();
ServiceAccountDashboardValueProvider.prototype = {
initialize: function() {
},
questionMap: {
account_id: '59fe77a4971311100362bfb6f053afcc',
type: 'f98b24a4971711100362bfb6f053afa0',
group: '3d4fbba4971311100362bfb6f053afe3'
},
getScriptedValue: function(item, config) {
var response = '';
var column = config.name;
if (this.questionMap[column]) {
response = this.getVariableValue(this.questionMap[column], item.sys_id);
}
return response;
},
getVariableValue: function(questionId, itemId) {
var response = '';
var mtomGR = new GlideRecord('sc_item_option_mtom');
mtomGR.addQuery('request_item', itemId);
mtomGR.addQuery('sc_item_option.item_option_new', questionId);
mtomGR.query();
if (mtomGR.next()) {
response = mtomGR.getDisplayValue('sc_item_option.value');
}
return response;
},
type: 'ServiceAccountDashboardValueProvider'
};
Now all we need to do is to pull up the dashboard under the new configuration and see how it all looks. First, let’s take a look at the new column that we added for the original request.
There is only data there for the most recent test, but that’s just because that field did not exist on the table until recently. Now let’s click on the Pending state and see how our item variables came out.
Very nice! OK, I think that about does it for this version of the sample dashboard. There is still some work that we could do on the Fulfiller perspective, and it might be nice to add an Admin perspective that showed everything, but since this is just an example of what might be done, I will leave that as an exercise for those who might want to play around with things a bit. Next time, let’s take a look at what now have up to this point, and at what there might be left to do before we can wrap this one up and call it done.
“There are no big problems, there are just a lot of little problems.” — Henry Ford
Last time, we wrapped up the initial table configuration for our Service Account dashboard and tested everything out to make sure that it all worked as intended. We also identified the fact that we need to add a second table to the configuration so that we can see the pending requests that have not yet created a record in the Service Account table.Before we do that, though, I decided that it would be useful to add a link to the original request on the Service Account record so that you could easily pull up the request from the account record.
To populate the field during the creation of the Service Account record, I pulled the Service Account Request Fulfillment flow up in the App Engine Studio and added an entry to drag in the data pill from the original request in the trigger.
With that out of the way, we can return our attention to adding the new table to the dashboard configuration. To do that, we go back to the Content Selector Configuration Editor that we recently updated to correct a few issues related to Scoped Applications. Before we do that, though, let’s pull up the list of Requested Items and build ourselves a filter that we can use to show all of the open items for Service Accounts requested by the current operator.
We are looking for active items requesting the Service Account catalog item requested by the currently logged on user. The filter can be found in the URL under the sysparm_query parameter.
Of course, we have to do a few change alls to get rid of all of the double encoding present in this version, but once we do that we will have a workable filter for the pending state on our newly added table.
Now let’s jump into the editor and add our new table.
For now, let’s assume that we don’t want anything to appear in the Active and Retired states, and we can use the same technique that we used on the original table when we didn’t want anything to appear for that table in the Pending state. We’ll set the field list to simply number, and set the filter to number=0.
For the Pending state, we add a few more relevant fields and use the filter we snagged from the list URL earlier.
We can do a little more with this, but let’s save what we have for now and take it out for a spin, just to make sure that everything is still in working order. Saving the changes should take us to the generated script, which now looks like this.
Now all we need to do is to pull up the dashboard with the modified configuration, click on the Pending state, and take a quick peek.
Well, that’s not too bad. Looks like we screwed up on the field name for the open date, but other than that, things look pretty good. I want to add a few more columns from the catalog item variables anyway, which we can do by configuring some Scripted Value Columns, so let’s fix our little error and deal with those new fields in our next installment.
“On your darkest days do not try to see the end of the tunnel by looking far ahead. Focus only on where you are right now. Then carefully take one step at a time, by placing just one foot in front of the other. Before you know it, you will turn that corner.” — Anthon St. Maarten
Last time, we threw together the beginnings of a configuration script for Service Account dashboard using the Content Selector Configuration Editor. Now that we have a viable script, we need to create Service PortalPage that will utilize that configuration. To begin, we will pull up the list of Portal Pages and click on the New button to create a new page.
We will call our new page Service Account Dashboard and give it an ID of service_account_dashboard. Once we submit the form we can pull it back up and use the link down at the bottom of the form to bring it up in Service Portal Designer. Onto the blank canvas we will drag a 12-wide container, and beneath that one, we will drag in a 3/9 container. Into the upper 12-wide container, we will drag in the Dynamic Service Portal Breadcrumbs widget, and into the 3 portion of the 3/9 container, we will drag in the Content Selector widget. In the 9 portion of the 3/9 container, we will pull in the SNH Data Table from URL Definition widget. Now that we have placed all of the widgets, we will need to edit them, starting with the Content Selector.
Here is where we enter the full name of the configuration script that we created last time. Since this is a Scoped application, we need to include the scope with the name so that it can be successfully located. That’s all there is to configuring that widget, as most of the configuration information is contained in the referenced script. Configuring the Data Table widget is a little more involved.
Here we give it a title of Service Accounts and select an appropriate Glyph image. We check the Use Instance Title checkbox to get our title to show up, and we leave all of the rest of them unchecked. Once we save that and save the page, we should be ready to try it out, which we can do easily enough with the View page in new tab button up in the upper right-hand corner.
So far, so good. The default selection is active Service Accounts from the requester’s perspective, and you can see all of the account records from our failed and successful test submissions. I went ahead and retired one of them so that we could test the Retired state. Let’s click on the Retired button and see how that comes out.
That looks good as well. Now let’s try the Pending state, which should come up empty for the Service Account table, as pending requests have not gotten far enough along in the process to have created the record in that table yet.
Well, that’s not right! But you knew things were going too well at this point and it was about time for something to go horribly wrong. This is just a problem with our Filter, though, and should be easily remedied. We used the filter 1=0, which obviously did not work, so let’s try using an actual field from the table and do something like this in our config file:
filter: 'number=0',
Before we add that to all of the pending configurations, let’s pull up the dashboard again and see how that looks.
That’s better. Of course, to actually see the pending Service Accounts, we will need to add another table to our configuration. We can go back into the Content Selector Configuration Editor to do that, and then go back to the dashboard and check it out. That sounds like a good exercise for our next installment.
Last time, we were about to throw together a little dashboard of Service Account information when we ran into a little problem with the Content Selector Configuration Editor. Actually, it turned out to be a problem with the snh-form-field tag, but now that we have taken the time to fix that, we should be able to get back to where we were and continue on. So let’s get back into the configurator tool and try one more time to create a new configuration script.
Well, that’s much better! Now we can see all of the fields again in the modal pop-up as well as both of the buttons, so things are back to normal with the newer version. After creating the Requester perspective, we go through the process again to create the Fulfiller perspective.
Now, we could have used slightly different names, such as Owner and Provider, but again, this is just a sample of what could be; your mileage may vary. One thing that we did do on the Fulfiller perspective, though, was to add the itil role so that only actual fulfillers would have access to that portion of the dashboard.
Next, we need to add some states, and for our purpose, the states of Active, Retired, and Pending should suffice.
With that out of the way, now we can start completing the Tables section. Clicking on the Add a new Table button in the Requester tab will bring up the modal Table Selector pop-up.
Once the Table has been added, we can fill in all of the rest of the configuration data.
For the Active state, we will use following fields:
For the Retired tab, we will just change the above filter from active=true to active=false. Everything else can remain the same. For accounts in the pending state, there will be no record on the Service Account table just yet, so we can just set the filter to 1=0, which should always find no records. To see the pending accounts, we will need to add another table. We can deal with that later, though, so for now let’s just focus on the Service Account table and then see how it all comes out.
Basically, we go through pretty much the same process for the Fulfiller tab, and once we save all of our input, we end up with the following configuration script.
Now all we need to do is to create a Portal Page that will use the configuration script and we can take it out for a spin. That sounds like a good project for our next installment.
“We learn from failure, not from success!” — Bram Stoker
A while back I was working on my Collaboration Store project when I discovered a problem with the SNH Form Fields when running on my Tokyo instance. At the time, I was not able to diagnose the source of the problem, but I did manage to come up with a work-around, which I implemented on the page that I was developing at the time. What I did not do was to go back and refactor all of the other widgets that utilize the snh-form-field tag to implement the work-around on those as well, nor did I invest any time in actually hunting down the source of the actual problem with the tag, correcting it, and producing a new version.
Recently, I was working on my little Service Account Management app, and was rudely reminded of this unfortunate oversight. Initially, I thought that there was something wrong with my modal pop-up box, but after further review I realized this was the same snh-form-field issue that I had run into earlier on the other project. Clearly, it was long since time to address it.
To implement the work-around, I brought up a list of all of the Service Portalwidgets that contained the text ‘snh-form-field’ in the Body HTML template property. Then one by one, I pulled them up in the editor, searched for the tag, and then wrapped a SPAN around each one, mitigating the problem. For example, here is the original HTML for the Aggregate Column Editor widget:
It was not difficult work, but it was rather tedious. Eventually, I got through the entire list. Then I put together a new Update Set for the SNH Data Table Widgets and posted the new version (2.4) out on Share. Unfortunately, it wasn’t until I had already posted it out there that I realized that I had left out a critical widget in the build, so I had to build the Update Set a second time. It did not look like there was any way to replace the Update Set on Share for the 2.4 version, so I called the corrected Update Set 2.4.1. But that is not a legal version name on that site, so on Share, that version is known as 2.41. Anyway, it’s out there now, so if you are running, or planning to run, on Tokyo or Utah, you should definitely go out to Share and pull down the latest Update Set. But stay away from version 2.4, because that was just an error, and shouldn’t even be out there.
Oh, and if you run into any issues with the 2.4.1 version, please provide some details in the discussion section on Share, or in the comments below. Thanks!
“Inside of every problem lies an opportunity.” — Robert Kiposaki
Last time, we wrapped up all of the work on the Service Account requisition process, although we left a number of things that could have improved the process to some mythical future effort. Unlike the SNH Form Fields or SNH Data Table Widgets, which were intended to be used as is, without the need to touch any of the provided code, the Service Account Management app is more of a sample of what could be done, with the understanding that implementers would want to craft their own account types, notice templates, and fulfillment Flows based on their unique requirements. Since it is just an example, and more of a concept than a product, we don’t need to solve every issue or build out every conceived improvement. We created enough pieces to demonstrate that it works, and that pretty much addresses the intent of the project.
So what’s left? One thing that we will want to do once a Service Account has been delivered will be to check back every so often and ensure that it is still needed. This is actually a common practice for a number IT artifacts, and the applications for such a process go well beyond the realm of Service Accounts. Periodically checking to see if a thing is still needed is something that could be applied to servers deployed for a development project, or access granted to vendors or contractors, or laptops issued to temporary staff, or communications links established with outside entities, or any number of other items that are deployed, granted, or procured for a limited purpose. That actually could be a stand-alone product that one could use out of the box, and one that could potentially have quite a few applications in an IT organization. In fact, for our little exercise here, let us assume that such a product exists, and we won’t bother to build out that capability here. However, one day we might tackle the development of such a product, and if we do, we can then use our Service Account Management app as one example of how such a product might be employed. But that’s not today’s worry.
So, again, what is left for us to do. So far, we have created our Scoped Application, built out all of our database tables, and created a process through which someone can request a new Service Account. If we assume that the periodic process though which these accounts will be reviewed will be handled by a separate product, then what is left for our product?
One thing that would be useful would be series of table views from both the requester’s perspective and the fulfiller’s perspective. Each might want to see a list of active accounts, retired accounts, and pending accounts. This is something for which the SNH Data Table Widgets were designed, so let’s see if we can use that product to produce a single dashboard that would contain all of these interrelated lists.
To begin, we will select Content Selector Configurator from the Tools menu (which is only there because we have previously installed SNH Data Table Widgets).
On the initial screen of the configurator, we will want to click on the Create a new Content Selector Configurator button.
We will call our new script ServiceAccountDashboardConfig and use the Add a new Perspective button to create two perspectives, Requester and Fulfiller. We may want to add a third at some point for an Admin view, but for now, let’s just focus on the two primary user perspectives. Once we click on the Add a new Perspective button, a modal dialog pops up where we can enter the details of our new perspective.
Well, that’s not right! The modal pop-up box is supposed to have several input fields and it is only tall enough to show the first one. There doesn’t seem to be any way to drag down the bottom of the box to reveal the rest of the form, either, so you can’t even get to the buttons that should be down at the bottom. This is probably another Tokyo thing that I am going to have to resolve, so it looks like it is time to set this project aside and dig into this little issue.
OK, well, I will see if I can’t figure out what is going on here, and once I do, we will get back to this in a future installment. Hopefully, this will be a quick break!
“Whenever you have taken up work in hand, you must see it to the finish. That is the ultimate secret of success. Never, never, never give up!” — Dada Vaswani
Last time, we built a companion widget to handle icons and bulk actions. Today we will do something a little different and build a companion widget to handle aggregate columns. Usually, clicking on a aggregate column will bring you to a list of the records represented by the value. If the value was 10, then you would expect to see a list of 10 records, either on a new page or in a modal pop-up. Linking to a new page is already built into the aggregate column specification, so our companion widget example will demonstrate the modal alternative. For our example, we will use catalog requests, with the aggregate column representing the number of requested items in each request.
Here is the configuration script used to produce this table:
Without a page_id property in the aggregate column specification, it will not automatically link to another page, but it will broadcast the click event, so we can use a version of the Simple List widget to display our records.
Clicking on an item should bring up the ticket page, where you can see more details about the item.
The reason we need to use a version of the Simple List widget is that the Simple List widget is configured using widget options, and the spModalopen() function does not provide the capability of setting the widget’s options. It does, however, provide the capability to configure widget input, so all we need to do is to add these few lines to the top of our version’s Server script to convert that input to options.
// begin mod
if (input && input.options) {
for (var name in input.options) {
options[name] = input.options[name];
}
}
// end mod
With that in place, we can now build a typical companion widget that listens for the aggregate click event and pops open our modal dialog using the modified Simple List widget.
And that’s all there is to that. That gives us three different examples covering buttons, icons, bulk actions, and now aggregate columns, so that should cover just about everything. Once you do a few and get the hang of how all of the parts play together, it’s pretty simple to create a new one for a different purpose. For those of you who like to play along at home, here is an Update Set that includes all of the parts and pieces for these various examples. Of course, you will need to install the SNH Data Table Widgets for any of this to be of any value, but you already knew that!
“If you set a good example you need not worry about setting rules.” — Lee Iacocca
Last time, we put together a simple companion widget for an SNH Data Table that contained a single button. Today we will try something a little more complicated, with three different buttons and three different bulk actions. For this example, we will create a list of records waiting for an approval decision from the perspective of the person who can make such a decision. All of our buttons and bulk actions will be related to passing judgement on the items listed.
Here is the configuration script that we will used to produce this table.
The buttons handle actions for individual items and the bulk actions perform the same functions, but for more than one item at a time. Clicking on a button that involves comments should bring up an input dialog.
Once the comments are entered, the companion widget should go ahead and update the database and inform the user of the action taken.
Once the list is refreshed to reflect the changes, we can demonstrate a similar process for bulk actions.
Once again, a dialog appears so that the rejection reason can be entered.
And once the comments have been entered, the action is taken for all selected records.
And once the action has been taken, the screen is again refreshed to reveal that there are no further items requiring approval decisions.
So that’s the concept. Now let’s take a look the companion widget that makes this all work. To begin, we throw in the same list of event names at the top like we do with all of the others.
We have to deal with both buttons and bulk actions for this one, but since they both do essentially the same thing, it would be nice to share as much of the code as possible. Since the only difference is that the buttons work on a single record and the bulk actions work on a list of records, we could convert the single record for the button into a list of one, and then hand off the work to a function that expected a list, which would then work for both. We still need two listeners, though, so let’s build those next.
$rootScope.$on(eventNames.buttonClick, function(e, parms) {
var action = parms.config.name;
var sysId = [];
sysId.push(parms.record.sys_id);
processAction(action, sysId);
});
$rootScope.$on(eventNames.bulkAction, function(e, parms) {
var action = parms.config.name;
var sysId = [];
for (var i=0; i<parms.record.length; i++) {
sysId.push(parms.record[i].sys_id);
}
processAction(action, sysId);
});
Our processAction function then will not know or care whether the action came from a button or a bulk action. Everything from this point on will be the same either way. Let’s take a quick peek at that function now.
The check of c.data.inProgress is just a defensive mechanism to ensure that we are only processing one action at a time. We set it to true when we start and to false when we are done, and if it is already set to true when we start, then we do nothing, as there is already another action in progress. The rest is just a check to see if we need to collect comments, and if not, we proceed directly to processing the action. If we do need to collect comments, then we call this function.
function getComments(action) {
var msg = 'Approval comments:';
if (action == 'reject') {
msg = 'Please enter the reason for rejection:';
}
spModal.prompt(msg, '').then(function(comments) {
c.data.comments = comments;
processDecision();
});
}
And in either case, we end up here to process the decision.
function processDecision() {
c.server.update().then(function(response) {
window.location.reload(true);
});
}
Basically, that just makes a call over to server side where the actual database updates take place. Here is the entire Client controller all put together.
api.controller = function($scope, $rootScope, $window, spModal) {
var c = this;
var eventNames = {
referenceClick: 'data_table.referenceClick',
aggregateClick: 'data_table.aggregateClick',
buttonClick: 'data_table.buttonClick',
bulkAction: 'data_table.bulkAction'
};
$rootScope.$on(eventNames.buttonClick, function(e, parms) {
var action = parms.config.name;
var sysId = [];
sysId.push(parms.record.sys_id);
processAction(action, sysId);
});
$rootScope.$on(eventNames.bulkAction, function(e, parms) {
var action = parms.config.name;
var sysId = [];
for (var i=0; i<parms.record.length; i++) {
sysId.push(parms.record[i].sys_id);
}
processAction(action, sysId);
});
function processAction(action, sysId) {
if (!c.data.inProgress) {
c.data.inProgress = true;
c.data.action = action;
c.data.sys_id = sysId;
c.data.comments = '';
if (action == 'reject' || action == 'approvecmt') {
getComments(action);
} else if (action == 'approve') {
processDecision();
}
c.data.inProgress = false;
}
}
function getComments(action) {
var msg = 'Approval comments:';
if (action == 'reject') {
msg = 'Please enter the reason for rejection:';
}
spModal.prompt(msg, '').then(function(comments) {
c.data.comments = comments;
processDecision();
});
}
function processDecision() {
c.server.update().then(function(response) {
window.location.reload(true);
});
}
};
The actual work of updating the approval records takes place over on the server side. Here is the complete Server script.
(function() {
if (input && input.sys_id && input.sys_id.length > 0) {
var total = 0;
var approvalGR = new GlideRecord('sysapproval_approver');
for (var i=0; i<input.sys_id.length; i++) {
approvalGR.get(input.sys_id[i]);
if (approvalGR.state == 'requested') {
approvalGR.state = 'approved';
if (input.action == 'reject') {
approvalGR.state = 'rejected';
}
var comments = 'Approval response from ' + gs.getUserDisplayName() + ':';
comments += '\n\nDecision: ' + approvalGR.getDisplayValue('state');
if (input.comments) {
comments += '\nReason: ' + input.comments;
}
approvalGR.comments = comments;
if (approvalGR.update()) {
total++;
}
}
}
if (total > 0) {
var message = total + ' items ';
if (total == 1) {
message = 'One item ';
}
if (input.action == 'reject') {
message += 'rejected.';
} else {
message += 'approved.';
}
gs.addInfoMessage(message);
}
}
})();
That’s all basic GlideRecord stuff, so there isn’t too much commentary to add beyond the actual code itself. And that’s all there is to that. I still want to bundle all of these example up into a little Update Set, but I think I will do one more example before we do that. We will take a look at that one next time out.
“Few things are harder to put up with than the annoyance of a good example.” — Mark Twain
A while back we pushed our SNH Data Table Widgets out to Share, and then later made a few revisions and enhancements to come up with the version that is posted out there now. After spending a little time refactoring and standardizing some of the processes, we now have a fairly consistent way of handling reference links, bulk actions, buttons, icons, and the new scripted value columns. In most cases, there is a $rootScope broadcast message that just needs to be picked up by a companion widget, and then the companion widget handles all of the custom stuff that won’t be found in the common components. Most of the work that we did during that development process was centered on the shared components; the few companion widgets that we did throw in as samples were not the focus of the discussion. Now that the development of the table widgets is behind us, it is a good time to spend a little more time on the companion widgets and how they augment the primary artifacts.
Let’s say that we have a policy that Level 2 and 3 technicians working Incidents should respond to all customer comments, and even without customer inquiries, no open ticket should ever go more than X number of days without some comment from the assigned technician indicating the current status. To assist the technician in managing that expectation, we could configure a list of assigned incidents that included some visual indication of the comment status of each.
For those tickets where a comment is needed, we would want to provide a means for the technician to provide that comment right from the list. One way to do that would be to pop up the Ticket Conversations widget in a modal dialog. This would allow the tech to both view the current conversion and add their own comments.
Once a comment has been provided, the comment appears in the conversation stream, after which additional comments can be provided or the pop-up window closed.
Once the modal pop-up window is closed, the list should be updated to reflect the new comment status of the incident.
Here is the configuration script to produce the above table using the SNH Data Table from JSON Configuration widget.
The Data Table widget can be configured to produce the list, but to launch the modal pop-up, you will need to add a companion widget to the page. A companion widget shares the page with a Data Table widget, but has no visual component. It’s job is simply to listen for the broadcast messages and take whatever action is desired when a message of interest is received. At the top of the Client script of all companion widgets, I like to throw in this little chunk of code to help identify the values used to distinguish each potential message.
In this particular case, we are looking for a button click, and since there are no other buttons configured in this instance, we don’t even need to check to see which button it was.
Basically, this is just your typical spModal widget open, passing in some widget input. In the case of the Ticket Conversations widget, you need both a table name and the sys_id of a record on that table. In our case, we know that the table is the Incident table, and we can obtain the sys_id of the incident in the row from the parameters passed in with the broadcast message. When the modal window is closed, there are two functions passed in as arguments to the then function, the first for a successful completion and the second for a cancellation. In our case, we want to reload the page for either result, so the logic for each is the same.
For most circumstances, this would be the extent of the widget. For the most part, companion widgets are pretty simple, narrowly focused components that are built for a single purpose: to accomplish something that is not built in to the standard artifacts. Everything else is left for the primary widgets. We couldn’t get away without throwing in a little bit of hackery, though, since that’s what we do around here, so in this particular example, we will need to add just a bit more to the widget before we can call it complete.
In our example, we are using the class of the button to visually identify the current state of the comments on each incident. This cannot be done with the standard button configuration, as the class name is one of the configuration properties, and it is applied to all buttons in the column. To produce specific class values for each row, we have to resort to using a scripted value column instead of configuring a button. Each scripted value column requires a script to produce the value, and for this particular example, our script looks like this:
var LastCommentValueProvider = Class.create();
LastCommentValueProvider.prototype = {
initialize: function() {
},
getScriptedValue: function(item, config) {
var className = '';
var helpText = '';
var journalGR = this.getLastJournalEntry(item.sys_id);
if (journalGR.isValidRecord()) {
if (journalGR.getValue('sys_created_by') == gs.getUserName()) {
if (journalGR.getValue('sys_created_on') > gs.daysAgo(7)) {
className = 'success';
helpText = 'Click here to add additional comments';
} else {
className = 'danger';
helpText = 'Click here to update the status';
}
} else {
className = 'danger';
helpText = 'Click here to respond to the latest comment';
}
} else {
className = 'default';
helpText = 'Click here to provide a status comment';
}
return this.getHTML(item, className, helpText);
},
getHTML: function(item, className, helpText) {
var response = '<a href="javascript:void(0)" role="button" class="btn-ref btn btn-';
response += className;
response += '" onClick="tempFunction(this, \'';
response += item.sys_id;
response += '\')" title="';
response += helpText;
response += '" data-original-title="';
response += helpText;
response += '">Comment</a>';
return response;
},
getLastJournalEntry: function(sys_id) {
var journalGR = new GlideRecord('sys_journal_field');
journalGR.orderByDesc('sys_created_on');
journalGR.addQuery('element_id', sys_id);
journalGR.setLimit(1);
journalGR.query();
journalGR.next();
return journalGR;
},
type: 'LastCommentValueProvider'
};
Basically, we go grab the last comment, and look at the author and the date to determine both the class name and the associated help text for the button. Once that has been determined, then we build the HTML to produce the button in a similar fashion to the buttons created using the button/icon configuration. Those of you paying close attention will notice that the one significant difference between this HTML and the HTML produced from a button/icon configuration is the use of onClick in the place of ng-click. This has to be done because the HTML added to the page for a scripted value column is not compiled, so an ng-click will not work. The problem with an onClick, though, is that it is outside the scope of the widget, so we have to add this little tidbit of script to the HTML of our companion widget to address that.
<div>
<script>
function tempFunction(elem, sys_id) {
var scope = angular.element(elem).scope();
scope.$apply(function() {
scope.buttonClick('comment', {sys_id: sys_id});
});
}
</script>
</div>
This brings things full circle and gets us back inside the scope of the primary widget to activate the normal button click process, which will send out the $rootScope broadcast message, which will in turn get picked up by our companion widget. Normally, the HTML for a companion widget would be completely empty, but in this particular case, we were able to leverage that section to insert our little client script. I plan to bundle all of these artifacts up into an Update Set so that folks can play around with them, but before I do that, I wanted to throw out a couple more examples. We will take a look at another one of those next time out.