Bi-Directional Synchronization of Reference Fields in ServiceNow (Without Infinite Loop)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 hours ago
Hi Community 👋,
I’d like to share a solution for a common but tricky requirement in ServiceNow:
keeping two reference fields synchronized in both directions without causing infinite recursion.
🔍 Requirement
On a ServiceNow form, there are two reference fields:
Oracle Supplier
Alternate Supplier
Business Need
When the user changes Oracle Supplier, the related Alternate Supplier should auto-populate.
When the user changes Alternate Supplier, the related Oracle Supplier should auto-populate.
The solution must avoid infinite onChange loops.
🛠 Implementation Overview
The solution uses:
Two onChange Client Scripts
One Script Include
GlideAjax for server-side lookup
Window-level flags to prevent recursion
Each client script listens to one field and updates the other.
To avoid infinite looping, we use boolean flags stored on the window object.
📌 Client Script 1
Oracle Supplier → Alternate Supplier
// onChange of oracle_supplier
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === oldValue) return;
if (window.skipOracleChange) {
window.skipOracleChange = false;
return;
}
var ga = new GlideAjax('SupplierUtils');
ga.addParam('sysparm_name', 'getAlternateFromSupplier');
ga.addParam('sysparm_supplier', newValue);
ga.getXMLAnswer(function(response) {
var result = JSON.parse(response);
window.skipAlternateChange = true;
g_form.setValue('alternate_supplier', result.alternate_name);
});
}
📌 Client Script 2
Alternate Supplier → Oracle Supplier
// onChange of alternate_supplier
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === oldValue) return;
if (window.skipAlternateChange) {
window.skipAlternateChange = false;
return;
}
var ga = new GlideAjax('SupplierUtils');
ga.addParam('sysparm_name', 'getSupplierFromAlternate');
ga.addParam('sysparm_alternate', newValue);
ga.getXMLAnswer(function(response) {
var result = JSON.parse(response);
window.skipOracleChange = true;
g_form.setValue('oracle_supplier', result.legal_supplier);
});
}
📘 Script Include
SupplierUtils
var SupplierUtils = Class.create();
SupplierUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getAlternateFromSupplier: function() {
var supplierId = this.getParameter('sysparm_supplier');
var result = {};
var gr = new GlideRecord('sn_fin_supplier');
if (gr.get(supplierId)) {
result.alternate_name = gr.getValue('alternate_name');
}
return new JSON().encode(result);
},
getSupplierFromAlternate: function() {
var altId = this.getParameter('sysparm_alternate');
var result = {};
var gr = new GlideRecord('sn_fin_supplier');
if (gr.get(altId)) {
result.legal_supplier = gr.getValue('oracle_supplier');
}
return new JSON().encode(result);
}
});
🔁 Recursion Handling Explained
Without safeguards, this scenario causes an infinite loop:
Oracle Supplier changes
Alternate Supplier auto-updates
Alternate Supplier onChange fires
Oracle Supplier auto-updates
Loop continues indefinitely ❌
✅ Solution
We use two window-level flags:
window.skipOracleChange
window.skipAlternateChange
When one field is updated programmatically:
A flag is set before updating the other field
The receiving onChange detects the flag and exits immediately
The flag is reset after one skip
This ensures one-time synchronization without recursion.
✅ Advantages of This Approach
✔ Prevents infinite loops reliably
✔ Clean and easy to understand
✔ Uses standard GlideAjax pattern
✔ Modular and reusable Script Include
✔ Minimal code duplication
🏁 Conclusion
This design ensures data consistency between two reference fields while maintaining system stability.
Using simple window flags is an effective and lightweight way to handle bi-directional synchronization in client scripts.
I hope this helps anyone facing a similar requirement.
Happy to hear suggestions or alternate approaches from the community 😊
Thanks,
Venkat Ajay P