Skip navigation

Developer Community

2 Posts authored by: Frank Schuster Employee

Long promised, now finally found the time to write about loading spinners in Service Portal.

Probably everybody has (or has not ) seen the three small dots in the Service Portal header, when there is work happening in the background.

OOTB-Loading-Dots.png

 

This is a great way to indicate to the user, that there is still work to do for the browser.

However I personally find, that those dots might not draw the attention you would want, so let's have a look at how you could override those dots with your own loading spinner.

 

The good news is: this is really, really simple and quick

 

In the next few steps, I will describe everything that is needed to get a new loading spinner into your portal.

Attached to this blog, there is also an update set containing the exact result of the below described steps.

 

1. Picking a new loading spinner & uploading the SVG file

There are plenty of great resources out there, where you can create your own loading spinners. A lot of them will be CSS based, but I personally prefer the .svg (Scalable Vector Graphics) spinners since those will allow you to simply upload the new spinner into the Images tables and then use it in your Header Widget with the <img> tag, rather than re-creating all the CSS.

 

Here are a few resources (in no particular order, though one of my favourites is loading.io).

For the following example, I will use the spin loading image from loading.io.

loading_io_-_Your_SVG___GIF_Ajax_Loading_Icons.png

Click Get SVG and Upload the SVG to the Images [db_image] table with a name of portal-loading-spinner.svg or pick your own name, just remember that you will have to use it in Step 3.

 

2. Explaining the out of the box loading spinner

The code snippet responsible for rendering the loading spinner is included in the HTML part of the Header Menu widget.

<div class="header-loader" ng-show="loadingIndicator">
    <div class="hidden-xs sp-loading-indicator la-sm">
      <div></div>
      <div></div>
      <div></div>
    </div>
  </div>

 

The sp-loading-indicator class on the inner <div> element refers to the sp-loader.css file, which ships with Service Portal. This is the CSS class, which contains all CSS styles for rendering the three dots. We will get rid of this <div> in a second, but let me talk about the ng-show for the loadingIndicator variable first.

The whole loading spinner <div> is only shown, when the loadingIndicator variable is set to true, but where does this happen?

 

Have a look at the Client Script part of the widget and you will find the following three lines:

$scope.$on('sp_loading_indicator', function(e, value) {
  $scope.loadingIndicator = value;
  });

 

By listening to the sp_loading_indicator event, which is again part of Service Portal, the value of the variable will be determined.

In line 2 of that widget, it will initially be populated with the value of that variable within the $rootScope object.

$scope.loadingIndicator = $rootScope.loadingIndicator;

 

Essentially everything is already prepared for us, we won't have any work on the client side. We just have to make sure, that our new loading spinner (the .svg image), is rendered in the HTML part.

 

As the Header Menu is an OOTB widget, we will first have to clone the widget, so we are able to edit it. As a reminder: this is a common practice for all OOTB widgets to make sure, that ServiceNow does not override your changes during the next upgrade.

 

3. Cloning the Header Menu Widget and overriding the loading spinner

Open the Widget Editor (Service Portal -> Service Portal Configuration -> Widget Editor) and select the Header Menu Widget.

Click the Hamburger Menu beside the Save button and Clone the Widget.

 

You will not have to work in the Client or Server Script, so you can deselect those parts.

In the HTML part, replace the part described in Step 2 (the whole header-loader <div>) with the following snippet:

<div class="header-loader" ng-show="loadingIndicator">
    <div class="hidden-xs la-sm">
      <img src="portal-loading-spinner.svg" class="loading-spinner"/>
    </div>
  </div>

Make sure the name of the image is the name of the image you uploaded in Step 1.

 

Hint: to test your loading spinner, remove the ng-show directive, so that the loading spinner will always be displayed (or change it to ng-hide). Now you can do a right-click + Inspect in your browser and modify padding, margin, sizing etc. to temporarily adjust and test your styling. Once you have your styling all figured out, you can revert the change and adjust the CSS class.

 

Now we only need a few minor CSS changes. Within the CSS part, add the definition for the class loading-spinner and make some small adjustments to the header-loader class.

 

Here is the OOTB CSS for the header-loader class:

.header-loader {
  float: left;
  width: 24px;
  position: relative;
  top: 24px;
}

 

Change it to the following and add the loading-spinner class:

.header-loader {
  padding: 5px;
}


.loading-spinner {
  width: 50px;
  height: 50px;
}

 

 

4. Adding the Header Menu to your Portal

The last step is, that you will have to use that new Header Menu in your Portal.

Navigate to your Portal record in the platform view, open the Main Menu that is allocated to your Portal and simply change the Widget reference (or keep it, if you already had your own widget, to which you added the above described changes).

 

And that's already it! With a few minor changes, we created our own loading spinner.

That's how it will look (I did not add any menu items to my new header menu):

 

Custom_Loading_Spinner_Portal_-_Service_Portal.png

 

