The ServiceNow Nerd

The musings of a ServiceNow platform developer and enthusiast

The SN Nerd

How to make UI Scripts work in all User Interfaces

by snnerd
1,200 views

In How to convert UI Scripts to run in Configurable Workspaces, we learned how to create UI Scripts that work in Configurable Workspaces (WEP) and Service Portal. The article was so big I had to split it into two! In this blog, you will learn how to write UI Scripts that work in both WEP and Classic UI.

UI Script

In my last article, we copied an existing UI Script made for the Classic UI and made it compatible with WEP. For reference, the script is shown below:

(function() {

    var Greetings = function(/*Your globals here */g_form, g_user) {
        var sn_nerd = sn_nerd || {};

        sn_nerd.greetings = (function() {

            function getFullName() {
                return g_user.getFullName();
            }

            return {
                hello: function() {
                    g_form.addInfoMessage("Hello " + getFullName());
                },
                goodbye: function() {
                    g_form.addInfoMessage("Goodbye " + getFullName());
                },
            type:'Greetings'
            };

        })();

        return sn_nerd;
    };

    function initialiseScript(g_form, g_user) {
      // Return intialise script to client script so globals can be passed in
        globalThis.sn_nerd = Greetings(g_form, g_user);
    }

   return initialiseScript;

})();

This script does not work in Classic UI. Let’s modify it so it works in both.

First, we must change the UI Type to All to make our UI Script cross-compatible with Classic UI.

Changin UI Type to All to allow UI Script to run in WEP and Classic UI

‘this’ is confusing

We need to initialise our script differently for Classic UI and WEP.

For WEP, we use globalThis to make our API accessible to all Client Scripts and return it to initialise and pass in our globals that are not accessible in UI Scripts for WEP.

function initialiseScript(g_form, g_user) {
      // Return intialise script to client script so globals can be passed in
        globalThis.sn_nerd = Greetings(g_form, g_user);
    }

   return initialiseScript;

We don’t need to return the initialiser function or pass in our globals in Classic UI as we already have access to them.

But how do we tell from the UI Script if we are in Classic UI or WEP?

Let’s log out the differences for this, global and globalThis as shown below:

(function(global) {

  // Log object to console to see the difference
  console.log('this');
  console.log(this);
  console.log('global');
  console.log(global);
  console.log('globalThis');
  console.log(globalThis);
  
  /* Your UI Script Code here */
  
})(this);

The console outputs are as follows:

Output of globals to console in Classic Environment
Output of globals to console in WEP

In our UI Script we can, therefore determine if we are running in the Classic UI or WEP by checking global to see if it is a function or an object.

 var isClassicUI = (typeof global == 'object');
    if (isClassicUI) {
        initialiseScript(g_form, g_user);
    } else {
        // In Configurable Workspaces (WEP) UI Scripts run in sandbox without access to globals such as g_form and g_user
		// Return the initialise function so it can be used in the calling script to pass the globals
        return initialiseScript;
    }

    function initialiseScript(g_form, g_user) {
        globalThis.sn_nerd = Greetings(g_form, g_user);
    }

You could also do this by checking if other globals, such as g_form and g_user are defined.

(function() {

    var Greetings = function(/*Your globals here */g_form, g_user) {
       // Original UI Script code here
    };

    var isClassicUI = (typeof g_form != 'undefined' && typeof g_user != 'undefined');
    if (isClassicUI) {
        initialiseScript(g_form, g_user);
    } else {
        // In Configurable Workspaces (WEP) UI Scripts run in sandbox without access to globals such as g_form and g_user
		// Return the initialise function so it can be used in the calling script to pass the globals
        return initialiseScript;
    }

    function initialiseScript(g_form, g_user) {
        globalThis.sn_nerd = Greetings(g_form, g_user);
    }

})();

I would probably do the above as it is less confusing, but it was interesting to explore how UI Scripts differ with my first method.

Consolidate

