Maik Skoddow
Tera Patron
Tera Patron
MaikSkoddow_0-1672560719213.png

 

Let's assume there is a requirement to offer users in your ServiceNow instance with activated Next Experience UI a quick link to any already existing information page outside your ServiceNow instance. 

 

The most prominent place for such a link would be the header bar, where we already find central functionalities like the combined Application & Update Set picker (globe icon) or the notification display (bell icon).

 

Unfortunately, there is no OOTB configuration available to inject another icon into the header bar. So the only way is extending the HTML code manually. And the only approach to implement something like this is creating a global UI script which waits until finished page loading to inject the required HTML code accordingly.

 

But first you have to decide for a certain icon. You can choose one at at https://developer.servicenow.com/dev.do#!/reference/next-experience/vancouver/now-components/now-ico... 

 

MaikSkoddow_1-1672561675273.png

 

Please note the corresponding ID of the icon (see red rectangle in the above screenshot) to insert it into the code (at number 1) I have provided below.

 

MaikSkoddow_0-1672569505986.png

Then create a UI Script with the following properties:

 

API Name

(give any name)

 

UI Type

Desktop

 

Global

(checked)

 

Script

var injectCustomIcon = function (strIconId, strHtmlId, strHtmlTitle, strOnClick) {
	try {
		//is Next Experience UI loaded?
		if (!(top.NOW && top.NOW.isPolarisWrapper === "true")) {
			return;
		}

		//is the icon already injected?
		if (typeof window.top.custom_icon_injected !== 'undefined') {
		  return;
		}		

		var objElement = window.top.document;

		//search for the DOM element for the icon bar
		[	
			'macroponent-f51912f4c700201072b211d4d8c26010', 'shadowRoot',
			'sn-polaris-layout', 'shadowRoot',
			'sn-polaris-header', 'shadowRoot',
			'.utility-menu'
		].forEach(function (strId) {
			if (!objElement) {
				return;
			}

			objElement = strId === 'shadowRoot' ? objElement.shadowRoot : objElement.querySelector(strId);
		});

		//inject HTML code for the custom icon
		if (objElement) {
			jQuery(objElement).prepend(
				'<span role="button" tabindex="0" class="contextual-zone-button polaris-enabled" ' +
					'id="' + strHtmlId + '" aria-label="' + strHtmlTitle + '" ' + 
					'onclick="' + strOnClick + '" ' +
					'title="' + strHtmlTitle + '" ' +
					'aria-describedby="contextual_zone_' + strHtmlId + '_button" ' +
					'aria-expanded="false"' + 
				'>' +
					'<now-icon class="contextual-zone-icon" icon="' + strIconId + '" dir="ltr"></now-icon>' +
					'<now-tooltip ' +
						'id="' + strHtmlId + '-tooltip" ' +
						'aria-label="' + strHtmlTitle + '" ' +
						'role="tooltip" dir="ltr" aria-hidden="true">' +
					'</now-tooltip>' +
				'</span>'		
			);

			top.custom_icon_injected = true;
		}
	}
	catch (e) {
		console.error(e);
	}
}

//after finished loading the page inject custom icon 
if (typeof jQuery === 'function') {
	jQuery(document).ready(
		injectCustomIcon(
			/* (1) ad the ID of the chosen icon */
			'circle-info-outline', 
			
			/* (2) HTML ID of the created icon */
			'my_custom_icon', 
			
			/* (3) text to display when hovering over the icon */
			'My custom icon. Click to action!', 
			
			/* (4) performed action when clicking on the icon */
			'window.open(\'https://www.servicenow.com\', \'_blank\');' 
		)
	);
}

 

At the end of the above script, you can see four parameters for the method injectCustomIcon() you have to adapt to your needs.

Comments
Robert Fauver
Tera Contributor

Thanks for this Maik. I'm curious as to how you recommend running the script.

 

Should the script run...

- from a form

- call it in the HTML

- or call the script from client side code?

 

 

Maik Skoddow
Tera Patron
Tera Patron

Hi @Robert Fauver 

you just create the UI Script as described by me. ServiceNow will then execute that UI Script each time you open any page.

