Interested in a ServiceNow event built for developers? Registration for now[dev]26 is officially open!

Variable Set fields in Classic Catalog not working(g_form.setDisplay() and g_form.setMandatory())

sattar3
Tera Contributor

Hello Everyone,

 

I am facing an issue with controlling Variable Set visibility and mandatory fields dynamically using Client Scripts in the Classic Catalog backend view.

 

I have a Catalog Item with a Variable Set called "Shipping Details"

Internal Name: shipping_address_reclaim_asset

Variable Names: first_name, last_name, street_address, apt_street_other, city, state_province, zip_postal_code. 

 

 I have a Script Include that checks if the logged-in user's company has "u_shipping_required = true" in a custom policy table (sn_hamp_territory_procurement). • Based on the result, I want to: → SHOW Variable Set + Make fields mandatory (if shipping required = true) → HIDE Variable Set + Remove mandatory (if shipping required = false)

  Script Include is returning correct response (true/false) GlideAjax call is working correctly STEP 6 alert fires confirming response = 'true' Everything works perfectly in ESC Portal g_form.setDisplay() works on regular catalog variables.

 

  g_form.setDisplay('shipping_address_reclaim_asset', false) → Variable Set still visible for ALL users in backend g_form.setMandatory('first_name', true) → Fields not showing mandatory (*) indicator in backend.

 

Script Include:

var ScriptIncludeName = Class.create();
ScriptIncludeName.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

    checkShippingRequired: function() {
        var isShippingRequired = 'false';

        // Get user passed from client script, fallback to logged-in user
        var targetUserSysId = this.getParameter('sysparm_user_id') || gs.getUserID();

        var userCompanyID = '';

        // Get Company from User record
        var userGR = new GlideRecord('sys_user');
        if (userGR.get(targetUserSysId)) {
            userCompanyID = userGR.getValue('company');
        }
        // Query custom policy table by Company
        if (userCompanyID) {
            var policyGR = new GlideRecord('sn_hamp_territory_procurement');
            policyGR.addQuery('u_company', userCompanyID);
            policyGR.query();

            if (policyGR.next()) {
                var boolVal = policyGR.getValue('u_shipping_required');

                if (boolVal == '1' || boolVal == true || boolVal == 'true') {
                    isShippingRequired = 'true';
                }
            }
        }
        return isShippingRequired;
    },
    type: 'ScriptIncludeName'
});
 

Client Scripts: 

 // OnLoad function onLoad()--> UI Type as All

 

function onLoad() {
    // 1. Get the Sys ID of the 'Requested For' user
    var reqUserId = g_form.getValue('requested_for');

    var ga = new GlideAjax('sn_hamp.ScriptIncludeName');
    ga.addParam('sysparm_name', 'checkShippingRequired');
    // 2. Pass the user ID to the Script Include
    ga.addParam('sysparm_user_id', reqUserId);
    ga.getXMLAnswer(analyzeResponse);
}

function analyzeResponse(response) {
    if (response === 'true') {
 
        g_form.setDisplay('shipping_address_reclaim_asset', true);
        g_form.setMandatory('first_name', true);
        g_form.setMandatory('last_name', true);
        g_form.setMandatory('street_address', true);
        g_form.setMandatory('state_province', true);
        g_form.setMandatory('city', true);
        g_form.setMandatory('zip_postal_code', true);
    } else {
        g_form.setMandatory('first_name', false);
        g_form.setMandatory('last_name', false);
        g_form.setMandatory('street_address', false);
        g_form.setMandatory('state_province', false);
        g_form.setMandatory('city', false);
        g_form.setMandatory('zip_postal_code', false);
        g_form.setDisplay('shipping_address_reclaim_asset', false);

    }
}
 
OnChange of Requested For Client Script: UI Type as All
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }

    // 1. Get the Sys ID of the 'Requested For' user
    var reqUserId = g_form.getValue('requested_for');

    var ga = new GlideAjax('sn_hamp.ScriptIncludeName');
    ga.addParam('sysparm_name', 'checkShippingRequired');
    // 2. Pass the user ID to the Script Include
    ga.addParam('sysparm_user_id', reqUserId);
    ga.getXMLAnswer(analyzeResponse);
}

function analyzeResponse(response) {
    if (response === 'true') {
      g_form.setDisplay('shipping_address_reclaim_asset', true);
    // alert("Line no.22 "+reqUserId);
        g_form.setMandatory('first_name', true);
        g_form.setMandatory('last_name', true);
        g_form.setMandatory('street_address', true);
        g_form.setMandatory('state_province', true);
        g_form.setMandatory('city', true);
        g_form.setMandatory('zip_postal_code', true);
        //alert("Line no.27 "+reqUserId);

    } else {
        // alert("Line no.32 "+reqUserId);
        g_form.setMandatory('first_name', false);
        g_form.setMandatory('last_name', false);
        g_form.setMandatory('street_address', false);
        g_form.setMandatory('state_province', false);
        g_form.setMandatory('zip_postal_code', false);
        g_form.setMandatory('city', false);
        g_form.setDisplay('shipping_address_reclaim_asset', false);
    }
}
 
