How to clone and configure Service Portal Header?

by Gratsiela Georgieva, Senior Technical ServiceNow Consultant, Do IT Wise 

Cloning the Service Portal Header is one of the most time-consuming and most complex configurations regarding the portal. This article will explain more about the problem and will describe the steps how to solve it. 
 

Problem 

Sometimes the clients’ business logic requires some header tabs/menu items to be added or removed from the portal’s header. Before revealing the solution, let’s discuss the Service Portal Header Structure. 

Here is an image representation of the hierarchy of the Service Portal Header. 

Hierarchy of the Service Portal Header.

Layer 1:
You can explore this structure easily by going to Service Portal > Portals > Open portal with name = Service Portal or URL suffix = sp. This is the root node of the diagram. 

Layer 2: 
On the second layer, we have Main Menu and Theme. These two nodes are represented by the fields “Main Menu” and “Theme” on the Service Portal form. 

Layer 3: 
If you open the main menu, you will see on the form a field “Widget”, which refers to the baseline “Header Menu” widget, and also will see that at the bottom of the structure, there is a related list with the menu items. These menu items are some of the tabs visible in the header. The missing tabs (not available in the related list) such as Wish list, Cart, and Tours (if you see them, depends on the logged-in user roles) are “hard-coded” in “Header Menu” widget. They can be displayed or hidden depending on different system properties and conditions. The out of the box “Header Menu” widget is responsible for displaying all these menu items in a proper format depending on their type (URL, Knowledge Base, Scripted List, Page, and so on). 

If we open the theme, you will see two fields on the form – “Header” and “Footer”. Baseline, “Header” field refers to out of the box widget “Stock Header”, which is used as a wrapper for the header logo, title (text next to the logo) and User profile settings menu tab. It “calls” the widget, which loads the menu tabs in the header. By default, the “Footer” field is empty, which means that the Service Portal does not have a footer out of the box. At the bottom of the theme form, there is a related list with CSS includes. They are used for adding additional style sheets to the theme. 

Layer 4: 
If you open the “Header Menu” widget on the main menu form, you will see related lists with a dependency with one JS include and 3 Angular templates. All these artifacts are used in addition to the code in the widget.  If we open the “Stock Header” widget on the theme form, you will see a related list with one Angular template. 

Solution

After we covered the ServiceNow Portal Header structure, we can continue with the steps needed to clone the Service Portal Header. 

Here they are: 

Note: In the example below, we are using my initials “GG” as a prefix for the new artifactsIn your case, something else will be more meaningful. Please replace the suggested initials with another prefix. 

1. Select the portal from Service Portal > Portals > Open portal with name = Service Portal or URL suffix = sp 

Service Portal

2. Copy the baseline artifacts – the instance with menu, called “SP Header Menu”, and the theme, called “La Jolla”, in “Main menu” and “Theme” fields. Both artifacts can be copied with “Insert and stay” button. You can name the copied artifacts respectively – “GG – SP Header Menu” and “GG – La Jolla”. 

SP Header Menu

Note: Keep in mind that when you clone the instance menu “SP Header Menu”, the menu items from the original artifact are not cloned. You need to recreate them. 

3. In the new theme “GG – La Jolla” add the same CSS includes as the ones that are already related to the original theme “La Jolla”. 

GG - La Jolla

4. In the cloned instance menu, “GG – SP Header Menu” in the “Widget” field is still the original widget “Header Menu”. We need to clone this widget and for example, rename it to “GG – Header Menu”. After the widget is cloned, please relate “GG – SP Header Menu” to the newly cloned widget “GG – Header Menu”. This can be done by changing the value in the “Widget” field in the instance menu “GG – SP Header Menu”. 

Widget

5. During the cloning on “GG – Header Menu”, the original dependency “angular-truncate” stays related, which is good. But what is missing are the Angular templates. Let’s create 3 Angular templates: 

5.1. GGmenuTemplate with the following template: 

<a role="menuitem" ng-if="item.items.length == 0 && !item.scriptedItems" ng-href="{{::item.href}}" ng-click="collapse()" target="{{::item.url_target}}" title="{{::item.hint}}">
  <fa ng-if="::item.glyph" name="{{::item.glyph}}"></fa>
  <span ng-bind-html="::item.label"></span>
</a>
<a role="menuitem" aria-haspopup="true" aria-expanded="false" ng-if="item.items.length > 0" href class="dropdown-toggle sp-menu-has-items" data-toggle="dropdown" aria-controls="{{::item.label.split(' ').join('')}}" aria-haspopup="true" title="{{::item.hint}}">
  <fa ng-if="::item.glyph" name="{{::item.glyph}}"></fa>
  <span ng-bind-html="::item.label"></span> <span class="caret"></span>
