ServiceNow custom catalog variable type

Sometimes the base set of variable types available to you as a service catalog developer just do not cut the cheese. I have often pined for other non-standard variable¬†types that are not included in the base platform. This article demonstrates how it is possible to roll your own catalog variable types using UI macros and a little bit of client-side trickery. The example I will be using here is a ‘duration’ type field, which allows the user to enter a number of days, hours, minutes or seconds. If the user enters a non-numerical value or a number that exceeds the permitted maximum for each field, an error message is displayed.

This solution uses the following steps to accomplish the desired function:

  1. A UI Macro used to create the physical variable template which the user will see on the form.
  2. When a user fills in the custom variable on a catalog item form, the values are stored in a ‘hidden’ text variable, as the UI macro cannot store data on its own.
  3. When viewing a Request Item, Catalog task or shopping cart item, a client script pulls the duration value from the hidden field and displays it in our macro.

This design can be used to create any type of variable you can think of. The principle here is that that data entered in the macro in whatever form is stored in plain text, which can be re-called when that variable is displayed at a later date. The following section explains in more detail how this works. Feel free to comment and ask questions about specific details, suggest improvements or even talk about other variable types that you may have created using this design principle. Please note that although this code has been built and tested exclusively in the Istanbul release, I see no reason to believe that this code would not work in earlier builds.

 

Technical Breakdown

The first thing we need to do is build a UI macro to store the layout of our variable. Here’s the code sample:

UI Macro

Name: AD_duration_catalog_variable

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
   <style>
     .duration-input {
     display: inline;
     width: 50px;
     height: 32px;
     padding: 6px 9px;
     font-size: 13px;
     line-height: 1.42857;
     color: #343d47;
     background-color: #fff;
     background-image: none;
     border: 1px solid #bdc0c4;
     border-radius: 3px;
     -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
     box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
     -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
     -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
     transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
   }
   .duration-div {
     display: inline;
     width: 25%;
     height: 32px;
     padding-right: 20px;
   }
  </style>
  <script>
    function durationCalc(field, max) {
      var el = document.getElementById(field);
      if(isNaN(el.value) || parseInt(el.value) > max) {
        g_form.addInfoMessage("Please enter a numerical value between 1 and " + max);
        el.value = "";
      }
      var secs = document.getElementById('dur_secs').value * 1000;
      var mins = document.getElementById('dur_mins').value * 1000 * 60;
      var hours = document.getElementById('dur_hours').value * 1000 * 60 * 60;
      var days = document.getElementById('dur_days').value * 1000 * 60 * 60 * 24;
      var total = days + hours + mins + secs;
      g_form.setValue("duration_hidden", total);
    }
  </script>
  <div class="duration-div"><input onblur="durationCalc('dur_days', 99);" type="text" id="dur_days" class="duration-input"/>&#160;days</div>
  <div class="duration-div"><input onblur="durationCalc('dur_hours', 23);" type="text" id="dur_hours" class="duration-input"/>&#160;hours</div>
  <div class="duration-div"><input onblur="durationCalc('dur_mins', 59);" type="text" id="dur_mins" class="duration-input"/>&#160;minutes</div>
  <div class="duration-div"><input onblur="durationCalc('dur_secs', 59);" type="text" id="dur_secs" class="duration-input"/>&#160;seconds</div>
</j:jelly>

At this point we should probably take a pause and see what’s happening here. The four div tags near the bottom of the script make up the display of the variable. Inside those divs we have four input boxes, one each for entering days, hours, minutes and seconds. When the user types a value into one of the boxes a script is executed, as defined in the onblur event on each of those input tags. The durationCalc function call passes two variables;the ID of the input tag and a maximum value for entry.

Moving upwards to the script tag, we can see the code that is executed in the durationCalc function. Firstly, a check takes place to ensure that the user has entered a number, and another check occurs to ensure that the number does not exceed the defined maximum, i.e. 59 seconds or 23 hours.

Below those checks we can see there’s a bunch of calculations going on. Now we know we are dealing with whole numbers, these sums basically work out the total numerical value of the days, hours minutes and seconds in milliseconds. We then store that value in a string variable that we keep hidden away on the form. More on that in the next section!

The styles used on this macro mimic the UI16 styling for input boxes. Although this will mean some manual work to update the tags should the ServiceNow UI change in a future upgrade, using any base platform classes on our input tags would bring some inherent behavior that we don’t want on our fields. There is also the risk that those tags could become deprecated one day, so from a design perspective it’s a minor victory.

Variable Set

With the build of the variable specification done, we can think about how to incorporate this variable on a catalog item. Two make this functionality work, we need two variables, a catalog UI policy and a catalog client script, so we should store them in a variable set so we can keep them bundled together. This helps with maintenance as we can see the parts that makeup this functionality, walled off from the rest of our code. Also, we can then use this variable on multiple catalog items without having to duplicate code. Double-yay.

The following section covers the components listed above and what they contain.

Variable 1

Name: duration
Label: Duration
Type: UI Macro
Macro: AD_duration_catalog_variable

This variable simply displays the UI macro that we’ve just developed.

Variable 2

Name: duration_hidden
Label: Duration (Hidden)
Type: Multi line text

The durationCalc function in our UI macro script pastes the calculated value in milliseconds that form the duration entered. The reason for using milliseconds is that this way is agnostic to any date-time formats that may be used across different instances. Using the GlideDateTime API’s we can also use a script to manipulate this value elsewhere in the platform should that be necessary.

Catalog UI Policy

Name: AD Hide duration string
Condition: None
Advanced view:
Applies on catalog item view: true
Applies on Requested Items: true
Applies on Catalog Tasks: true
UI Policy action:
Field: duration_hidden
Read only: Leave alone
Visible: False
Mandatory: Leave alone
Hides the string variable storing the time in milliseconds from the form.

Catalog Client Script

Name: AD Set duration field
Type: onLoad
Applies on catalog item view: true
Applies on Requested Items: true
Applies on Catalog Tasks: true
Used to display the duration entered by the user on Request Items, Catalog Tasks and when editing a cart item. This script pulls the numerical value from the duration_hidden string field and performs a quick calculation to display the days, hours, minutes and seconds on the form.

function onLoad() {

  var total = g_form.getValue("duration_hidden");
  if(total != "") {
    var secs = (total % (60 * 1000)) / 1000;
    total = total - (secs * 1000);
    var mins = (total % (60 * 60 * 1000)) / (1000 * 60);
    total = total - (mins * 1000 * 60 );
    var hours = (total % (24 * 60 * 60 * 1000)) / (1000 * 60 * 60);
    var days = parseInt(total / (24 * 60 * 60 * 1000));
    document.getElementById("dur_days").value = days;
    document.getElementById("dur_hours").value = hours;
    document.getElementById("dur_mins").value = mins;
    document.getElementById("dur_secs").value = secs;
  }
}

Recent Comments

Leave a Reply

Your email address will not be published. Required fields are marked *