Front end(Portal): Working fine in portal
sattar3_0-1786441559855.png

 

Backend: Fields are not mandatory and variable set showing for all users

 

sattar3_1-1786441674801.png
 
Variable Set Screenshot:
sattar3_2-1786441928628.png

 

Only failing in backend catalog view (com.glideapp.servicecatalog_cat_item_view.do) • No JavaScript errors in browser console • GlideAjax response is correct (confirmed via alerts) Any help or guidance is greatly appreciated! So that the script should works in backend as well.
 
Please share any possible way to fix this, or any changes needed in the script?
 
 
Thanks,
Sattar
14 REPLIES 14

vsrahul127
Giga Contributor

Hi @sattar3 ,

I have reviewed your problem statement and also done the code analysis, I believe we need to make the variables non mandatory first and then we need to write g_form.setDisplay("variable_ name", false)  for each individual variable, then it will work.

function onLoad() {
    // 1. Get the Sys ID of the 'Requested For' user
    var reqUserId = g_form.getValue('requested_for');

    var ga = new GlideAjax('sn_hamp.ScriptIncludeName');
    ga.addParam('sysparm_name', 'checkShippingRequired');

    // 2. Pass the user ID to the Script Include
    ga.addParam('sysparm_user_id', reqUserId);
    ga.getXMLAnswer(analyzeResponse);
}

function analyzeResponse(response) {
    if (response === 'true') {

        g_form.setDisplay('first_name', true);
        g_form.setDisplay('last_name', true);
        g_form.setDisplay('street_address', true);
        g_form.setDisplay('state_province', true);
        g_form.setDisplay('city', true);
        g_form.setDisplay('zip_postal_code', true);

        g_form.setMandatory('first_name', true);
        g_form.setMandatory('last_name', true);
        g_form.setMandatory('street_address', true);
        g_form.setMandatory('state_province', true);
        g_form.setMandatory('city', true);
        g_form.setMandatory('zip_postal_code', true);

    } else {

        g_form.setMandatory('first_name', false);
        g_form.setMandatory('last_name', false);
        g_form.setMandatory('street_address', false);
        g_form.setMandatory('state_province', false);
        g_form.setMandatory('city', false);
        g_form.setMandatory('zip_postal_code', false);

        g_form.setDisplay('first_name', false);
        g_form.setDisplay('last_name', false);
        g_form.setDisplay('street_address', false);
        g_form.setDisplay('state_province', false);
        g_form.setDisplay('city', false);
        g_form.setDisplay('zip_postal_code', false);
    }
}
 
After this modify the same in the onChange clienScript.

I hope this helps! If this solution resolves your issue, please consider marking this answer as Correct✔️.



Abhishek Pal
Giga Guru

Hi @sattar3 ,

Based on your screenshots, the GlideAjax part is working because you are receiving the expected "true" response. I would therefore not troubleshoot the Script Include first.

For this requirement, I would change the design slightly instead of directly controlling the Variable Set with multiple g_form.setDisplay() and g_form.setMandatory() calls.

The more reliable OOB approach across both:

- Classic Catalog / Try It
- Employee Center / Service Portal

is:

GlideAjax
-> Set one helper variable
-> Catalog UI Policy
-> Control Variable Set visibility and mandatory variables

ServiceNow specifically supports Catalog UI Policy Actions against an entire Variable Set, and recommends UI Policies over g_form methods where possible.

1. First verify the Catalog Client Script configuration

For both your onLoad and requested_for onChange scripts verify:

UI Type:
All

Applies on a Catalog Item view:
Checked

The Classic "Try It" page runs in the Desktop/Core UI, while ESC runs in the portal UI. UI Type = All is therefore required.

Since your alert is already firing in Classic, the script itself is loading, but please still confirm "Applies on a Catalog Item view".

2. Create a helper variable

Create a variable on the Catalog Item:

Type:
Single Line Text

Name:
shipping_required_flag

Default:
false

Do NOT set the variable's Hidden checkbox directly.

We will hide it using a Catalog UI Policy because ServiceNow requires variables used in UI Policy conditions to exist on the form; variables hidden by UI Policy can still be evaluated.

3. Create an "Always hide helper variable" Catalog UI Policy

Applies to:
A Catalog Item

Catalog Item:
Your Catalog Item

On load:
True

Reverse if false:
False