</a>
<ul ng-if="item.items.length > 0" class="dropdown-menu" role="menu" id="{{::item.label.split(' ').join('')}}">
  <li ng-repeat="item in item.items" ng-include="'menuTemplate'" role="presentation" />
</ul>
<a role="menuitem" aria-haspopup="true" aria-expanded="false" ng-if="item.scriptedItems.count > 0" href data-toggle="dropdown" aria-label="{{::item.label ? item.label + ': ' : ''}}{{::item.hint ? item.hint + ': ' : ''}}{{item.scriptedItems.count}}" title="{{::item.hint}}">
  <fa ng-if="::item.glyph" name="{{::item.glyph}}"></fa>
  <span ng-bind-html="::item.label"></span>
  <span ng-if="::!item.scriptedItems.omitBadge" class="label label-as-badge label-primary sp-navbar-badge-count">{{item.scriptedItems.count}}</span>
</a>
<sp-dropdown-tree role="menu" aria-label="{{::item.label}}" ng-if="item.scriptedItems.count > 0" items="item.scriptedItems.items" />

5.2. GG-item-added-tooltip.html with the following template: 

<div ng-click="toggleCart()" class="item-added-tooltip">${Item has been added to your cart.}</div>

5.3. spDropdownTreeTemplateGG with the following template: 

<a ng-if="mi.type == 'link'" title="{{::mi.title}}" target="{{::mi.target}}" ng-href="{{::mi.href}}" ng-click="collapse()" role="menuitem">
  {{::mi.title | characters:60}}
</a>
<a ng-if="mi.type == 'record' && !mi.__page" aria-label="${Open} {{::mi.number}} : {{::mi.short_description}}" aria-describedby="id_{{::mi.unique_number}}" ng-href="?id=ticket&table={{::mi.__table}}&sys_id={{::mi.sys_id}}" ng-click="collapse()" role="menuitem">
  <span>{{::mi.short_description | characters:60}}</span>
  <span class="block color-primary text-muted">
    <span class="block" style="float: right" id="id_{{::mi.unique_number}}">
      <sn-time-ago timestamp="::mi.sys_updated_on" />
    </span>
    {{mi.number}}
  </span>
</a>
<a ng-if="mi.type == 'record' && mi.__page" aria-label="${Open} {{::mi.number}} : {{::mi.short_description}}" aria-describedby="id_{{::mi.unique_number}}"  ng-href="?id={{::mi.__page}}&table={{::mi.__table}}&sys_id={{::mi.sys_id}}" ng-click="collapse()" role="menuitem">
  <span>{{::mi.short_description | characters:60}}</span>
  <span class="block color-primary text-muted">
    <span class="block" style="float: right" id="id_{{::mi.unique_number}}">
      <sn-time-ago timestamp="::mi.sys_updated_on" />
    </span>
    {{::mi.number}}
  </span>
</a>
<a ng-if="mi.type == 'request'" aria-label="${Open} {{::mi.number}} : {{::mi.short_description}}" aria-describedby="id_{{::mi.unique_number}}"  ng-href="?id=sc_request&table={{::mi.__table}}&sys_id={{::mi.sys_id}}" ng-click="collapse()" role="menuitem">
  <span>{{::mi.short_description | characters:60}}</span>
  <span class="block color-primary text-muted">
    <span class="block" style="float: right" id="id_{{::mi.unique_number}}">
      <sn-time-ago timestamp="::mi.sys_updated_on" />
    </span>
    {{::mi.number}}
  </span>
</a>
<a ng-if="mi.type == 'approval'" aria-label="${Open} {{::mi.number}} : {{::mi.short_description}}" aria-describedby="id_{{::mi.unique_number}}"  ng-href="?id=approval&table={{::mi.__table}}&sys_id={{::mi.sys_id}}" ng-click="collapse()" role="menuitem">
  <span ng-if="mi.short_description">{{::mi.short_description | characters:60}}</span>
  <span class="block color-primary text-muted">
    <span class="block" style="float: right" id="id_{{::mi.unique_number}}">
      <sn-time-ago timestamp="::mi.sys_updated_on" />
    </span>
    {{::mi.number}}
  </span>
</a>
<a ng-if="mi.type == 'menu' && mi.items.length" aria-label="{{::mi.title}}" ng-click="collapse()" class="menu_trigger right-caret" role="menuitem">
  {{::mi.title | characters:60}}
</a>
<sp-dropdown-tree ng-if="mi.type == 'menu' && mi.items.length" items="mi.items" />

6. In widget “GG – Header Menu” replace menuTemplate with GGmenuTemplate at this line: 

<li ng-repeat="item in item.items" ng-include="'menuTemplate'" role="presentation" />