Because g_user.fullName is not available in the WEP version of GlideForm, we are opting to use an available function. In the event that you are calling a function not available to both API’s, you can try both of them.

return g_user.getFullName(); // Used by WEP and Classic UI

return g_user.fullName || g_user.getFullName(); // If fullName fails, try getFullName()

// Check if methods are undefined
if (typeof g_user.fullName != 'undefined') {
  return g_user.fullName;
} else if (typeof g_user.getFullName != 'undefined') {
  return g_user.getFullName();
} else {
  return 'Guest'; 
}
  
  

Our final UI Script looks like this:

(function() {

    var Greetings = function(/*Your globals here */g_form, g_user) {
        var sn_nerd = sn_nerd || {};

        sn_nerd.greetings = (function() {

            function getFullName() {
                return g_user.getFullName();
            }

            return {
                hello: function() {
                    g_form.addInfoMessage("Hello " + getFullName());
                },
                goodbye: function() {
                    g_form.addInfoMessage("Goodbye " + getFullName());
                },
				type:'Greetings'
            };

        })();

        return sn_nerd;
    };

    var isClassicUI = (typeof g_form != 'undefined' && typeof g_user != 'undefined');
    if (isClassicUI) {
        initialiseScript(g_form, g_user);
    } else {
        // In Configurable Workspaces (WEP) UI Scripts run in sandbox without access to globals such as g_form and g_user
		// Return the initialise function so it can be used in the calling script to pass the globals
        return initialiseScript;
    }

    function initialiseScript(g_form, g_user) {
        globalThis.sn_nerd = Greetings(g_form, g_user);
    }

})();

Client Script

Similar to the UI Script, we must change the UI Type to All.

Depending on the UI, we need to use the appropriate function to load in UI scripts.

function onLoad() {
	var scriptName = "Greetings";
    if (typeof g_ui_scripts != "undefined") {
        g_ui_scripts.getUIScript(scriptName).then(function(initScript) {
            initScript(g_form, g_user);
            sayHello();
        });
    } else {
		ScriptLoader.getScripts(scriptName + ".jsdbx", sayHello);
    }

    function sayHello() {
        sn_nerd.greetings.hello();
    }

}

Chain loading

Unlike ScriptLoader, g_ui_scripts API can only load one UI Script at a time. If you need to load in multiple UI Scripts and run some wrap-up code, you can chain-load them using the following pattern:

 var scripts = ['Script1', 'Script2', 'Script3'];

    // Chain loading of scripts
    function loadScript(loadScripts) {
        g_ui_scripts.getUIScript(loadScripts[0]).then(function(script) {
            console.log('Script ' + loadScripts[0] + ' loaded');
            var nextScript = loadScripts.shift(); //Remove first element
            if (typeof nextScript != 'undefined') {
		console.log('Attempt to load next script ' + loadScripts[0]);
                loadScript(loadScripts);
            } else {
                allScriptsLoaded();
            }
        });

    }

    function allScriptsLoaded() {
        console.log("Scripts Loaded");
    }

    loadScript(scripts);

TLDR

If you do not want to manage separate Client Scripts and UI Scripts for WEP and Classic UI, you can combine them. You need to:

  • Change Client Script and UI Script UI Type to All
  • Handle UI Script Initialisation based on UI Type
    • Return an initialiser function to set globals for WEP
    • Use globalThis for both
  • Handle UI Script loading in Client Script based on UI Type
    • ScriptLoader for Classic UI
    • g_ui_scripts for WEP
      • Chain load for multiple with dependencies
  • Consider and handle any API differences for global objects
    • g_form and g_user may have different methods
    • g_user_date_time_format for Classic UI
    • g_date_time_format for WEP
    • Be wary of other differences that may occur.

While it takes more effort to create scripts for compatibility, if you manage forms in Classic UI and WEP, you can use the above techniques to combine your scripts to make them easier to manage.

Related Posts

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More