@mentions in the Ticket Conversations Widget, Revisited

“Continuous improvement is better than delayed perfection.”
Mark Twain

When I hacked up the Ticket Conversations Widget to add support for @mentions, I knew that a number of people had already asked ServiceNow to provide the same capability out of the box. I also knew, though, that these things take time, and not wanting to wait, I just charged ahead as I am often prone to do. However, I was happy to hear recently that the wait may not be all that much longer:

Hi,

The state of idea Service Portal – @Mention Support has been marked as Available

Work for idea is complete and planned to be released in the next Family version

Log in to Idea Portal to view the idea.

Note: This is a system-generated email, do not reply to this email id.
If you would like to stop receiving these emails you can change your preferences here.

Sincerely,
Community Team  

I am assuming that the “next Family version” is a reference to Quebec, although I have nothing on which to base that interpretation. It’s either that or Rome, so one way or another, it appears to be on the way. If and when it does arrive, I will gladly toss my version into the trash and use the real deal instead. I don’t mind building out things that I want that the product currently doesn’t provide, but as soon the product does provide it, my theory is that you fully embrace the out-of-the-box version and throw that steaming pile of home-grown customized nonsense right out the window. I actually look forward to that day.

But then again, that day is not today! Not yet, anyway.

Configurable Data Table Widget Content Selector, Revisited

“Learning from mistakes and constantly improving products is a key in all successful companies.”
Bill Gates

When I first conceived of my Configurable Data Table Widget Content Selector, my main focus was on creating a process that would read the JSON configuration file and turn those configuration rules into a functioning widget in accordance with the specifications. That was an interesting challenge that I had a quite a bit of fun with, but I started out by hard-coding the configuration object at the beginning of the widget and I never went back and set things up so that you could reuse the widget with a different configuration. Obviously, that’s not very friendly; now I need to go back in and set things right. The configuration object that you want to use should be an external parameter that gets passed into the widget via some external source such as a URL parameter or widget option. Let’s see if we can’t fix that right now.

I think that the first thing that I want to do is to create a base class for the configuration object script. That will do two things: 1) provide a common foundation of code for all of the configuration objects that you would like to build, and 2) provide a way to identify all of the qualifying scripts as all scripts that extend this particular base class. We’ll call it the ContentSelectorConfig:

var ContentSelectorConfig = Class.create();
ContentSelectorConfig.prototype = {

	initialize: function() {
	},

	getConfig: function(sp) {
		return {
			perspective: this.perspective,
			state: this.state,
			table: this.table
		};
	},

	type: 'ContentSelectorConfig'
};

With our base class established, I can now hack up my earlier configuration object and make it an extension of this new base class:

var MyTaskConfig = Class.create();
MyTaskConfig.prototype = Object.extendsObject(ContentSelectorConfig, {
	initialize: function() {
	},

	perspective: [...],

	state: [...],

	table: {...},

	type: 'MyTaskConfig'
});

For now, I am just going to reuse the list of perspectives, states, and tables that I was using before and focus on the mechanics of making this configuration an external parameter rather than a hard-coded reference. Rather than make the changes to my original My Data widget, though, I decided to clone the widget, give it a new name, and leave the original widget as is. I called my new widget Content Selector, and it started out life as an exact copy of the My Data widget before I started to hack it up.

The first thing that I did was to add a new option to the Option Schema so that we could pass in the name of the ContentSelectorConfig that we want to use. We already had an existing option to display the widget content in a single row rather than a stacked block, so this was just a matter of adding a second option to the existing array of options.

[{
  "hint":"Mandatory configuration script that is an extension of ContentSelectorConfig",
  "name":"configuration_script",
  "section":"Behavior",
  "label":"Configuration Script",
  "type":"string"
},{
  "hint":"If selected, will display the widget content in a single row rather than a stacked block",
  "name":"display_inline",
  "default_value":"false",
  "section":"Presentation",
  "label":"Display Inline",
  "type":"boolean"
}]

Now that our new option has been defined, it’s time to rewrite the code that pulled in the hard-coded configuration object. Here is the original version of the first few lines of code in the server side script:

var mdc = new MyDataConfig();
data.config = mdc.getConfig($sp);
data.config.authorizedPerspective = getAuthorizedPerspectives();
establsihDefaults();
data.user = data.user || {sys_id: gs.getUserID(), name: gs.getUserName()};
data.inline = false;
if (options && options.display_inline == 'true') {
	data.inline = true;
}

