Using the Service Portal for modal dialog popups in ServiceNow
This is quite advanced but very cool as it allows you to use the flexibility of the service portal in the back-end of ServiceNow without the need for any Jelly XML. You will find a working version of this code in the attached update set
In essence, you can use a service catalog record producer as a custom form in a modal dialog box. That gives you a lot of flexibility as the record producer can be easily updated/changed, etc, and you escape the ugliness of Jelly XML.
Here's some screenshots of how it looks/works...
Add a button to the regular form
Launch a record producer within the service portal in a modal dialog box
Here's how you can do it...
Generic Portal
The first step is to set up a generic portal. The reason for this is you don't want users to be able to navigate away to other portal pages, so the generic portal is simply blank. There are no themes or navigation. I've added a "blank" page as the home page simply so if anyone accidentally reaches this portal they see a white page.
Once you've got a generic portal in place, simply create the record producer you want to use. Note, if this is only accessible to back-end process workers you might want to set up the user-criteria to reflect that so your new record producer isn't visible in the regular service portal.
Record Producer
In order to get information from our service portal record producer back onto the g_form, we're going to use session data. This is where things get interest. The first thing is we need to define what we're going to pass back to our g_form and put that in the script that runs once the record producer has created a new record.
var newUser = {displayValue: producer.first_name.toString() + ' ' + producer.last_name.toString(), value: current.sys_id.toString()};
gs.getSession().putClientData('newUser', JSON.stringify(newUser));
The important thing to note here is the NAME of the session variable we're passing to the client. In this particular case, it is newUser and it is an object with two attributes to capture the display value and sys_id (value). This is the glue that binds everything together. The process is going to be...
- User launches record producer in a modal dialog box
- User creates new record but how do we get that back to our g_form? Pop it in the session data
- Glide Modal Dialog closes and grabs that new value from session data and puts it on the form
Add button(s) and modal dialog box(es) to your g_form
Now, on our regular ServiceNow glide form we're going to add a button that launches our record producer in the generic portal. To do this, we're going to add an On Load client script (with Isolate Script turned off)
The following script has a few details you'll need to tweak for your instance.
- whichButton should be the field you want to attach this button to (it'll be tableName.fieldName)
- whichItem should be the sys_id of your record producer
- whichTitle is whatever you want to appear at the top of the modal dialog
- recordProdVariable MUST be the same as the variable name you passed to the session object (above)
//Add as many buttons as you need on your form as objects to this array
var buttons = [
{
whichButton:'element.incident.caller_id',
whichItem : 'cfc13a06db812300407f78eebf9619a6',
whichTitle:'Add New User',
recordProdVariable:'newUser'
}
];
var modalWidth = 1050; //default is 800 x 800
var modalHeight = 800;
////////////////////////////////////////////////////
//add a button after this field
function onLoad() {
buttons.forEach(function(button){
var baseElement = document.getElementById(button.whichButton);
var thisElement = jQuery(baseElement).find('.form-field-addons');
thisElement.css('width','25%');
thisElement.append('<input type="button" value="'+button.whichTitle+'" class="btn btn-default" style="width:160px;" onclick="openModal(\''+button.whichTitle+'\',\''+button.whichItem+'\', \''+button.recordProdVariable+'\', \''+button.whichButton+'\')">');
});
}
////////////////////////////////////////////////////
//open a record producer in a modal window
function openModal (whichTitle, whichItem, recordProdVariable, whichButton) {
var whichAddress = '/generic?id=sc_cat_item&sys_id='+whichItem+'&sysparm_variables=%7B\"type\":\"'+recordProdVariable+'\"%7D';
var angularModal;
angularModal = new GlideModal();
angularModal.setTitle(whichTitle);
angularModal.setWidth(modalWidth);
angularModal.setPreference('id','jModal');
angularModal.renderWithContent("<iframe id='iframe_modal' name='iframe_modal_name' width='100%' height='"+(modalHeight-80)+"px' frameBorder='0' src='"+whichAddress+"' onload='iframeAngularOnLoad(\""+recordProdVariable+"\", \""+whichButton+"\")'></iframe>");
}
////////////////////////////////////////////////////
//When the iframe loads, prepare to pass info back to the g_form
function iframeAngularOnLoad(recordProdVariable, whichButton){
var thisFrame = jQuery('iframe#iframe_modal');
jQuery('.modal-content').css("width", modalWidth);
jQuery('.modal-content').css("height", modalHeight);
var requestSuccess = jQuery('div.text-center.text-success' ,thisFrame[0].contentWindow.document.body);
var ticketOpened = jQuery("span:contains('Your request has been submitted')",thisFrame[0].contentWindow.document.body);
var notifications = jQuery('div#uiNotificationContainer' ,thisFrame[0].contentWindow.document.body);
//If either the request was successful or a new record was added close this modal window automagically
if(requestSuccess.length!=0 || ticketOpened.length!=0){
setTimeout(function(){
updateDetails(recordProdVariable, whichButton);
}, 1000);
}
//Sometimes new record only return an ajax notification to the user, so we will watch for these
notifications.bind('DOMSubtreeModified', function(thisElement) {
var messageType = "";
//Examine the change in angular notifications
for(var thisTarget in thisElement.target){
if(thisElement.target[thisTarget] !== null && thisElement.target[thisTarget] !== undefined && thisElement.target[thisTarget].hasOwnProperty('$spNotificationsController')){
thisElement.target[thisTarget]['$spNotificationsController'].notifications.forEach(function(notification){
if(notification.hasOwnProperty('type')){messageType = notification.type;}
//examine the property 'message' if more precise information is needed than just the 'type'
});
}
}
if(messageType=="info"){//If we got an information message about a successful entry, close the modal and update the form
setTimeout(function(){
updateDetails(recordProdVariable, whichButton);
}, 1000);
}
});
var thisJavascriptContext = window.frames[window.frames.length -1];
thisJavascriptContext.isAngularReady = function(){
if(thisJavascriptContext.angular.element('main').scope().page.title=='Loading...') {
window.setTimeout(function(){ thisJavascriptContext.isAngularReady(); },100);
}else{
console.log('%cThis is the "'+thisJavascriptContext.angular.element('main').scope().page.title + '" page', 'color:red');
console.log('%cThe angular scope for this page is...','color:red');
console.log(thisJavascriptContext.angular.element('main').scope());
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//Close the dialog if the page isn't in the service catalog
//(ie, the catalog item has been saved and the user has been redirected to a summary page outside the catalog)
if(thisJavascriptContext.angular.element('main').scope().page.static_title != 'Catalog Item'){updateDetails(recordProdVariable, whichButton);console.log('Update details successful');}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//Sometimes new record only return an ajax notification to the user, so we will watch for these
var main = jQuery('main',thisFrame[0].contentWindow.document.body);
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver = new MutationObserver (mutationHandler);
main.each ( function () {
myObserver.observe (this, { childList: false, characterData: false, attributes: true, subtree: false });
});
function mutationHandler (mutationRecords) {
mutationRecords.forEach ( function (mutation) {
if(mutation.target.nodeName=='MAIN' && mutation.attributeName=='data-page-id'){
console.log('!!page refreshed!! ');
updateDetails(recordProdVariable, whichButton);
}
} );
}
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////
};
thisJavascriptContext.isAngularReady();
}
function updateDetails(recordProdVariable, whichButton){
jQuery(window.document).find('button[data-dismiss=GlideModal]').trigger("click");
var ga = new GlideAjax('getSessionData');
ga.addParam('sysparm_name', 'fetch');
ga.addParam('sysparm_key', recordProdVariable);
ga.getXML(updateForm);
function updateForm(response) {
var answer = response.responseXML.documentElement.getAttribute("answer");
var newUser = JSON.parse(answer);
if(answer !== null && newUser.hasOwnProperty('displayValue')){
var buttonName = whichButton.split('.');
g_form.setValue(buttonName[2],newUser.value, newUser.displayValue);
}
}
}
And the glue that holds it all together...
This is where the magic happens... we now have a regular g_form with a modal dialog popup that brings up our record producer, but in order to get the information that was entered into the record producer back to our form we need the following script include.
var getSessionData = Class.create();
getSessionData.prototype = Object.extendsObject(AbstractAjaxProcessor, {
fetch: function(){
var thisKey = this.getParameter('sysparm_key');
var results = gs.getSession().getClientData(thisKey);
gs.getSession().clearClientData(thisKey);
return results;
},
type: 'getSessionData'
});
...and there you have it...
You should now have a regular ServiceNow form with a button after one of the fields. Pressing that button will launch a record producer in a generic portal. Any record created by that record producer then populates your ServiceNow form.
Here's an example where there are seven buttons (but only two record producers, one for people, the other for organisations). Clicking each button allows you to create a new user/organisation that is immediately populated on the g_form.
If you find this doesn't work, go back and make sure you've got all the components in place. The attached update set should help.
Have fun
https://www.servicenow.com/community/now-platform-articles/using-the-service-portal-for-modal-dialog-popups-in-servicenow/ta-p/2304505