Applies on a Catalog Item view:
True

No condition is required.

Create one UI Policy Action:

Variable:
shipping_required_flag

Visible:
False

Mandatory:
Leave alone

Read only:
Leave alone

4. Simplify the onLoad Catalog Client Script

Instead of changing all of the fields from JavaScript, only populate the helper variable.

Use:

function onLoad() {

var reqUserId = g_form.getValue('requested_for');

var ga = new GlideAjax('sn_hamp.ScriptIncludeName');

ga.addParam(
'sysparm_name',
'checkShippingRequired'
);

ga.addParam(
'sysparm_user_id',
reqUserId
);

ga.getXMLAnswer(function(answer) {

answer = (answer || '')
.toString()
.trim()
.toLowerCase();

g_form.setValue(
'shipping_required_flag',
answer == 'true' ? 'true' : 'false'
);
});
}

Replace:

sn_hamp.ScriptIncludeName

with the API name of your actual Script Include.

5. Simplify the requested_for onChange Client Script

Use:

function onChange(
control,
oldValue,
newValue,
isLoading
) {

if (isLoading)
return;

if (!newValue) {
g_form.setValue(
'shipping_required_flag',
'false'
);
return;
}

var ga = new GlideAjax(
'sn_hamp.ScriptIncludeName'
);

ga.addParam(
'sysparm_name',
'checkShippingRequired'
);

ga.addParam(
'sysparm_user_id',
newValue
);

ga.getXMLAnswer(function(answer) {

answer = (answer || '')
.toString()
.trim()
.toLowerCase();

g_form.setValue(
'shipping_required_flag',
answer == 'true' ? 'true' : 'false'
);
});
}

This also fixes one small issue in your current onChange script:

if (isLoading || newValue == '') {
return;
}

When Requested For becomes empty, your current script exits without resetting the previous visibility/mandatory state.

6. Create the Catalog UI Policy that controls Shipping Details

Create another Catalog UI Policy:

Applies to:
A Catalog Item

Catalog Item:
Your Catalog Item

Short description:
Show Shipping Details when shipping is required

On load:
True

Reverse if false:
True

Applies on a Catalog Item view:
True

Condition:

shipping_required_flag
is
true

Then create these UI Policy Actions.

Action 1:

Variable:
Shipping Details [shipping_address_reclaim_asset]

Visible:
True

Mandatory:
Leave alone

Read only:
Leave alone

Then add individual Mandatory actions:

first_name
Mandatory = True

last_name
Mandatory = True

street_address
Mandatory = True

state_province
Mandatory = True

city
Mandatory = True

zip_postal_code
Mandatory = True

Do not make:

apt_street_other

mandatory if that field is optional.

Because:

Reverse if false = True

when shipping_required_flag becomes false, ServiceNow will reverse the actions:

Shipping Details
-> Hidden

Required shipping variables
-> Not mandatory

When it becomes true:

Shipping Details
-> Visible

Required shipping variables
-> Mandatory

7. Why I recommend this over the current script

Your current code is trying to control two different concerns asynchronously:

GlideAjax result
+
UI rendering/mandatory state

Moving the UI behavior into a Catalog UI Policy gives you:

GlideAjax
-> Business decision only

Catalog UI Policy
-> UI behavior only

This is much easier to maintain and troubleshoot across Classic Catalog and ESC.

Also, ServiceNow applies the following priority:

Mandatory
-> Read Only / Display

Therefore, if a mandatory empty variable is still mandatory, ServiceNow will not allow that variable/Variable Set to be hidden.

That is another reason the mandatory and visibility behavior should be managed together through one Catalog UI Policy with Reverse if false.

8. Check for another UI Policy overriding your result

Even if the variables are not mandatory at the variable definition level, another Catalog UI Policy or Catalog Client Script may still be changing them.

From the Classic Catalog:

Maintain Items
-> Open Catalog Item
-> Try It

Enable:

System Settings
-> Developer
-> JavaScript Log and Field Watcher

Right-click one of the variables, for example:

First Name

and select:

Watch Variable

Then change Requested For.

The Variable Watcher will show whether:

- Your Catalog Client Script ran
- A Catalog UI Policy changed Mandatory
- Another policy/script changed it afterward

This is the best way to confirm exactly what is overriding the field.

9. One more improvement to your Script Include

Your current boolean check works, but it can be simplified.

For example:

checkShippingRequired: function() {

var userSysId =
this.getParameter('sysparm_user_id') ||
gs.getUserID();

var userGR = new GlideRecord('sys_user');

if (!userGR.get(userSysId))
return 'false';

var companySysId =
userGR.getValue('company');

if (!companySysId)
return 'false';

var policyGR =
new GlideRecord(
'sn_hamp_territory_procurement'
);

policyGR.addQuery(
'u_company',
companySysId
);

policyGR.setLimit(1);
policyGR.query();

if (!policyGR.next())
return 'false';

return policyGR.getValue(
'u_shipping_required'
) == '1' ? 'true' : 'false';
},

