Checking for Modified or Changed Fields in Script

//, Client scripts/Checking for Modified or Changed Fields in Script

Checking for Modified or Changed Fields in Script

W

orking in Service-now, you’ll find that a lot of scripting tasks come down to identifying which fields changed on a form (client-side) or record (server-side). In this post, I’ll show you some different techniques to identify changed fields in both client-side, and server-side scripts. I’ll also show you a way that you can capture changed fields and values and print them in an email notification…without having to check every potential field in a record.

ServiceNow - Changed Fields

Identifying modified or changed fields in Client scripts

When looking at a form in Service-now, it’s usually pretty easy to identify the fields that have changed since the form loaded (as seen in the screen shot above). The green field indicators give a simple visual cue to the user. Behind the UI, the system is taking cues as well and firing off ‘onChange’ events and setting field parameters as a result. When you write on ‘onChange’ client script or a UI policy, you can tie into the events and take actions as a result. If at all possible, you should stick to ‘onChange’ client scripts or UI policies.
There are some special situations (typically on submission of a record) where you don’t have a special event that tells you something changed. In an ‘onSubmit’ scenario, you have to check for these changes yourself. A great example of this can be used is the ‘Dirty form’ navigation property (glide.ui.dirty_form_support) that can be turned on to display a message to users if they navigate away from a ‘dirty’ or changed form without first saving the record.

Client Script examples
If you just want to do a check to see if any value on the entire form changed (a dirty form), you can use ‘g_form.modified’ in an ‘onSubmit’ client script like this…

function onSubmit() {
  if(g_form.modified){
    return confirm("Values on the form have changed.\nDo you really want to save this record?");
  }
}

If you just want to check for changes to a few specific fields then you could use something like this in an ‘onSubmit’ client script…

function onSubmit() {
  var field1 = g_form.getControl('caller_id'); //Get the 'Caller' field control
  var field2 = g_form.getControl('short_description'); //Get the 'Short description' field control
 
  //See if the 'changed' attribute on either field is true
  if(field1.changed || field2.changed){
    return confirm("Values on the form have changed.\nDo you really want to save this record?");
  }
}

Checking for changes to variables is a bit more complex because the underlying element changes names. This script will check an individual variable…

var el_id = g_form.getControl("requested_for").id;
var orig_val = gel("sys_original."+ el_id).value;
var new_val = g_form.getValue("requested_for");
if(orig_val != new_val){
   alert("It's changed");
}

This script will check for all of the variables on the form to see if they have the ‘changed’ CSS class. It will return a count, so any count greater than 0 means that something on the form has been modified…

if($$('.changed').length > 0){
   return confirm("Values on the form have changed.\nDo you really want to save this record?");
}


Identifying modified or changed fields in Business rules

Business rules are server-side scripts, so there’s no form, no green indicator, and no ‘onChange’ event to tell you what happened. In these scenarios, business rules just care about what’s on its way to the record in the database.
Service-now includes some simple convenience methods for identifying changes to a record or field in a business rule. You may have seen these used in the ‘Condition’ field on many of the out-of-box business rules. All of the methods below return true or false based on changes to the ‘current’ record.

Record change: current.changes();
Field change: current.YOUR_FIELD_NAME_HERE.changes();
Field changes TO a value: current.YOUR_FIELD_NAME_HERE.changesTo(VALUE);
Field changes FROM a value: current.YOUR_FIELD_NAME_HERE.changesFrom(VALUE);

But what if you don’t know in advance what might change? What if you just need to return a list of changed fields or values? This might be one field, or 30 fields! I recently discovered the ‘GlideScriptRecordUtil’ class can get this type of information for you. You can use the following methods in a ‘before’ or ‘after’ business rule.

var gru = GlideScriptRecordUtil.get(current);
gru.getChangedFields(); //Returns an arrayList of changed field elements with friendly names
gru.getChangedFieldNames(); //Returns an arrayList of changed field elements with database names
gru.getChanges(); //Returns an arrayList of all change values from changed fields
Business Rule example
You can paste this script into a ‘before’ business rule on any table to display information messages about changed fields. This isn’t terribly useful, but does do a good job of showing how you can process changed field results.