Obviously you could also pick a CSS based spinner. In that case you would have to add the according CSS to the CSS part of the Widget (or CSS include, that should be related to your portal). In my opinion, SVG files are a great alternative to save yourself some work and still keep it light-weight (the spinner used here, has only a size of 3 KB).

Keep browser support in mind, when using SVG files. In general all major browsers support .svg files, only IE8 and below will not be able to render it.

 

If the new spinner is still not obvious enough, you could certainly take all of this and render it i.e. in a Bootstrap Modal and display it right in the center of the screen  - that will hopefully be sufficient

 

Credits go out to daniel.conroy and napike who sent me on the right path

 

Next time, read about integrating a Google Custom Search Engine into your Service Portal!

 

-Frank

Let's talk a bit more about Widget options! b-rad already published a great article about the usage of options, which I always like to compare to System Properties. Those can hold values to design content a bit more dynamic, rather than hard-coding values.

 

The available option types, as of today, are:

  • Integer
  • String
  • Boolean
  • Choice
  • Field name & Field List (both dependent on the "table" option on the according Widget instance)
  • Reference

 

Within this post we want to look a bit more into the Reference option type.

 

Use Case #1 - Understanding the Reference option

Before we start with advanced options, let's just have a look what you could do with a standard reference type option. Assume we would want to build a Menu block based off the Instance with Menu [sp_instance_menu] table. Instead of writing a Widget for each and every Menu block, how about we just "optionize" (yes, I think that should be a word moving forward in the Service Portal context ) in a way, that an SP Admin can simply pick the Menu from a reference field, rather than changing the sys_id in the Widget?

 

