“Do not wait; the time will never be ‘just right.’ Start where you stand, and work with whatever tools you may have at your command, and better tools will be found as you go along.”
— George Herbert
I’ve had this idea for a while to attempt a different approach to Service Portal breadcrumbs, and I finally quit tinkering with my Data Table clones and Configurable Content Selector long enough to actually throw something together. My issue with the out-of-the-box breadcrumb widget is that you have to tell it what the breadcrumbs are on every page rather than the system keeping track of where you are and how you got there. It seemed to me that it would not only be easier to set up, but it would also be more accurate, since there are often times more than one path to get to a specific page.
To keep track of the current page stack for the breadcrumbs, I decided to leverage the existing User Preferences infrastructure. User Preferences are accessible in the Service Portal via built-in GlideSystem functions, and provide a convenient means to keep track of a user’s path through the various screens in the portal. To fetch a User Preference, you use the gs.getPreference(key) method, and to update a User Preference, the script is gs.getUser().setPreference(key, value).
To begin, I pulled up the existing breadcrumb widget and created a clone that I called SNH Breadcrumbs. I did not want to change the way the breadcrumbs were displayed, so I left the HTML portion of the widget intact. I did not want to set the value of the breadcrumbs via widget option anymore, though, so I removed the option. Then I modified the server-side script to create a label for the current page and pull the current breadcrumbs out of the User Preferences. I also provided the means to update the breadcrumbs when an update was invoked on the client side. The complete server-side script now looks like this:
(function() {
if (input) {
if (input.breadcrumbs) {
gs.getUser().setPreference('snhbc', JSON.stringify(input.breadcrumbs));
}
} else {
data.table = $sp.getParameter('table');
data.sys_id = $sp.getParameter('sys_id');
if (data.table) {
var rec = new GlideRecord(data.table);
if (data.sys_id) {
rec.get(data.sys_id);
data.page = rec.getDisplayValue('number');
if (!data.page) {
data.page = rec.getDisplayValue('name');
}
if (!data.page) {
data.page = rec.getDisplayValue('short_description');
}
if (!data.page) {
data.page = rec.getLabel();
}
} else {
data.page = rec.getPlural();
}
}
data.breadcrumbs = [];
var snhbc = gs.getPreference('snhbc');
if (snhbc) {
data.breadcrumbs = JSON.parse(snhbc);
}
}
})();
The page label is based on the URL parameters table and sys_id. If both are present, I go ahead and grab the record and attempt to obtain a label from the data. If only the table parameter is present, then I assume that we are talking about multiple records, so I grab the Plural label for the table itself. If there is no table parameter, then I let the client-side script handle the label for the page. On the client side, I build a breadcrumb entry for the current page, and then loop through the existing breadcrumbs to see if this page is already in the list. If it is, then that’s where we will stop; otherwise, we will just tack the new current page entry on to the end of the existing stack of pages. Here is the complete client-side script:
function($scope, $rootScope, $location, spUtil) {
var c = this;
c.expanded = !spUtil.isMobile();
c.expand = function() {
c.expanded = true;
};
c.breadcrumbs = [];
var thisPage = {url: $location.url(), id: $location.search()['id'], label: c.data.page || document.title};
if (thisPage.id != $rootScope.portal.homepage_dv) {
var pageFound = false;
for (var i=0;i<c.data.breadcrumbs.length && !pageFound; i++) {
if (c.data.breadcrumbs[i].id == thisPage.id) {
c.breadcrumbs.push(thisPage);
pageFound = true;
} else {
c.breadcrumbs.push(c.data.breadcrumbs[i]);
}
}
if (!pageFound) {
c.breadcrumbs.push(thisPage);
}
}
c.data.breadcrumbs = c.breadcrumbs;
c.server.update();
}
That’s really all there is to it. Here’s one example of how it looks in practice:
In the example above, the URL for the page contains a table parameter, but no sys_id parameter. This generates a page label from the table’s getPlural() method. If we select a different perspective, which uses a different table, we will still be on the same page, but the page label will reflect the current table in use for that perspective.
Now, if you click on one of the items in the table, you will see that the breadcrumb list grows, and this time the URL has both a table and a sys_id parameter, but the record in question (sysapproval_approver) has no number, name, or short_description fields, so the label is defaulted to the generic label for the record.
Clicking on the Approvals breadcrumb will take you back to the original screen, removing the single Approval record from the breadcrumb array.
Now, if you click on the Change record instead of the Approval record, the Change record actually does have a number field, so the label for that page is the actual number of the record.
And finally, if you click on the Opened by column, which is configured to take you to the User Profile page, there is no number, but there is a name, so that becomes the label.
The reason that you can find the record to fetch the name in the above example is because the Data Table widget arbitrarily passes both the table and sys_id parameters to the User Profile page, even though table is not needed (the table sys_user is assumed by the User Profile page — you don’t have to pass it). When you pull down the User menu and select Profile, no table name is passed in the URL, so the label defaults to the name of the page.
One thing to keep in mind is that the trail will only build as you pass through pages that contain the widget. Any pages that you pass through that do not contain the widget will not get added to the running list of pages, as there will be no code running that pushes the page onto the stack. Other than that, it seems to work in most other cases. If you want to try it out for yourself, here’s an Update Set that contains the custom widget.
Update: There is a better (working!) version here.
Antonio says:
Thanks for building that and it really seems very nice, for sure! I have only one problem to run the server-side script in my instance, the loop “for” is appearing syntax error.
snhackery says:
If you did a cut and paste from the blog, chances are that you inadvertently copied the “&&” that displays on that line. For some reason, WordPress’s code formatter likes to convert all & characters to &, which is not legal code. Try changing the && to && and see if that resolved the problem.