O
ne problem I’ve seen a few times is the need to create a new record on the fly in order to populate it into a reference field. This problem recently came up for me with a client I’m working with so I decided to come up with a good solution. An example scenario would be a technician filling out an incident record for a user that doesn’t yet exist in the system. At first glance, the solution seems simple enough…simply navigate to the user form and create the new user, then create your incident record. While that can be done, it’s not always so simple. What if the technician has just spent several minutes filling out the incident and then realizes the caller doesn’t exist? The user would then have to navigate away from that incident record and lose all of the changes, or open an entirely new browser window to create the user and then return and populate the field.
Fortunately, there is a better way if you know how to leverage UI Macros and GlideDialogWindow QuickForms. This article shows a solution that you can use for any ‘sys_user’ reference field in your system. It can also be easily modified and applied to other reference fields as well.
The first step in this solution is to create a new UI Macro that can be associated to the reference field(s) that you want to be able to create new records from. The macro I’ve created here is very loosely based on an out-of-box macro, but also has three very important differences.
- The first difference is that it uses standard ‘A’ and ‘IMG’ html tags to display the macro icon rather than the ‘g:reference_decoration’ tag that is normally used for reference field macros. The reason for this is that we need the macro to always be visible…especially if the reference field doesn’t have anything in it. By design, the ‘g:reference_decoration’ tag only shows the macro if something is populated in the reference field.
- The other change is what the ‘onClick’ event (the ‘addEditUserPop’ function) does when the macro icon is clicked. It opens a GlideDialogWindow quickform to display either a new record window (to add a new record if the field is empty when the macro is clicked) or an edit window to edit the record (if the field is populated when the macro is clicked). Check this article out if you need more information about how GlideDialogWindow quickforms work.
- Lastly, the callback function (the ‘setUserField’ function) gets called after the dialog is submitted and populates the reference field with the newly-added or edited record.
You can set up the UI Macro like this…
Name: add_edit_user
Description:
Show add/edit user macro icon on a reference field
Activate by:
– Setting the active field in this macro to true
– Adding the attribute to a dictionary field: ref_contributions=add_edit_user
XML:
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate var="jvar_guid" expression="gs.generateGUID(this);" />
<j:set var="jvar_n" value="add_edit_user_${jvar_guid}:${ref}"/>
<a id="${jvar_n}" onclick="addEditUserPop('${ref}')">
<img border="0" src="images/icons/user_profile.gifx" title="${gs.getMessage('Add/Edit User')}" alt="${gs.getMessage('Add/Edit User')}"/>
</a>
<script>
//OnClick function for UI macro
function addEditUserPop(reference){
var s = reference.split('.');
var referenceField = s[1];
var v = g_form.getValue(referenceField);
//If user field is empty then pop open an 'Add User' dialog
if(v == ''){
var dialog = new GlideDialogForm('Add User', 'sys_user', setUserField);
dialog.setSysID('-1'); //Pass in -1 to create a new record
}
//Else pop open an 'Edit User' dialog for the populated user record
else{
var dialog = new GlideDialogForm('Edit User', 'sys_user', setUserField);
dialog.setSysID(v); //Pass in reference sys_id to edit record
}
dialog.addParm('sysparm_view', 'default'); //Specify a form view
dialog.addParm('sysparm_form_only', 'true'); //Remove related lists
dialog.render(); //Open the dialog
//Callback function executed from dialog submit
function setUserField(action, sys_id, table, displayValue){
//Set the user field with the popup user
g_form.setValue(referenceField, sys_id);
}
}
</script>
</j:jelly>
XML (Fuji version):
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate var="jvar_is_itil" expression="gs.hasRole('itil')" />
<j:if test="${jvar_is_itil == true }">
<g:evaluate var="jvar_guid" expression="gs.generateGUID(this);" />
<j:set var="jvar_n" value="cf_add_edit_user_${jvar_guid}:${ref}"/>
<a>
<span id="${jvar_n}" onclick="addEditUserPop('${ref}')"
class="btn btn-default icon-add-circle"
title="${gs.getMessage('Add User')}"
alt="${gs.getMessage('Add User')}">
</span>
</a>
<script>
//OnClick function for UI macro
function addEditUserPop(reference){
var s = reference.split('.');
var referenceField = s[1];
var v = g_form.getValue(referenceField);
//If user field is empty then pop open an 'Add User' dialog
if(v == ''){
var dialog = new GlideDialogForm('Add User', 'sys_user', setUserField);
dialog.setSysID('-1'); //Pass in -1 to create a new record
}
//Else pop open an 'Edit User' dialog for the populated user record
else{
var dialog = new GlideDialogForm('Edit User', 'sys_user', setUserField);
dialog.setSysID(v); //Pass in reference sys_id to edit record
}
dialog.addParm('sysparm_view', 'add_user'); //Specify a form view
dialog.addParm('sysparm_form_only', 'true'); //Remove related lists
dialog.render(); //Open the dialog
//Callback function executed from dialog submit
function setUserField(action, sys_id, table, displayValue){
//Set the user field with the popup user
g_form.setValue(referenceField, sys_id);
}
}
</script>
</j:if>
</j:jelly>
Once you’ve set up the UI Macro, you need to add the appropriate attribute to the reference field so that the macro will be displayed. In order to do that, you simply need to personalize the dictionary for your reference field and add the ‘ref_contributions=add_edit_user‘ attribute to the ‘Attributes’ field on the dictionary. If you have multiple ‘ref_contributions’ attributes, they should be separated by a semicolon and this macro will look best if it is the first macro listed for the field.
That’s it! Here are a few screenshots of the finished product!
Mark,
maybe I’m missing something, but couldn’t you simply click the magnifying glass next to the Caller field to get the reference list, then click the New button to create a new User record in the reference pop-up window? When you click Submit on that record it will populate the Caller field on the Incident form.
Brian
You’re right, and that solution probably works well in quite a few cases. This solution here has the added benefit of allowing you specific control over the form view used. It also allows you to edit the record if necessary without leaving the form and uses the dialog window (which I think just looks nicer) :).
Great Solution. Tried it out on an alternate reference type field on the Incident Form. Worked like a Charm 🙂
BTW, do you have a Cheat Sheet or Quick Reference to help become a Jedi on Jelly UI Macros Development?
Thank You.
Not yet. It’s something that I’m trying to work on, but it involves a lot of different pieces so it might be a while before I get around to it.
Mark,
Thanks for this. I’ve used it on three different implementations. To extend the capabilities, you can add a line of code that will set some of the values on the new record. Here’s how:
dialog.addParm(‘sysparm_query’, ‘first_name=Peter^last_name=Oneppo^company=debf798b0a0a3caa0015818a47f41007’);
This will set the First Name, Last Name, and Company on a new user record. Company needs to be the sys_id since it is a reference.
I hope someone finds that useful!
Thanks,
Peter
Thanks for sharing! Definitely a useful addition.
Similarly, we just snagged the user’s input, too. So if you type a “bad” reference name, we take it make that the first and last name of the new user.
var firstName = name.substring(0, name.indexOf(' '));
var lastName = name.substring(name.indexOf(' ') + 1);
dialog.addParm('sysparm_query', 'first_name='+firstName+'^last_name='+lastName);
If one has an Interceptor defined on a table, is there a way to make sure the Wizard is kicked off to walk the user through and then create the referenced record? I’m trying a few sneaky ideas with GlideDialogWindow without luck, yet…
Seems like that will definitely require some GlideDialogWindow trickery. I’ve never tried it. You might have to navigate away from the form and pass some URL parameters to get that done.
I wanted to add to this in-case someone comes across this article and wants to use the UI Macro on a Wizard vs. a normal form. There might be a bug with wizard variables using the ref_contributions attribute to make the UI macro display correctly (Refer to this post: http://community.service-now.com/forum/8130). To make the UI Macro display on a Wizard, I had to refer to the element ID (which on wizards is not the variable name but “IO:sys_id” (sys_id of the variable). This scenario is also mentioned in another community post: http://community.service-now.com/forum/3713
A combination of those posts let me add my UI macro to the wizard. There is a icon (always displayed) next to a Location field to do a GlideDialogWindow to add a new location on the fly (and post the new location back to the field) without having to leave the wizard.
Here is the code to the onload wizard client script:
var field = 'IO:00eb70147b799000118b395aca4d4d52';
try{
var img = document.createElement('img');
img.src = 'images/add_filter.pngx';
img.alt='Create Location';
img.title='Create Location';
var link = document.createElement('a');
if (navigator.appName == 'Microsoft Internet Explorer'){
link.setAttribute('onclick', Function('doSomething()'));
}
else{
link.setAttribute('onclick', 'doSomething()');
}
link.name='create_location';
link.id='create_location';
link.style.display="inline";
link.appendChild(img);
var fldParent = g_form.getElement(field).parentNode;
var lookupIconSib = document.getElementById("lookup." + field.nextSibling);
fldParent.insertBefore(link,lookupIconSib);
// document.getElementById(field).parentNode.appendChild(link);
}
catch(e){
//alert('Error');
}
}
//onclick event to fire when the image is clicked
function doSomething() {
//Get the table name and sys_id of the record
var tableName = "cmn_location";
//Create and open the dialog form
var dialog = new GlideDialogForm('Create Location', tableName, setUserField);
dialog.addParm('sysparm_view', 'location');
dialog.addParm('sysparm_form_only', 'true');
dialog.render();
//Callback function executed from dialog submit
function setUserField(action, sys_id, tableName, displayValue){
//Set the user field with the popup user
g_form.setValue('location_otto', sys_id);
}
}
Nice work! Thanks for sharing your solution!
Mark,
Thanks for this solution. I used it for a different reference field and it works great. I have a query though. Can this solution be applied to ‘Request Item or Catalog Request’ reference field ?
Thanks,
Mayank
I assume you’re asking if this can be used on reference variables in the service catalog. The answer to that is ‘yes’, but you’ll have to go about it a bit differently because variables don’t have attributes that allow you to add macros. You can use client scripts to create macros though. This post should get you started.
https://servicenowguru.wpengine.com/system-ui/ui-macros/add-macro-non-reference-field/
Hi Mark,
I want to do something similar to this. I have a reference variable on my catalog item. I have requirement like, a user should be able to enter a new value in the reference variable other than that of the available value without leaving the form.
Thanks
Anish.
Variables don’t support reference attributes so you have to manufacture the icon to produce the same behavior. I don’t have an exact example of this suitable for the catalog, but this post might help you get started.
https://servicenowguru.wpengine.com/system-ui/ui-macros/add-macro-non-reference-field/
Hi Mark,
Is there a way to enforce certain mandatory fields on a ‘GlideDialogForm’ object?
Thanks.
In the scenario presented above, where you are just referencing a standard form view, you can use standard UI policies and client scripts (specific to that view) to set mandatory fields, etc.
Thanks for the advice. Can you think of a reason that a UI policy (referencing the same form view as the UI macro) wouldn’t be kicking in?
The first thing you should do is bring up the form view outside of the dialog window to isolate the cause. If it’s being caused by the dialog window (and it shouldn’t be) then you’ll probably need to talk to SN support. I think the most likely cause is an error from one of your other client scripts or UI policies running on that table. Client scripts and UI policies are built to run on all views by default so you may have some conflicts with something there that requires a specific field or element that you don’t have on your custom view. If this is the case, you’ll see an error in your browser error console.
Thanks Mark for another helpful post. This is really useful for anyone with an external facing instance, where users come and go.
With it being a UI Macro, the usual g_form.getValue() type calls are not available. Instead one has to use gel() which I cannot find documented on the SN Wiki.
Tony Fugere, above, showed how to grab user-input with gel(‘sys_display.incident.field_name’). For a reference field, e.g. location, this gives the text string. But if you need the SysID, the syntax is below.
var location = gel(‘sys_display.incident.location’).value;
var location_sysid = gel(‘incident.location’).value; // location is a reference, so the sysID is needed
Grabbing the SysID is handy if you want to pre-populate a reference field on the user table, based on the Incident form.
Thanks Rich! FYI, ‘gel’ is simply a ServiceNow shorthand for ‘document.getElementByID’, which is standard javascript. You can also use prototype or jQuery libraries to do the same thing. The prototype library is built into ServiceNow and is what I use most often. It’s got a ton of flexibility to select elements from a page. The dollar selector will give you the same thing as using ‘gel’…$(‘incident.location’).value for example. Here’s an API doc that may help. Using prototype in ServiceNow has really given me a lot more flexibility to manipulate forms.
http://api.prototypejs.org/dom/dollar/
Probably should also note that this type of targeting (gel, $, etc.) and form manipulation can get you into upgrade issues if used improperly but you’re usually pretty safe in targeting this way because you reference a specific element ID. Where possible, it’s definitely best to use ‘g_form’ before these other methods.
I have added the insert button to the form that is generated in the ui macro, when a user clicks insert is there a way to capture the newly inserted records sys_id to update the reference field?
That’s what the ‘setUserField’ callback function is supposed to do. I don’t know of any reason that it wouldn’t work for ‘Insert’ as well other than the fact that the sys_id isn’t immediately available in that case. You might try playing with that function and just seeing if there’s a difference in what gets passed back.
Thank you for the reply,
I ended up doing a query for the last user created by the logged in user with a limit of 1. Then updating the sysid variable inside that function.
[code]
function setUserField(action, sys_id, table, displayValue){
if (action != ‘sysverb_insert’){
//Set the user field with the popup user
g_form.setValue(referenceField, sys_id);
} else {
var ggsys = new GlideRecord(‘u_gct_caller_information’);
ggsys.addQuery(‘sys_created_by’, g_user.userName);
ggsys.orderByDesc(‘sys_created_on’);
ggsys.setLimit(1);
ggsys.query();
if (ggsys.next()){
sys_id = ggsys.sys_id;
g_form.setValue(referenceField, sys_id);
}
}
}
[/code]
In our Fuji Upgrade (from Dublin), we lost the ‘submit’ button in this add/edit user dialog. Any ideas?
I don’t know of anything that would cause that since the dialog simply displays the standard form it should just inherit the regular form, client scripts, etc, but I’ve added a Fuji version of the code above that you can try. If the button is still hidden and you’re sure the user has create/write ACL permissions, then I would probably start to look at client scripts and see if something is set up to hide the buttons on the source table.
Thanks for the excellent post – 5 years after it was published, it is still relevant 🙂
A quick question: is there a way to stay in the form after clicking “Save”? Currently, “Save” and “Save and Exist” do the same; I would like the user to be able to stay after saving, instead of clicking again on the macro button.
Not a way that I know of. Maybe some custom UI action to save asynchronously, but the standard form buttons will take over the whole page.
Five years later and still a very slick solution for a variety of use cases. I just added this to a custom reference field that feeds into a table where we track employee appointments (that do not get registered in the official employee directory).
Thanks Mark.
Is it possible to have list edit functionality in the GlideDialogForm, if opening list view of particular table in the popup?
Thanks.
Not as far as I’m aware.
You saved a life today with this lol. Thank you, your siteis amazing.
Just implemented this in our system today with a few tweak for different reference fields.
Hi Mark, I am getting multiple scroll bars on the GlideWindow when implementing this solution.
Hi Mark,
I am calling the ‘GlideDialogForm’ from a catalog catalog client script.
I am now trying to close this ‘GlideDialogForm’ window from the UI Action of the target table ( which GlideDialogForm is referring to). But , I had no success to close it off. There were couple of option I used which did close the GlideDialogForm but it also refreshes the original catalog form as well ( The form over which ‘GlideDialogForm’ is running).
Thanks,
Ash