Updated: Apr 1

Learn how to customize the default Service Portal loading page

By Jeff Pierce, Service Portal Practice Manager @ Cerna Solutions

I love everything Service Portal has to offer. I mean, who doesn’t? I’m quite impressed by all the thought and design that went into it. But like everything good, nothing is perfect. Exhibit A – The Service Portal Loading Page:

If you’re an optimist, you may look at this and think: simple, minimal, or even… arid? I’m a realist, I have other words come to mind: barren, bleak, desolate, and stark (not the ‘House of’ kind). Now consider the fact that this page is anything but configurable, and I’ve gone from unimpressed to annoyed.

What are we to do? Pretend it’s not there? Look away every time we load our portal? No, I can’t. I won’t.

Where do we start?

So I put my inspector gadget hat on and dug my heels in. I noticed something… If you watch the element inspector closely as the page loads, you might spot this (if you’re quick):

See that h4 element?

<h4 ng-if="firstPage" class="ng-scope" data-ng-animate="1">...</h4>

That’s the element that is putting the message and spinning icon on the page. Where is the variable firstPage being set? Heck if I know. But now I’ve got this tiger by the toe. By looking at this, I can tell that the most unique thing about the element is the ng-if attribute. Therefore, I can use a CSS attribute selector to attack that thing with some style.

h4[ng-if="firstPage"] { ...styles will go here... }

The Styles

Our options are limited with only that one element to go on. But I’m still going to push this as far as I can without spending too much time on it. I’d like enlarge the message and icon, center it, color the page, and include a site logo. So here are all the CSS properties I’m going to throw at this.

position: absolute; 
top: -10px;
left: 0;
width: 100%;
height: 100%;

I want this element to take up the entire screen, so I’ll use absolute positioning to position it relative to the document body. The other properties are used to cause this element to stretch the entire width and height of the page. By default, there is a little padding at the top of the screen caused by the ‘padding-top’ class on the main page element, so I use top: -10px; to take care of that empty space. This is important if you’re going to fill the page with color.

text-align: center;
padding-top: 10%;
font-size: 3em;
color: #fff;

I don’t like how small that message and icon are, or how it’s tucked up in the corner like it is. So, I’m centering it with text-align, pushing it away from the top with a relative padding. I’m making it bigger with font-size, and coloring it white with color.

background: #1E9479 url('my_logo.jpgx') no-repeat center;
background-size: 35%;

The background property is a mouthful. The first value is the color with which I’ll fill the page. The second value is the name of an image in the image table. Remember to add the ‘x’ to the end of the image’s file extension. You might be wondering why, but I’ll leave you in suspense. With the third value, I’m telling it to print the image only once. The fourth is positioning it in the center. The background-size property is used to relatively size the image.

Throw all this jazz into one of your portal’s css files:

h4[ng-if="firstPage"] {
 position: absolute;
 top: -10px;
 left: 0;
 width: 100%;
 height: 100%;
 text-align: center;
 padding-top: 10%;
 font-size: 3em;
 color: #fff;
 background: #1E9479 url('my_logo.jpgx') no-repeat center;
 background-size: 35%;

Note: In Kingston and later, you may need to use this as your selector:


The Result

Remember to do a hard refresh of your browser to reset the cached CSS.

Oh yeah! That looks so much better! I think even Erlich would be proud.

Now, keep in mind that you may need to adjust those CSS properties a bit depending on the image and colors you choose. But as you can see, it’s actually pretty easy. I hope you have fun with this and get a chance to do something awesome to your otherwise lackluster portal loading screen.

Updated: Apr 1

ng-class: Demystifying AngularJS and Service Portal

This series of blog shorts is intended for those who have little to no experience with AngularJS. We will take little bites into the world of Service Portal and AngularJS, demystifying it piece by piece.

In this post, we’ll learn how to dynamically add a class name to an html template by using the AngularJS ng-class directive. While there are many ways to utilize ng-class, we’ll focus on a few that will likely be of use to you. To illustrate these methods, let’s build a widget from scratch. The first thing we’ll add is the HTML template for the bootstrap jumbotron component. Let’s include variables for a simple welcome message:

<div class="jumbotron">
   <h1>{{}}, {{}}</h1>

Populate these variables in the controller:

function($rootScope) {
   var c = this; = $; = 'Welcome';

Don’t forget to inject $rootScope into the controller function. This is what gives us access to current user’s information, like their name.

Save what you have so far. Put the widget on a new page, and check it out:

Now, let’s color the background of the banner. First, we’ll define CSS for two classes:

.morning {
   background: #FC5C7D;  /* fallback for old browsers */
   background: -webkit-linear-gradient(to top, #6A82FB, #FC5C7D);  /* Chrome 10-25, Safari 5.1-6 */
   background: linear-gradient(to top, #6A82FB, #FC5C7D); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
   color: #2B2B2B;
.evening {
   background: #1a2a6c;  /* fallback for old browsers */
   background: -webkit-linear-gradient(to top, #fdbb2d, #b21f1f, #1a2a6c);  /* Chrome 10-25, Safari 5.1-6 */
   background: linear-gradient(to top, #fdbb2d, #b21f1f, #1a2a6c); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
   color: #FFFFFE;

The Simplest Method

We won’t hardcode a class name into the HTML. We’ll use ng-class to hold a variable that can be any class we want:

<div class="jumbotron" ng-class="c.bannerClass">

In the client controller, we define c.bannerClass.

c.bannerClass = ‘morning’;

As a result, the class morning is included in our template. Look at what we have so far:

Considering Conditions

To conditionally apply morning or evening, we need to first script the necessary logic. In the server script we’ll figure out if it’s before noon, or after noon:

var now = gs.nowDateTime();
var nowTime = now.split(' ')[1].split(':')[0];
if(nowTime > 11) {
   data.itIsEvening = true;

Though unrelated to CSS, let’s update the client controller to change depending on the time of day: = 'Good morning';
if( { = 'Good evening';

Now we’re ready to be dynamic! In the HTML, we’ll apply the morning class by default in the regular class attribute. In the ng-class attribute we’ll use an object to conditionally include the evening class if it is evening:

<div class=”jumbotron morning” ng-class={ ‘evening’: }>

Due to the cascading nature of CSS, the properties of the evening class will override the properties of the morning class, because evening is defined after morning in the CSS.

If you want to test that this is working, just change your definition of evening in the server script. Rather than 11, use an hour that is earlier than the current hour in your timezone.

I can now see that when evening comes, my widget changes appropriately:

If you don’t want to include morning by default, then you can add another property to the object we created in the ng-class attribute:

<div class=”jumbotron” ng-class={ ‘evening’:, ‘morning’: ! }>

The above method is great in situations where you could potentially apply more than one class. However, if it is an either morning or evening situation, like what we’re doing, then use a ternary operator to decide which class to use:

<div class=”jumbotron” ng-class=” ? ‘evening’ : ‘morning’”>

Using a Function

What if it’s more complicated than this? What if, rather than just morning and evening, you want to also include afternoon and night? First, update your ng-class attribute to call a function. This function doesn’t exist yet, so call it whatever you want, like:


Then, update your server script to provide an hour value, rather than a simple boolean. It’s pretty simple, just move the nowTime variable from a local var to an item in the data object. Your whole server script will now look like this:

var now = gs.nowDateTime();
data.nowTime = now.split(' ')[1].split(':')[0];

Now, you can use on the client side. In the controller, create the getTimeClass function that will return the proper class name to the HTML. Go ahead and update your messaging while you’re at it.

c.getTimeClass = function() {
   var timeClass = 'morning';
   if( > 11){
      timeClass = 'afternoon';
   if( > 17) {
      timeClass = 'evening';
   if( > 21) {
      timeClass = 'night';
   } = 'Good ' + timeClass;
   if( < 6) { = 'Go to bed you maniac';
      timeClass = 'go-to-bed';
   return timeClass;

Don’t forget to go add CSS for these new classes. You can use the ones I picked below, or use to create your own cool gradients.

.afternoon {
   background: #36D1DC;  /* fallback for old browsers */
   background: -webkit-linear-gradient(to bottom, #5B86E5, #36D1DC);  /* Chrome 10-25, Safari 5.1-6 */
   background: linear-gradient(to bottom, #5B86E5, #36D1DC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
   color: #fff;
.night {
   background: #141E30;  /* fallback for old browsers */
   background: -webkit-linear-gradient(to bottom, #243B55, #141E30);  /* Chrome 10-25, Safari 5.1-6 */
   background: linear-gradient(to bottom, #243B55, #141E30); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
   color: #ccc;
.go-to-bed {
   background: #000000;  /* fallback for old browsers */
   background: -webkit-linear-gradient(to bottom, #434343, #000000);  /* Chrome 10-25, Safari 5.1-6 */
   background: linear-gradient(to bottom, #434343, #000000); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */

The best way to test this is to override in the controller. So before the getTimeClass function, set to different values. Just remember to remove this when you’re done testing.

In Review

We used these ng-class methods to conditionally apply a class in our HTML:

The simplest way to define a conditional class:


Apply a class based on object notation and simple expression:

ng-class={ ‘evening’: }

Apply more than one class conditionally via object notation, by including another object property:

ng-class={ ‘evening’:, ‘morning’: ! }

Using a ternary operator for deciding between two classes:

ng-class=” ? ‘evening’ : ‘morning’”

Calling a function to return a class based on more elaborate evaluations:


Now you have what it takes to use ng-class in a variety of ways, and make your HTML styles more dynamic than ever!

#css #servicenow #portal #serviceportal #ngclass #angularjs

Data Collections: Demystifying AngularJS and Service Portal

This series of blog shorts is intended for those who have little to no experience with AngularJS and custom widgets. We will take little bites into the world of Service Portal and AngularJS, demystifying it piece by piece.

In this post, we’ll look at how a list of catalog items is populated from a server side data collection. To illustrate, we’ll examine the SC Category Page widget, which is what you see when you click into a category in the Service Catalog.

If you’re following along in your own instance, go open the “SC Category Page” widget in widget editor mode. Clone it to create an editable copy for you to play with.

We want to understand how this list of items is created. So, to get our first clue we look in the HTML template. We talked about ng-repeat in a previous post, so you may recognize what’s happening in line 9. That ng-repeat is looping through the items in the array data.items.

<div class="col-sm-6 col-md-4" ng-repeat="item in data.items">

Great, so we found where each item gets repeated in the template, but what is populating data.items with content?

Looking through the client script and server script, see if you can find anything defining data.items. Spoiler alert, it’s in line 17 of the server script.

var items = data.items = [];

Exactly what are data.items and items?

data is an object used to store information and properties about each widget instance. There are many useful properties, options, etc. stored in the data object of each widget. In this widget, we add items to data. Thus data.items is now an object we can use to hold our collection of catalog items.

items is a local array that was created to hold the collection of catalog items and their properties. Both items and data.items start out as blank arrays. As we push content to items, both items and data.items will stay in sync. Why? Because data.items is a reference to items as a result of the way line 17 was written.

How do we push GlideRecord results into items?

When the GlideRecord queries, it returns the entire record object for each catalog item. That’s a lot of useless info. We don’t need to know everything, just what’s necessary. Therefore, we use a separate item object to collect only the properties/values we are interested in. This also gives us a chance to manipulate the data into a format most friendly to our purposes.

On line 32 of the server script an empty item object is defined inside the GlideRecord while loop.

while ( {

     // Does user have permission to see this item?
     if (!$sp.canReadRecord("sc_cat_item", sc.sc_cat_item.sys_id.getDisplayValue()))
 var item = {}; }

The item object is used to collect the properties we want for each catalog item.

For example, on line 37 a property is set for sys_class_name. It’s given the same value as the catalog item’s sys_class_name.

item.sys_class_name = sc.sc_cat_item.sys_class_name + "";

Line 37 uses the + “” method to stringify the field value. This is important, because this value (sys_class_name) may get interpreted as an object, which would cause problems. Another way to stringify is: sc.sc_cat_item.sys_class_name.toString();

Or, the ServiceNow best practice way would be: sc.sc_cat_item.sys_class_name.getValue(‘sys_class_name’);

On line 38 a page property is hardcoded: = 'sc_cat_item';

Then depending on conditions, such as if this is a Content Item or Order Guide, we might change the property, or create new properties. Lines 39 – 50:

if (item.sys_class_name == 'sc_cat_item_guide') = 'sc_cat_item_guide';
else if (item.sys_class_name == 'sc_cat_item_content') {
     $sp.getRecordValues(item, gr, 'url,content_type,kb_article');
     if (item.content_type == 'kb') {
 = 'kb_article';
          item.sys_id = item.kb_article;
     } else if (item.content_type == 'literal') {
 = 'sc_cat_item';
     } else if (item.content_type == 'external')
 = '_blank';

See, you can create as many properties as you want, however you want, for each item. When done, push the item object to the items array. Line 52:


Remember, these changes also affect data.items since it is a reference to items.

data.items should be full of catalog items now

Since we stored the collection items in the data object, we can access it in the HTML template and client controller. To see it in action, go back to Line 9 in the HTML template where ng-repeat loops on data.items:

<div class="col-sm-6 col-md-4" ng-repeat="item in data.items"><div class="text-muted item-short-desc">{{item.short_description}}</div></div>

Remember, if you want to use something in the template, first you’ll have to add it to the item object in the server script. For example, if there was a catalog item field named u_font_icon, I can add it as a property to item. At around line 51 in the server script I would write:

item.fontIcon = sc.sc_cat_item.getValue(‘u_font_icon’);
  1. fontIcon is the name I give to the property.

  2. sc_cat_item.u_font_icon is the reference to the custom field u_font_icon.

Then, in the HTML template I can use the property (here we assume the value I get from that field is a font awesome class, ie. ‘fa-laptop’):

<div class="col-sm-6 col-md-4" ng-repeat="item in data.items"><i class=”fa {{ item.fontIcon }}></i>
     <div class="text-muted item-short-desc">{{item.short_description}}</div></div>

There’s a lot to explore on this topic, so in the spirit of brevity I will call curtains on this post. I hope this helps your understanding the widget’s data object, and using it to store content.

We didn’t even touch the client controller… maybe next time.

#angularjs #serviceportal #servicenow #widget

Start Now

Security & Risk Solutions
IT Solutions
Business Solutions
HR Solutions
Customer Solutions
CS 2020 LOGO - solutions tagline (white)


Phone:  +1 844 804 6111 (US)

               +44 (20) 33254077 (UK)

  • White LinkedIn Icon
  • White YouTube Icon
  • White Twitter Icon
ServiceNow Services

© 2020 Cerna Solutions, Inc. All Rights Reserved. 2056 Palomar Airport Road Carlsbad, CA, 92011.