//Display an information message for each change to the record
var gru = GlideScriptRecordUtil.get(current);
var changedFields = gru.getChangedFields(); //Get changed fields with friendly names
var changedValues = gru.getChanges(); //Get changed field values
//Convert to JavaScript Arrays
gs.include('j2js');
changedFields = j2js(changedFields);
changedValues = j2js(changedValues);

//Process the changed fields
for(var i = 0; i < changedValues.length; i++){
   var val = changedValues[i];
   if(val.getED().getType() == -1 && val.getJournalEntry(1)){
      //Print out last journal entry for journal fields
      gs.addInfoMessage(val.getJournalEntry(1));
   }
   else{
      //Print out changed field and value for everything else
      gs.addInfoMessage(changedFields[i] + ': ' + val.getDisplayValue());
   }
}

ServiceNow Changed Fields Business Rule

Printing all record changes in an email notification

One common use case I’ve seen for collecting all record changes in an update is to send all of those changes in an email notification. Without the use of the ‘GlideScriptRecordUtil’ methods described above, this really wasn’t practical. Here’s a solution I’ve come up with that allows you to accomplish this…

The first part is gathering these changes when you trigger your email notification event. Because the changes need to be captured at the time the record is updated or inserted, this needs to happen in a business rule. This script example collects all changed fields on an incident record and passes them as an event parameter to the ‘Incident Assigned to my Group’ notification.

//Collect all changed fields
var gru = GlideScriptRecordUtil.get(current);
var fields = gru.getChangedFieldNames(); //Get changed fields with database names
//Convert to JavaScript Array
gs.include('j2js');
fields = j2js(fields);

if (!current.assignment_group.nil() && current.assignment_group.changes()) {
   gs.eventQueue("incident.assigned.to.group", current, fields , '');
}

Once you’ve passed the event (with the changed fields parameter) to your notification, you can set up a mail script to iterate through the changed fields and print the results to the outgoing email.

One thing you’ll want to be careful of here is read access to the fields and values you’re printing out. There’s not a simple way to determine who should be able to read these fields in an email notification so if you’re sending an email to end users, you probably want to specify individual fields in a more targeted technique.
Changed fields:
<mail_script>
//Process the changed fields
var fields = event.parm1.split(',');
for(x in fields){
   //Get the field label, value, and type
   var field = fields[x];
   var fieldLabel = eval('current.' + field + '.getLabel()');
   var fieldVal = eval('current.' + field+ '.getDisplayValue()');
   var fieldType = eval('current.' + field + '.getED().getType()');

   if(fieldType == -1 && eval('current.' + field + '.getJournalEntry(1)')){
      //Print out last journal entry for journal fields
      template.print(eval('current.' + field + '.getJournalEntry(1)'));
   }
   else{
      //Print out changed field and value for everything else
      template.print(fieldLabel + ': ' + fieldVal + '\n');
   }
}
</mail_script>

The result of this mail script will look something like this…
Email Changed Fields

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!

38 Comments

  1. Brian Broadhurst January 20, 2011 at 8:25 pm - Reply

    Hi Mark,

    great information – as always! The paragraph before your sample mail_script says that you need to query the sys_trigger table, but the script actually uses event.parm1 – can you give an example of the sys_trigger query?

    Also, is it possible in the business rule to access the old value of the field as well as the new value, so you can send an email that says, for example, “Priority changed from 2-High to 1-Critical”?

    Brian

    • Mark Stanger January 20, 2011 at 11:37 pm - Reply

      Hey Brian,

      Thanks for the feedback – as always :). The paragraph in question has been removed. The method I had mentioned was necessary at one point, but is no longer necessary. I’ve updated the article to show the correct method.

      As for the current and previous values, it is possible, but I don’t have a full script for that at the moment. If somebody can come up with that it would be very helpful :). There are really 2 options for producing that information though. The first would be to try to gather the previous field values (probably display values only) in the business rule by looking at the ‘previous.FIELD_NAME’ values and pass those in as an array in the second event parameter to your email notification.

      In my opinion, this method would probably be pretty messy to pull off because getting display values for different types of fields can be difficult. I think a simpler method might be to leverage the new history set functionality and query the ‘sys_history_line’ table to get that information. The potential drawback here is that history sets are only available for audited tables. This can also be kind of tricky to make sure you get the correct information. If you want to go the history set route, then you could look at the UI page included in the Simultaneous Update Alert Update Set that I built recently.

  2. Tony Balestreri July 6, 2011 at 2:19 pm - Reply

    Hi Mark – this is working great for me except for one field, the description field has an error when it is changed.

    The follow message occurs on the top of the form when I use your business rule example:
    “org.mozilla.javascript.Undefined@63f6ea” Have you had any problems with the description?

    • Mark Stanger July 6, 2011 at 2:37 pm - Reply

      Thanks for the feedback. It looks like you’ve found a bug. I’ve updated the code above with the fix. Please let me know if that works better for you.

  3. Michele King August 11, 2011 at 3:28 pm - Reply

    I did the business rule like above under the heading: “Printing all record changes in an email notification” but changed the event to incident.watchlist. The email fires and includes all the fields that were changed except Urgency and Description. Impact and Short Description work so it’s not the type of field. I was getting an error on the line dealing with the getJournalEntry so I removed that section from the mail script but if I just update the Description on the Incident, the email fires and does not include the change to that field. Any ideas?

    Thanks a bunch.

  4. Michele King August 16, 2011 at 1:08 pm - Reply

    Nevermind, I got it.

    I had to change the email notification FROM:

     var fieldType = eval('current.' + field + '.getED().getType()');

       if(fieldType == -1 &amp;&amp; eval('current.' + field + '.getJournalEntry(1)')){
          //Print out last journal entry for journal fields
          template.print(eval('current.' + field + '.getJournalEntry(1)'));
       }

    TO:

     var fieldType =  fieldVal + '.getED().getType()';
       var fieldJournal = fieldVal +  '.getJournalEntry(1)';

       if (fieldType == -1 &amp;&amp; fieldJournal) {
          //Print out last journal entry for journal fields
          template.print(fieldLabel + ': \n' + fieldJournal + '\n');
       }

    Used the business rule you have above to show the changes to the fields and noticed you used changedValues in that business rule.

  5. austin June 8, 2012 at 2:44 pm - Reply

    How can I prevent an update to selected fields but allow updates to other fields on the record?

    • Mark Stanger June 8, 2012 at 3:02 pm - Reply

      You can accomplish this with ACLs. There’s a lot of good information on the wiki that talks about this subject.

  6. Joan October 2, 2012 at 4:14 am - Reply

    Hi,

    Our requirement is to have an alert if the Update button was hit and there are no changes in the form. I used the first script on the other way:

    function onSubmit() {
    if(!g_form.modified){
    return alert(“Values on the form have changed.\nDo you really want to save this record?”);
    }
    }

    But this still pop ups everytime the user right click and choose on the context menu…
    Please help. Thanks,

  7. Josh B October 15, 2012 at 12:32 pm - Reply

    Mark,
    Do you know if g_form.modified works on Service Catalog item forms?

    • Mark Stanger October 15, 2012 at 12:41 pm - Reply

      It should, but I just tested it and it doesn’t work on catalog forms.

  8. Joan December 20, 2012 at 2:47 am - Reply

    Hi Mark,

    I combined this script and with the post you mentioned below.. It works pretty well. But we add a counter in our incident form. I am wondering why it still gives a message that it was not being updated even if the counter was clicked and registered from the activity log?

    Below is my script:

    function onSubmit(){

    var action = g_form.getActionName ();

    // if update button is clicked without modifications in the form

    if((action == ‘sysverb_update’ ||action == ‘sysverb_update_and_stay’) && !g_form.modified){

    return g_form.addInfoMessage(‘No values on this form have been changed. Update skipped.’);

    }

    }

    • Mark Stanger December 20, 2012 at 4:59 am - Reply

      Probably because your script is a client script and the counter is being updated at the server level, which happens after the client script runs.

  9. Rachelle Sanon September 25, 2013 at 3:02 pm - Reply

    Will the Packages.com.glide.script.GlideRecordUtil work on a table that does not extend from the Task Table?

  10. Rachelle Sanon January 9, 2014 at 2:22 pm - Reply

    The Business Rule example you provided, was working in our instance, until we upgrade to Calgary. After the upgrade, it returns the value “undefined”


    //Display an information message for each change to the record
    if (typeof GlideScriptRecordUtil != ‘undefined’)
    var gru = GlideScriptRecordUtil.get(current);
    else
    var gru = Packages.com.glide.script.GlideRecordUtil.get(current);
    var changedFields = gru.getChangedFields(); //Get changed fields with friendly names
    var changedValues = gru.getChanges(); //Get changed field values
    //Convert to JavaScript Arrays
    gs.include(‘j2js’);
    changedFields = j2js(changedFields);
    changedValues = j2js(changedValues);

    //Process the changed fields
    for(var i = 0; i < changedValues.length; i++){
    var val = changedValues[i];
    if(val.getED().getType() == -1 && val.getJournalEntry(1)){
    //Print out last journal entry for journal fields
    gs.addInfoMessage(val.getJournalEntry(1));
    }
    else{
    //Print out changed field and value for everything else
    gs.addInfoMessage(changedFields[i] + ': ' + val.getDisplayValue());
    }
    }

  11. Rachana Ramachandran February 20, 2014 at 11:27 pm - Reply

    Hello Mark,

    We am trying to implement the same through a script include or global business rule as we want the getChangedFields to work on gliderecord objects.

    This is how we are using it. But, somewhere it is breaking and not getting the result.

    *********************************************************************
    var compareDate = gs.now() + “,’00:00:00′”;

    var cr = new GlideRecord(“change_request”);
    cr.addQuery(“sys_updated_on”,”>”,compareDate);
    cr.query();

    while (cr.next()){

    var fieldsChanged = new Packages.java.util.ArrayList();
    fieldsChanged = getFieldsThatChanged(cr);

    gs.log(“CHANGED FIELDS” + cr.number + fieldsChanged);
    }
    }

    function getFieldsThatChanged(gr) {
    var changes = [];

    if (gr == null || !gr.isValid())
    {return changes;
    }
    var gru = GlideScriptRecordUtil.get(gr);
    // var ignoreables = new Packages.java.util.ArrayList();
    // ignoreables.add(“last_discovered”);

    var whatChanged = gru.getChangedFields();
    gs.log(“whatChanged ” + whatChanged);

    for (var i = 0; i < whatChanged.size(); i++) {
    var f = whatChanged.get(i);
    changes.push(f);
    }

    return changes;
    }

    ***********************************************
    Note: If I use it in Business rule with the "current" object, it is giving correct output.

    Request your help please.

    Thanks much,
    Rachana

    • Mark Stanger February 21, 2014 at 6:23 am - Reply

      It works with ‘current’ because that GlideRecord object contains information about the changed fields. I don’t think you’re going to get this to work with a regular GlideRecord object unfortunately.

  12. Brad April 4, 2014 at 8:57 am - Reply

    Would this work for sending a notification when custom variables from a service request (the stuff that shows up on the Variable Editor) change? I’m trying to find a way to send email alerts based on that. At this point I’m not sure if ServiceNow can actually tell when/if those variables change.

    • Mark Stanger April 4, 2014 at 9:02 am - Reply

      I don’t think the business rule portion can easily identify changes to individual variables using this method. It should still pick up changes to the ‘variable_pool’ on the record though. I would just put it in a system and test it out. You should be able to tell within a few minutes how it will react to variables.

      • Brad April 4, 2014 at 10:21 am - Reply

        I feel like it wants to work. To test, I just used the first business rule that displays an information message. I do get a message when I update anything, but it always says “variable: undefined”. I see that a previous comment mentioned the same problem. Do you know if there’s something used in the script that no longer works in Calgary? I know that package calls have changed, but I see the script already accounts for that. I’m new to this, so I wasn’t sure what else to look for.

        • Mark Stanger April 4, 2014 at 4:08 pm - Reply

          The ‘Undefined’ in the post above is something different that has been fixed in my scripts. What you’re seeing is a result of trying to print out the ‘variable_pool’ object. That’s not going to give you what you want because the variables are items within that object. It’s very easy to detect whether a variable in the variable pool changes, but there’s not really a way to detect which variable changed. The best solution I’m aware of would be to simply print all of the variables out in an email if one changes. An example of that can be found here on the SN wiki.

  13. Stephen November 22, 2014 at 7:51 pm - Reply

    Mark,

    I can’t tell you how valuable this post was to me when I created an integration service that syncs work items in Service-now to Microsoft Team Foundation Server and (vice versa). I used your script that loops through the changed fields to generate an xml string which has worked great for a few years. Now the Eureka release just went in and I’m getting “undefined” for each value that has changed. Do you know what could have changed in the release to cause this? Below is the script I used based on your article. The val.getDisplayValue always comes across undefined but everything else gets filled in.

    function PackageChanges() {
        var gru = Packages.com.glide.script.GlideRecordUtil.get(current);
        var changedFields = gru.getChangedFieldNames();
        var changedValues = gru.getChanges();

        gs.include('j2js');
        changedFields = j2js(changedFields);
        changedValues = j2js(changedValues);
        var changes = '';
        changes += '' + current.sys_id + '';
        changes += '' + current.sys_class_name + '';
        changes += '' + current.u_tfs_work_item_id + '';
        changes += '' + currentGroup + '';
        changes += '' + current.sys_updated_by + '';
        changes += '' + current.operation() + '';
        changes += '';
        for (var i = 0; i  0 &amp;&amp; changedFields[i] != 'variables') {
                changes += '';
                var val = changedValues[i];
                if (val.getED().getType() == -1 &amp;&amp; val.getJournalEntry(1)) {
                    changes += '' + changedFields[i] + '';
                    changes += '';
                }
                else {
                    changes += '' + changedFields[i] + '';
                    changes += '';
                }
                changes += '';
            }
        }
        changes += '';
        changes += '';
        return changes;
    }
    • Mark Stanger November 22, 2014 at 10:10 pm - Reply

      Thanks for the comment! The only piece of this code that I know for sure you’ll have issue with in Eureka is the ‘Packages’ call you have included. Notice how all of my sample code snippets include both a ‘Packages’ call and a ‘non-packages’ version as well. This is because ‘Packages’ calls were phased out in the Calgary release. I think if you replace your ‘Packages’ call in your function with this things will work better. If that doesn’t fix it then I’m not sure what the issue is.

      ‘var gru = GlideScriptRecordUtil.get(current);’

      • Stephen November 23, 2014 at 8:14 am - Reply

        Thanks for responding to quick! I’ll try that as I get access today. Do you know if they changed the behavior of asynchronous business rules so that we have access changesTo(), changesFrom, and previous? It would be nice if I could build the event to not only include the current value, but also the previous value without having the user wait on sending the event to the integration server.

      • Stephen November 23, 2014 at 5:18 pm - Reply

        I updated the script with the code you suggested but it did not work. I get the value of the changed field if I don’t use the getDisplayValue() method, but I really need the display value. Have you seen security as a possible cause of this anywhere. My experience has always been if all else fails, it’s probably permissions : )

        Thanks Mark!

        • Mark Stanger November 24, 2014 at 7:12 am - Reply

          Stephen, you’ll probably need to contact ServiceNow support at this point because I’m not sure what the issue might be. As far as ‘changes’ being available in async business rules, I don’t think that will ever be the case and I know it hasn’t changed in Eureka.

  14. Peter March 2, 2015 at 10:38 pm - Reply

    Hi Mark,

    I am using this technique on knowledge submissions, and it keeps reporting the “text” field as changing even when I don’t touch it. How I use this is that if key fields are changed then the submission is set back to draft. I also use it on published knowledge articles and the same is happening, it is reporting the “text” field as changing when when there is no change.

    Note the “text” field on both is a html

    regards
    Peter

    • Mark Stanger March 3, 2015 at 5:30 am - Reply

      The script is working correctly I’m sure, but the problem is that detecting changes to HTML fields is notoriously difficult. The text you’ve typed into the HTML field may not have changed, but simply placing your cursor into an HTML can impact the underlying HTML (which is what the true value is). Unfortunately I’m not sure what you can do about this. There’s no other way to separate the changes in HTML versus the changes in the text and images displayed in the HTML.

  15. Michael McCall May 21, 2015 at 4:35 pm - Reply

    Is there a simple swap for g_form.getControl() that allows you to discover value changes on mobile forms? I’m building a Client Script to check if specific fields have new values before submitting, and getControl() is perfect…

    …except for the new deprecation warnings on the Wiki! (http://wiki.servicenow.com/index.php?title=Mobile_Client_GlideForm_%28g_form%29_Scripting#Do_Not_Use_Deprecated_Methods)

    Is this type of logic just something that can’t be easily achieved in mobile? (By the way, I’m specifically checking in a Client Script because I’m prompting users with a chance to cancel their submission when certain fields are changed.)

  16. Jose Eduardo Chirinos Odio January 20, 2016 at 9:03 am - Reply

    Hello Mark Stanger and everyone else, I was checking out this code in Geneva Release, this is my output of the business rule: if it is tested in global scope the script indeed recognizes the changes made to a form, the only thing that was not working is “what is the new data added”, I get an undifined in the info message, something like this: “Short_description : undefined”, I am just letting you all know! 🙂

    Now that I shared my output of this script in Geneva, I would like to request some help! I trying to do this but in a scoped application! :), what I recieved at first instance was that GlideRecordUtil is not available for scoped applications, so I used the word ‘global’ before each statement related to GlideRecordUtil, and it almost worked!! then the output I keep recieving this:

    ‘Invalid object in scoped script: [JavaClass com.glide.script.GlideRecordUtil]’

    I have tried so hard to make this right! and it was not possible! any ideas? how to apply the same scrip to a scoped application!? 🙂 Thank you very much in advance! very good post!

    • Mark Stanger January 20, 2016 at 9:59 am - Reply

      You can try ‘GlideRecordScriptUtil’ instead. That’s the new class name as of Calgary. I am seeing that ‘getChanges()’ is broken in Geneva even though it’s still available as a method. I’d advise you to contact ServiceNow support about that one.

  17. Stijn Van Echelpoel March 2, 2016 at 2:33 am - Reply

    Mark,

    Thank you very much for sharing your code. It is extremely useful.
    I am particullary interested in the section about “Printing all record changes in an email notification”.

    I am very interested in using these code snippets but when notifying users they are not interested in all variables. For example they don’t need information about system variables.
    My question is can I limit the number variables displayed in my notification to a selection of my choice?

    How would I acieve that ? I suppose with somekind of IF structure.

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

      Yes, you would need to include an ‘if’ statement to check to see that the field name matched what you wanted to include (or exclude). In the mail script example, the ‘if’ statement would need to start directly below the ‘var field’ line and wrap around everything up-to and including the final ‘else’ statement. if(field == ‘category’ || field == ‘short_description’) would allow for just the category or short description fields for example.

  18. Stijn Van Echelpoel March 3, 2016 at 6:57 am - Reply

    Hi Mark,

    Thank you for the quick reply. My business client has one additional question concerning the notification. Is it possible to show both the current and previous value of the fields that have been changed in the notification mail?

    • Mark Stanger March 3, 2016 at 7:51 am - Reply

      Good question. There’s not a way I’ve built yet but I’ve addressed this in the top comment on this thread from Brian Broadhurst. It offers a couple of suggestions for how you could do this. Not as straight-forward as it should be unfortunately.

  19. Colleen Bester November 10, 2017 at 3:22 pm - Reply

    I don’t know if anyone is going to still be looking at this. But, just in case, it looks like you can get the old values with some coding, using the previous item. Basically, if you use gru.getChangedFieldNames() in a business rule, and then use previous.getValue() with the fieldname that was changed, you can get the old value.

Leave A Comment