Here's what we could do (no need to rebuild this yourself, it's more about how to use the reference option - you will build a widget later on):

  1. Let's assume we'll create a widget called "Menu Block"
  2. Then we will create a reference option in the widget pointing to the Instance [sp_instance_menu] table
  3. Have the Server Script utilize the newly created option value to retrieve the menu record, which will be defined in the widget instance. Retrieve the basic values of that menu with the $sp server-side API and have the HTML display the values within the data object
  4. Create a new Menu record (for demo purposes I added an Image field to the sp_instance_menu table, that we'll pull into the Widget)
  5. Create a widget instance by dragging & dropping the widget onto a page & define a menu
  6. Modify the Menu of an existing widget instance by Ctrl + right-clicking on the widget instance (this does not work in the Designer, you'd have to be on the actual page)
  7. Voila, we changed the instance of that menu block to a different menu.

output_9zFFN7.gif

This example is not meant to be particularly pretty, it should more explain the concept of how to effectively utilize reference options.

I also refrained from providing the code to evaluate the child items for this very reason.

 

So far so good - now that we know how to use Reference options, let's move on to the advanced use-case.

Probably almost everybody of you has used Reference Qualifiers in the platform view/backend (you name it), to filter down the results of a reference field - e.g. just return all operational CI's on a configuration item reference field or just display active callers on a user reference field.

 

Now, how would we apply those to a service portal option? The bad news: not at all. The option schema does not accept reference qualifiers. The good news: you can create your own "Option tables". You might have come across the Data Table field, which is defined on the Widget record itself. Per default you will mostly find Instance [sp_instance], but you'll also find e.g. Instance of Simple List [sp_instance_vlist], which is used by the Simple List Widget. All fields on the table defined as the data table, are available options for the widget that uses this table .

Report1.png

 

In addition, you will still be able to define options via the Widget Options. Be aware that those are not automatically fields on the according instance table, those will be transformed into options represented in the JSON format - here is an example how this would look like based on the first use-case described in this post.

JSONOptions.png

 

This is great for multiple reasons:

  1. As this is a "real" field, no JSON option, we can apply Reference Qualifiers!
  2. Looks like a form doesn't it? Well, it is! That also means that we can leverage UI Policies to conditionally hide & show options. Sweet! Something else, that we would not be able to achieve, if we would stick to the default Option Schema.

 

Use-Case #2 - Creating your own Instance Table to apply Reference Qualifiers & leverage UI Policies

Enough talk, let's create a custom "options" table.

  1. Navigate to System Definition > Tables, click Create New
  2. Pick a Name & Label for your new table, given the naming convention of all existing tables I'd suggest something along those lines: "Instance of xyz" / "Instance with xyz". Personally I always went for SP <Name of the Widget> Instance so far.
  3. Extend the new table from Instance [sp_instance]
  4. If you want create a new Module for it within the Service Portal application. Make sure to disable the role creation, numbering etc. - we don't really need this in the context of options.
  5. Click Submit
  6. The table will inherit all fields from the Instance table, so make sure you do not create duplicate fields for something that's already there (e.g. HREF/URL, Bootstrap Color etc.).
  7. Now you can go ahead and create whatever field you want from the Tables view, via the Form Designer or Form Layout - I created the following fields for the demo:
    1. Reference - to Business Services [cmdb_ci_service]
    2. Override Styles - Boolean
    3. Font Color - Color
    4. Border Color - Color

Report3.png

 

If you navigate to the form of that table, you will most likely see the following (unless you directly created and formatted the form via Form Designer / Layout).

Report4.png

 

At this point we can start applying our UI Policies and the Reference Qualifier on the Reference field. Let's start with the UI Policy, which should hide the color fields if Override Styles is not checked.

Report5.png

 

For the reference qualifiers you can Right-click on the label of the reference field and then choose Configure Dictionary.

If you switch into the Advanced View of the dictionary record you will also be able to define an Advanced Reference Qualifier (e.g. calling your own filter function defined in a Script Include), rather than only a Simple Reference Qualifier.

output_L1AD2u.gif

 

 

At this point we have prepared our option/instance table: once you've done this, you will be able to choose from the fields on that table right below Data Table, by using the Fields field list.

The Option Schema will not automatically expose all fields as options - quite the contrary: it will only use the fields that you pick within that field list and expose them as options in the widget instance. Another thing that I found is, that the field will have to be on the Widget Instance form - only selecting it within the Fields won't work.

 

Let's create a new widget to test our new instance table. Open the Widget editor via Service Portal > Service Portal Configuration or https://yourInstance.service-now.com/sp_config

Give your new widget a name, e.g. "Widget Option Test" - if you create a Test Page, be aware that you will have to delete the widget instance on the page and re-add the widget after you changed the Data Table, otherwise it will still point to Instance, rather than to your new table.

Once the widget is created navigate to the platform view/backend and open up the widget.

 

Report9.png

 

Scroll down to Data Table & Fields and define your new option table. Also select the fields (and essentially now options) we created.

 

output_cJFAyE.gif

 

Now we are almost done - but at this point we have a blank widget, that wouldn't display anything, so let's just add some HTML and data based on the selected service.

Again, this widget will not really make a whole lot of sense, but should rather convey what you will be able to do.

 

Go back to the Widget Editor. If you had your Widget Editor open when you associated the new data table with your widget record, make sure to reload the widget editor so that it has the correct context, i.e. the new data table.

 

Write the following code into the Server Script:

function() {

  //initialize serviceValues object within the data object
  data.serviceValues = {};

  //Business Criticality has indeed only one "s" in the tech field name, no error.
  var fields = "name, owned_by, busines_criticality, version, used_for, location";

  //the option name is going to be the technical field name on the table
  var service = options.u_business_service; 
  var grService = new GlideRecord("cmdb_ci_service");
  if(grService.get(service)) {
       data.serviceValues = $sp.getFieldsObject(grService, fields);
  }


})();

 

If you are curious what $sp.getFieldsObject() does, I suggest doing a Ctrl + Right-click on the Widget instance and then do a Log to Console: $scope.data.

 

Open the developer tools of your browser, go to Console and review the serviceValues object, which will contain e.g. the display values and labels of all the fields we defined.

Debug.png

 

Note: you can also use $sp.log(), which only outputs if the user has the sp_admin role or is impersonating. You can even use console.log() in the server script to debug objects, variables etc. If you are debugging an object make sure not to prefix it with a string (i.e. console.log("This is my serviceValue object: " + data.serviceValues)), since that would convert it into a string and you would not be able to move through the hierarchy within the console.

 

 

Now, we write the following HTML:

<div>
  <div class="panel panel-default">
    <div class="panel-heading" ng-style="{'background-color': options.u_border_color}">
      <h3 class="panel-title">{{data.serviceValues.name.display_value}}</h3>
    </div>
    <div>
      <dl>
        <!-- Because the <dt> tag closes we have to extend the ng-repeat range by using ng-repeat-start, otherwise the <dd> tag could not access the values -->
        <dt ng-repeat-start="item in data.serviceValues" ng-style="{'color': options.u_font_color}">{{item.label}}</dt>
        <dd ng-repeat-end>{{item.display_value}}</dd>
      </dl>
    </div>
  </div>
</div>

 

Save your Widget and go to Service Portal > Service Portal Configuration and open up the Designer or the platform view of the Widget Instance.

Navigate to your test page and modify the Widget Instance Options by providing a title to the widget, picking a service and providing a font color via the color picker (another great side effect of using real fields instead of JSON options) + a border color, that we'll use for the panel heading (slight misusage of the name, but you get the idea ). We are not using it within the widget, but it's always good to have a title if you go to the list of widget instances - helps distinguishing between instances based of the same widget.

Report12.png

 

Go to your test page and reload it. It should now look like this, most likely it would contain different data dependent on the service that you chose:

Report13.png

 

At this point you have successfully created a new Widget Instance table, added fields to be used as options on that particular table and you made use of them within the Widget. I hope you found this article useful and that it will help you to create more dynamic widgets in the future.

Filter Blog

By date: By tag: