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.
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.
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…
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…
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 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…
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.
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
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.
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());
}
}
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.
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.
<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…
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
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.
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?
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.
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.
Nevermind, I got it.
I had to change the email notification FROM:
if(fieldType == -1 && eval('current.' + field + '.getJournalEntry(1)')){
//Print out last journal entry for journal fields
template.print(eval('current.' + field + '.getJournalEntry(1)'));
}
TO:
var fieldJournal = fieldVal + '.getJournalEntry(1)';
if (fieldType == -1 && 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.
How can I prevent an update to selected fields but allow updates to other fields on the record?
You can accomplish this with ACLs. There’s a lot of good information on the wiki that talks about this subject.
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,
onSubmit scripts will run for ANY form submission, which includes context menu actions. If you want this to happen just for a particular button, you’ll need to identify that button in your script. This post shows you how to do that.
https://servicenowguru.wpengine.com/scripting/client-scripts-scripting/identifying-ui-action-clicked-submit/
Mark,
Do you know if g_form.modified works on Service Catalog item forms?
It should, but I just tested it and it doesn’t work on catalog forms.
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.’);
}
}
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.
Will the Packages.com.glide.script.GlideRecordUtil work on a table that does not extend from the Task Table?
Yes, it should work on any table.
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());
}
}
–
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
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.
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.
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.
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.
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.
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.
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 && changedFields[i] != 'variables') {
changes += '';
var val = changedValues[i];
if (val.getED().getType() == -1 && val.getJournalEntry(1)) {
changes += '' + changedFields[i] + '';
changes += '';
}
else {
changes += '' + changedFields[i] + '';
changes += '';
}
changes += '';
}
}
changes += '';
changes += '';
return changes;
}
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);’
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.
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!
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.
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
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.
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.)
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!
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.
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.
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.
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?
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.
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.
Great Stuff Mark !!
I was getting value as undefined when I pasted below script, I guess the val is already comprising of value of the field. I removed .getDisplayValue() from gs.addInfoMessage(changedFields[i] + ‘: ‘ + val.getDisplayValue()); and it worked like a charm.
//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());
}
}
Quick question is there a way to post changes made on other table. I mean if value of alert record changes, I want to log those changes on incident table in work notes.I guess var gru = GlideScriptRecordUtil.get(gr); is not supported.
Quick question is there a way to post changes made on other table. I mean if value of alert record changes, I want to log those changes on incident table in work notes.I guess var gru = GlideScriptRecordUtil.get(gr); is not supported.
Answer : I was able to pass it as string to Incident and later parse it.
Thanks Mark for the above post !! 🙂