Adding Referenced Records Without Leaving the Form

///Adding Referenced Records Without Leaving the Form

Adding Referenced Records Without Leaving the Form

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.

GlideDialogWindow-Add User

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.

  1. 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.
  2. 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.
  3. 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…

‘add_edit_user’ UI Macro
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:

<?xml version="1.0" encoding="utf-8" ?>
<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):

<?xml version="1.0" encoding="utf-8" ?>
<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!

Add/Edit User UI Macro
AddEditUser-Macro

Add User Dialog Window
GlideDialogWindow-Add User

Edit User Dialog Window
GlideDialogWindow-Edit User

Reference field populated with new user

By | 2018-07-09T15:00:00+00:00 March 24th, 2011|Categories: UI macros|Tags: , , , , |35 Comments

About the Author:

Mark has worked in the IT industry since 2002 and with ServiceNow since 2007. He is the founder and creator of SN | Guru and the co-founder of Crossfuze, one of the worlds leading ServiceNow consulting partners. Prior to co-founding Crossfuze, he worked for ServiceNow as a Senior Architect on the Professional Services team. He has personally led dozens of successful implementations encompassing every part of the ServiceNow platform. He is also responsible for designing and developing groundbreaking ServiceNow solutions and best practices in the form of various applications, turnkey solutions, and integrations during his tenure at ServiceNow, Crossfuze and, of course, SN | Guru. These solutions are used today by ServiceNow administrators and consultants alike in hundreds of ServiceNow instances around the world!

35 Comments

  1. Brian Broadhurst March 24, 2011 at 5:48 am - Reply

    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

    • Mark Stanger March 26, 2011 at 1:35 pm - Reply

      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) :).

  2. Steve Darity March 25, 2011 at 8:53 am - Reply

    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.

    • Mark Stanger March 26, 2011 at 1:32 pm - Reply

      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.

  3. Peter Oneppo April 6, 2011 at 11:52 am - Reply

    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

    • Mark Stanger April 6, 2011 at 12:33 pm - Reply

      Thanks for sharing! Definitely a useful addition.

    • Tony Fugere September 20, 2011 at 5:46 pm - Reply

      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 name = gel('sys_display.incident.caller_id').value;
      var firstName = name.substring(0, name.indexOf(' '));
      var lastName = name.substring(name.indexOf(' ') + 1);

      dialog.addParm('sysparm_query', 'first_name='+firstName+'^last_name='+lastName);
  4. Tony Fugere September 20, 2011 at 5:48 pm - Reply

    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…

    • Mark Stanger September 21, 2011 at 6:47 am - Reply

      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.

  5. Cary Trusty November 7, 2011 at 1:45 pm - Reply

    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:

    function onLoad() {
       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);
    }
    }
    • Mark Stanger November 7, 2011 at 2:01 pm - Reply

      Nice work! Thanks for sharing your solution!

  6. Mayank November 18, 2011 at 6:49 am - Reply

    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

    • Mark Stanger November 19, 2011 at 1:50 pm - Reply

      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://www.servicenowguru.com/system-ui/ui-macros/add-macro-non-reference-field/

  7. Anish March 5, 2013 at 8:18 am - Reply

    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.

  8. Joe July 1, 2014 at 11:06 am - Reply

    Hi Mark,
    Is there a way to enforce certain mandatory fields on a ‘GlideDialogForm’ object?

    Thanks.

    • Mark Stanger July 1, 2014 at 11:10 am - Reply

      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.

      • Joe July 1, 2014 at 2:14 pm - Reply

        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?

        • Mark Stanger July 1, 2014 at 3:06 pm - Reply

          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.

  9. Richard Selby August 6, 2014 at 5:25 am - Reply

    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.

    • Mark Stanger August 6, 2014 at 7:26 am - Reply

      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.

  10. Rnorton September 23, 2015 at 11:03 am - Reply

    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?

    • Mark Stanger September 24, 2015 at 5:49 am - Reply

      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.

      • Rnorton September 24, 2015 at 12:26 pm - Reply

        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.

        • Rnorton September 24, 2015 at 12:36 pm - Reply

          [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]

  11. Dan Alexander September 23, 2015 at 12:48 pm - Reply

    In our Fuji Upgrade (from Dublin), we lost the ‘submit’ button in this add/edit user dialog. Any ideas?

    • Mark Stanger September 24, 2015 at 5:46 am - Reply

      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.

  12. Harel Ben-Sheffer March 2, 2016 at 5:37 am - Reply

    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.

    • Mark Stanger March 2, 2016 at 6:04 am - Reply

      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.

  13. bryan April 14, 2016 at 3:57 pm - Reply

    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.

  14. Ashvin June 24, 2016 at 8:08 am - Reply

    Is it possible to have list edit functionality in the GlideDialogForm, if opening list view of particular table in the popup?

    Thanks.

  15. Olivia June 22, 2017 at 2:25 pm - Reply

    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.

  16. Sam July 12, 2017 at 8:41 pm - Reply

    Hi Mark, I am getting multiple scroll bars on the GlideWindow when implementing this solution.

  17. Ashwin October 31, 2017 at 8:49 am - Reply

    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

Leave A Comment