fauverism
Kilo Sage
Kilo Sage

Thanks for the quick response @Maik Skoddow but unfortunately that didn't work for me.

 

I wrote the code by hand initially and pasted what you wrote after that didn't work. I put an alert() in the function and did a search for the classes of the element in the DOM. Neither worked.

 

I'll keep at it and post what I find. Thanks again for posting this. Very cool!

Cuneyt Bozok
Tera Guru

@Maik Skoddow thank you for your great content but I have a question, how can we make it work for pages like "pa_dashboards_overview", those pages are rendered in an iframe, and UI scripts don't look like running in there. 

 

"/now/nav/ui/classic/params/target/%24pa_dashboards_overview.do"

"/now/nav/ui/classic/params/target/%24ciModel.do"

 

 

Jeffrey Siegel
Mega Sage

I am in the Tokyo release, and i literally copied your code and pasted it into a UI Script, and it doesnt show anything.  am i doing something wrong?

once i get it to work ill customize those last 4 lines, but no point in doing that if i cant get it to work..

 

JeffreySiegel_0-1678814891564.png

 

 

EDIT:
after playing with alerts, this is kicking me out: 

 

if (!(top.NOW && top.NOW.isUsingPolaris && top.NOW.isPolarisWrapper === "true")) {
		return;
	}

 

i am on the new next experience, why would that be kicking me out?

 

Maik Skoddow
Tera Patron
Tera Patron

Hi @Jeffrey Siegel 

 

Many thanks for this find. It seems that 

top.NOW.isUsingPolaris

 doesn't exist anymore.

 

Therefore, I have removed it from the code and also enclosed everything in a try-catch-block because a broken UI Script can prevent you from logging into your instance.

Maik Skoddow
Tera Patron
Tera Patron

Hi @Cuneyt Bozok 

 

Yes this is right. If you enter the instance via a deep link to a Dashboard then the UI Script is not triggered. Only when opening a list or form view the UI Script will kick in. This cannot be changed and other extensions like "XPlore" have the same issue.

 

The only workaround I can imagine is placing a static HTML block on your dashboard that executes the given code.

Jeffrey Siegel
Mega Sage

@Maik Skoddow 

i got around the check for next experience ui by using the user preferences to see if they are on the new experience:

 

 

 var ga2 = new GlideAjax("CheckUser");
    ga2.addParam('sysparm_name', 'u_NextExperience');
    ga2.getXMLWait();
    var Experience = ga2.getAnswer();
    //is Next Experience UI loaded?
    if (!(Experience === "true")) {
        return;
    }

 

 

CheckUser SI: (Client Callable, global, all application scopes)

 

var CheckUser = Class.create();
CheckUser.prototype = Object.extendsObject(AbstractAjaxProcessor, {
	u_user: function() {
		return gs.getUserID();
	},
	u_userName: function(){
		return gs.getUserName();
	},
	
	u_hasRole: function(role) {
   if(role == undefined || role=="")role = this.getParameter("sysparm_role");
   return gs.hasRole(role);
	},
	
	u_NextExperience: function(){
		var userPreference = gs.getUser().getPreference('glide.ui.polaris.use');
		return userPreference;
		
	},
    type: 'CheckUser'
});

 

 

F Steiner
Tera Contributor

Hi,

Somebody know how to, still add an icon, but at another place? because if you try to replace "prepend" by "append"

jQuery(objElement).append( ..... )

all sub-menus are shifted to the top arrow (in this example, when i click on the "user" icon, the arrow is shifter)

Pasted image.png

 

My try is to add a new icon, between the "notification" icon and the "user" icon

Thanks

ashwinishedge
Tera Contributor

@Maik Skoddow, I truly appreciate the helpful script you provided! I’m excited to see more like this in the future. Thank you!.

saikumarred
Tera Explorer

@Maik Skoddow This script is causing overflow menu issues and profile icon visibility issue on the global header if the screen resolution is small like in laptop screens.

 

I understand DOM manipulation is not supported by ServiceNow.

 

is there any way we can fix the issues?

Version history
Last update:
‎01-25-2024 07:53 PM
Updated by:
Contributors