We are still going to want to establish the data.config object, but we are going to want to do it using an instance of the class named in our new Option. Last year, when I was working on my Static Monthly Calendar, I needed to turn a class name into an object of that class, and I built a little tool for that, which I called the Instantiator. We can use that same tool here to turn the name of our configuration script into an instance of that class that we can use to pull in the configuration. Here is the restructured code to start out our updated widget:

data.config = {};
data.inline = false;
data.user = data.user || {sys_id: gs.getUserID(), name: gs.getUserName()};
if (options) {
	if (options.configuration_script) {
		var instantiator = new Instantiator();
		instantiator.setRoot(this);
		var configurator = instantiator.getInstance(options.configuration_script);
		data.config = configurator.getConfig($sp);
		data.config.authorizedPerspective = getAuthorizedPerspectives();
		establsihDefaults();
	}
	if (options.display_inline == 'true') {
		data.inline = true;
	}
}

Now we just need to throw it on a page with a Data Table widget, configure the options, and give it a spin. After dragging all of the widgets onto the page in the Page Designer, clicking on the pencil icon in the upper right-hand corner of our update widget will bring up the Options dialog where we can specify our configuration script.

Widget Options dialog

Once the widget Options have been specified, all we need to do is to pull up the page and see if things are still functioning as they should, which appears to be the case.

Testing the completed modifications

Well, that’s about all there is to that. Basically, it does exactly what it did before, but you can now specify the configuration script using the Widget Options instead of having it hard-coded in the script as it was in the original version. Here’s an Update Set with the modifications if you’d like to play around with it on your own.

Dynamic Service Portal Breadcrumbs, Corrected (again!)

“What we see depends mainly on what we look for.”
Sir John Lubbock

One of the nice things about sharing your code with others is they end up testing it for you, and they do it from a different perspective than your own. The other day I received a comment from Ken out in California regarding my Dynamic Service Portal Breadcrumbs widget. He wanted to let me know that whenever he dragged the widget onto a page in the Portal Page Designer, the widget would disappear. I’ve had that little widget for quite a while now, and I have corrected it, perfected it, enhanced it, and I was actually aware of that behavior, but I had always just chalked that up as an irritating annoyance. From Ken’s perspective, though, it was a problem that needed to be addressed, and of course, he is correct. So, I decided to see if I could figure out why it was doing that, and if I could actually fix the problem.

I started out by doing a little research, just to see if anyone else had experienced this phenomenon. Sure enough, other people had reported the same behavior with other widgets, and the common thread always seemed to be that there was some coding error in the Client Script of the widget. Armed with that little tidbit of information, I set out to do a little experimenting.

The first thing that I did was comment out all of the code inside of the Client Script function and then run over to the Page Designer to see if that had any effect. Sure enough, the widget now appeared on the screen. That told me that I was on the right track, and that the issue actually was some problem in the Client Script. After that, it was just a matter of running through a sort of binary search, revealing and commenting out various chunks of code until I finally narrowed it down to the one line that was causing the problem:

var portalSuffix = ' - ' + $rootScope.portal.title.trim();

The reason that this worked on all of the pages in all of the portals where I have used it, but not in the Page Designer itself, was that the Page Designer has no title defined. When you try to run the trim() function on a String that doesn’t exist, you are going to get a null pointer exception. That’s obviously not good. So now what?

To step back just a bit, the whole point in grabbing the title was to look for it in the page title and remove it. Page titles in the form of <page title> – <portal title> are extra wide and contain the redundant portal title value, so I wanted to strip off that portion, if it was present. If there is no portal title, then that entire segment of code has no value, so I ended up checking for the presence of the title first, and then tucking all of that code underneath that conditional.

if ($rootScope.portal && $rootScope.portal.title) {
	var portalSuffix = ' - ' + $rootScope.portal.title.trim();
	var cutoff = thisTitle.indexOf(portalSuffix);
	if (cutoff != -1) {
		thisTitle = thisTitle.substring(0, cutoff);
	}
}

That was it. Problem solved. Thank you, Ken, for prodding me to take a look at that. It turned out to be a relatively easy fix, and something that I should have addressed a long time ago. Here’s an Update Set with the corrected code.