Avoiding code duplication in Catalog Client Scripts

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

 

Andrew Hunt and David Thomas

The Pragmatic Programmer

 

One of the first questions I asked when I started working with the Service Catalog in Servicenow was how to share some common functionality among Catalog Client Scripts in order to avoid code duplication.

It is not unusual to have some piece of code that needs to be run in different Catalog Client Scripts either in the same or a different Catalog Item (i.e. validating some data).

I haven’t found the perfect solution for this issue yet. Moreover, it has become even more challenging when having to make it work for the Service Portal, where UI Macros are not rendered and Catalog Client Scripts are encapsulated.

In this post I try to summarise the different possibilities.

1. Global UI Script

  1. Create a UI Script with your functions
  2. Set global to true
  3. (For Service Portal) Add it to your Service Portal Theme
  4. Call the function from a Catalog Client Script (i.e. onChange)
function testCatalogUIScript(test){
    alert(test);
}

 

function onChange(control, oldValue, newValue, isLoading) {
   if(isLoading)
       return;
   testCatalogUIScript('Test');
}

Warning: Using global UI Scripts is a bad practice.

2. Macro Variable

  1. Create a UI Script with your functions
  2. Create a UI Macro linking to the UI Script
  3. (For Service Portal) Create a Widget adding the UI Script as a dependency
  4. Create a Variable with type = Macro
  5. Link the variable to the UI Macro
  6. (For Service Portal) Link the Variable to the Widget
  7. Hide the variable with a UI Policy
  8. Call the function from a Catalog Client Script (i.e. onChange)
  9. (Optional) Add the variable to a Variable Set if you want to share the functionality among different Catalog Items
function testCatalogUIScript(test){
    alert(test);
}

 

<?xml version="1.0" encoding="utf-8" ?>  
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">  
    <g:requires name="CatalogUIScriptTest.jsdbx"/>  
</j:jelly>

 

function onChange(control, oldValue, newValue, isLoading) {
   if(isLoading)
       return;
   testCatalogUIScript('Test');
}

3.onLoad Catalog Client Script

  1. Define your functions in an onLoad Catalog Client Script
  2. Call the function from a different Catalog Client Script (i.e. onChange)
  3. (Optional) Add the onLoad Catalog Client Script to a Variable Set if you want to share the functionality among different Catalog Items
function onLoad() {}

this.testCatalogClientScript = function(test) {
    alert(test);
};

testCatalogClientScript2 = function(test) {
    alert(test);
};

function testCatalogClientScript3(test) {
    alert(test);
}

 

function onLoad_b4d17eae0f233200c620b17ce1050e7f() {}

//The three functions will be visible to other scripts

this.testCatalogClientScript = function(test) {
    alert(test);
};

testCatalogClientScript2 = function(test) {
    alert(test);
};

function testCatalogClientScript3(test) {
    alert(test);
}


addRenderEvent(onLoad_b4d17eae0f233200c620b17ce1050e7f);

 

(function(g_form, g_scratchpad, g_user, g_modal, window, document, $, jQuery, $$, $j, angular, snmCabrillo, cabrillo, GlideAjax, GlideRecord, getMessage, getMessages, g_service_catalog, spModal, g_list/*``*/
) {
    return (function anonymous(/*``*/
    ) {
        function onLoad() {
        }
        
        //this is a reference to the window object
        this.testCatalogClientScript = function(test) {
            alert(test);
        };
        
        //check the implicit globals link below
        testCatalogClientScript2 = function(test) {
            alert(test);
        };
        
        //This one won't be visible to other scripts
        function testCatalogClientScript3(test) {
            alert(test);
        }

        return onLoad.apply(this, arguments);
    })()
}
)

 

function onChange(control, oldValue, newValue, isLoading) {
    if(isLoading)
        return;
    testCatalogClientScript('Test');
    testCatalogClientScript2('Test2');
    testCatalogClientScript3('Test3'); //This won't work in the Service Portal as it is not visible outside the anonymous function
}
Warning: functions need to be declared as expressions in order for them to work in the Service Portal. They are rendered inside an anonymous function, so they need to be made available to the outside.
Some info on implicit globals.

Conclusions

There is not a perfect solution. Number 3 is my favourite one so far, even though it required some adjustments to make it work in the Service Portal.

It is up to you! Choose one of the options, be consistent with it and don’t repeat yourself. Your future self will thank you!

Leave a Comment