This keeps the Script Include responsible only for returning true/false.

Recommended final design:

Requested For
-> GlideAjax
-> Check user's Company
-> Check u_shipping_required
-> Set shipping_required_flag
-> Catalog UI Policy
-> Show/hide Shipping Details
-> Set/unset mandatory fields

I would use this approach instead of adding DOM manipulation or modifying the Classic Catalog UI Page.

Also do not modify:

com.glideapp.servicecatalog_cat_item_view

to solve this.

Official references:

Service Catalog UI Policies:
https://www.servicenow.com/docs/r/servicenow-platform/service-catalog/c_ServiceCatalogUIPolicy.html

Create Catalog UI Policy:
https://www.servicenow.com/docs/r/servicenow-platform/service-catalog/t_CreatUIPolicyForSvcCalgIt.ht...

Catalog Client Scripts:
https://www.servicenow.com/docs/r/api-reference/scripts/c_CatalogClientScriptCreation.html

Debug Service Catalog Variables:
https://www.servicenow.com/docs/r/servicenow-platform/service-catalog/debug-a-service-catalog-variab...

Hope this helps!

If this response helped, please mark it as Helpful.
If it resolves your issue, please Accept it as Solution.

Kind Regards,
Abhishek Pal

VJ_Srivastava
Tera Contributor

Hello @sattar3,

From an architectural perspective, this discrepancy perfectly highlights the fragility of relying on complex client-side scripting to control UI behavior across different rendering engines (Service Portal versus Classic UI).

Whenever we see logic working in the Portal but failing in the backend, it is almost always an indicator that the architecture has strayed too far from the platform's native declarative capabilities. Using g_form methods to dynamically show and hide entire Variable Sets inside an asynchronous GlideAjax callback introduces unnecessary technical debt and risks breaking during future family release upgrades.

To build scalable, upgrade-safe catalog items, I strongly advise decoupling your data retrieval from your UI manipulation. Use your GlideAjax call purely to fetch the data and store that boolean result in a hidden reference variable on the form. From there, leverage native Catalog UI Policies to govern the mandatory and visibility states. Declarative UI Policies are inherently designed by ServiceNow to operate consistently across all interfaces—Workspaces, Portals, and Classic views—ensuring your forms remain resilient and easy to maintain.

NehaG8791370651
Tera Contributor

Hi @sattar3,

The classic backend catalog view (UI16) can sometimes be incredibly stubborn when asynchronous GlideAjax callbacks attempt to manipulate container or Variable Set visibility. The issue you are encountering is often caused by scoping loss with the analyzeResponse function sitting outside your main onLoad or onChange blocks, combined with how the classic UI rendering engine handles DOM updates from background scripts.

The most robust, foolproof way to solve this—and vastly simplify your client-side code—is to use the Hidden Helper Variable + UI Policy design pattern:

  1. Create a Hidden Variable: Add a new, hidden Single Line Text variable to your catalog item (e.g., is_shipping_required).

  2. Refactor the Client Script: Change your GlideAjax script to use an inline callback function to completely eliminate scoping issues, and use it strictly to update the hidden variable.

    JavaScript
     
    function onChange(control, oldValue, newValue, isLoading) {
        if (isLoading || newValue == '') return;
    
        var reqUserId = g_form.getValue('requested_for');
        var ga = new GlideAjax('sn_hamp.ScriptIncludeName');
        ga.addParam('sysparm_name', 'checkShippingRequired');
        ga.addParam('sysparm_user_id', reqUserId);
    
        // Use an inline callback
        ga.getXMLAnswer(function(response) {
            g_form.setValue('is_shipping_required', response);
        });
    }
  3. Create a Catalog UI Policy: Create a no-code UI Policy with the condition is_shipping_required = true. Check the "Reverse if false" box.

  4. Add UI Policy Actions: Set the shipping_address_reclaim_asset Variable Set to Visible = True, and the individual fields to Mandatory = True.

This forces the platform's native UI engine to handle the visibility and mandatory states, which works flawlessly and consistently across both the Service Portal and the classic backend!

sattar3
Tera Contributor

@Ehab Pilloor @vsrahul127 @Abhishek Pal @VJ_Srivastava @NehaG8791370651 @Ankur Bawiskar Thanks everyone.

Here the issue is with Variable Set scope-previously it is in Asset Management Comm scope.
I created new variable set in Hardware Asset Management scope which is working as expected in both Backend and Frontend.

 

Thanks,

Sattar