It should look like this: 

<li ng-repeat="item in item.items" ng-include="'GGmenuTemplate'" role="presentation" />

7. In widget “GG – Header Menu” replace item-added-tooltip.html with GG-item-added-tooltip.html at this line: 

<a href
       data-toggle="dropdown"
       id="cart-dropdown"
       uib-tooltip-template="'item-added-tooltip.html'"...

It should look like this: 

<a href
       data-toggle="dropdown"
       id="cart-dropdown"
       uib-tooltip-template="'GG-item-added-tooltip.html'"

8. In widget “GG – Header Menu” create a new Angular provider with the name “spDropdownTreeTemplateGG” and client script: 

function () {
	return {
		restrict: 'E',
		scope: {items: '='},
		replace: true,
		template: '<ul class="dropdown-menu">' +
		'<li ng-repeat="mi in items" style="min-width: 20em;" ng-class="{\'dropdown-submenu\': mi.type == \'menu\', \'dropdown-menu-line\':$index < items.length - 1}" ng-include="getURL()">' +
		'</ul>',
		link : function(scope, element, attrs, controller) {
			scope.getURL = function() {
				return 'spDropdownTreeTemplate';
			}
		}
	}
};
(function($) {
	$("body").on( "click", "a.menu_trigger", function(e) {
		var current = $(this).next();
		var grandparent = $(this).parent().parent();
		if ($(this).hasClass('left-caret') || $(this).hasClass('right-caret'))
			$(this).toggleClass('right-caret left-caret');
		grandparent.find('.left-caret').not(this).toggleClass('right-caret left-caret');
		current.toggle();
		$(".dropdown-menu").each(function(i, elem) {
			var elemClosest = $(elem).closest('.dropdown');
			var currentClosest = current.closest('.dropdown');
			if (!elem.contains(current[0]) && elem != current[0] && (!currentClosest.length || !elemClosest.length || elemClosest[0] == currentClosest[0]))
				$(elem).hide();
		})
		e.stopPropagation();
	});
	$("body").on( "click", "a:not(.menu_trigger)", function() {
		var root=$(this).closest('.dropdown');
		root.find('.left-caret').toggleClass('right-caret left-caret');
	});
})(jQuery);
GG Header Menu Widget

9. In the newly created Angular provider “spDropdownTreeTemplateGG” replace spDropdownTreeTemplate with spDropdownTreeTemplateGG at this line: 

scope.getURL = function() {
				return 'spDropdownTreeTemplate';
			}

It should look like this: 

scope.getURL = function() {
				return 'spDropdownTreeTemplateGG';
			}

10. In angular template “GGmenuTemplate” related to widget “GG – Header Menu” replace sp-dropdown-tree with sp-dropdown-tree-template-GG at line: 

<sp-dropdown-tree role="menu" aria-label="{{::item.label}}" ng-if="item.scriptedItems.count > 0" items="item.scriptedItems.items" />

It should look like this: 

<sp-dropdown-tree-template-GG role="menu" aria-label="{{::item.label}}" ng-if="item.scriptedItems.count > 0" items="item.scriptedItems.items" />

11. Let’s continue with the theme in the Service Portal record. At step 1, we have already cloned the theme and named it “GG – La Jolla”, but still in the new theme, the“Header” field refers to the baseline “Stock Header” widget. What we need to do now is first to clone out of the box widget “Stock Header” and for example to name it “GG – Stock Header” and second to change the reference in the theme “GG – La Jolla” from “Stock Header” to “GG – Stock Header” 

Stock Header

12. In the newly cloned widget “GG – Stock Header” add a new Angular template, called “GGmodalLogin” with the following template: 

<div class="login_widget">
  <sp-widget widget="data.loginWidget"></sp-widget>
  <style>
    .modal-content { border: 0px solid transparent; }
  </style>
</div>
GG - Stock Header Widget

13. In the client script of the newly cloned widget “GG – Stock Header” replace modalLogin with GGmodalLogin at line: 

$scope.modalInstance = $uibModal.open({
			templateUrl: 'modalLogin',
			scope: $scope
		});

It should look like: 

$scope.modalInstance = $uibModal.open({
			templateUrl: 'GGmodalLogin',
			scope: $scope
		});

14. Finally, after all, when previous steps are complete, we need to change the theme and main menu references in the Service Portal record. So, go to Service Portal > Portals > Open portal with name = Service Portal or URL suffix = sp, and change the “Main menu” field to refer to “GG – SP Header Menu” and “Theme” field to refer to “GG – La Jolla”.

Service Portal

Do you find this article helpful? If you enjoy such content, check out our article on implementing Google Analytics on a ServiceNow Portal page

Start typing and press Enter to search

Shopping Cart