Allen Andreas
Administrator
Administrator

Have you ever had a need to be able to access Order Guide variables within the Order Guide script field?

Within Order Guides there really isn't an efficient way to access the Order Guide variables within the Order Guide script field as easy as you can within a Record Producer. Within a Record Producer, you can access the variables by simply using: producer.variable_name.

For Order Guides, sure you can cascade the values and/or use a "Rule base" to look at the variables and their values and then add catalog items dynamically that way or set specific field values on that catalog item based on an order guide variable's value, but we're talking about the Order Guide script field.

find_real_file.png

The Order Guide script field is executed once you click the "Next" or "Choose Options" button on the initial order guide page and proceed to the next steps.

Possible Use Cases: Common use cases for this script field is to dynamically add catalog items to the order guide process in an automated way when you're in a situation where you may not want to setup your Rule base to house tons of catalog items (i.e., you need to check a value on another table, then based on that and various Order Guide variable values, you may need to add 5 random catalog items for this user). Another use case could be that you need to execute some sort of server side script to capture something somewhere else and need access to the Order Guide variables and their values to complete this task.

In any case, previously, this question has mostly gone unanswered or partially answered. For example, there's been the suggestion to use something like this in the order guide script field to get the value of a particular Order Guide variable:

g_request.getParameter('IO:sys_id_of_variable')

But that doesn't work on the Portal...

Then there's actually a way to access the Order Guide variables and their values within the sc_cart record for the user as the data is captured within the "Current guide serial" field, but it contains the variable sys_ids without their variable names and requires you to either use that variable sys_id or do a lookup within the database and retrieve the variable name (or label) and even then it's still a bit of a mess that you have to filter through as it's just one massive block of string, example:

find_real_file.png

Who knows, maybe you didn't even know about that field and want to stop reading here and can use that and you'll take it from here. If so, by all means, go for it!

Use Case for this Article: Let's say we have a New Hire order guide and based on the value of the order guide reference variable "hiring_group", this determines if we need to add a specific catalog item to this process. That catalog item is saved in a separate reference field within the selected group's sys_user_group record.

Solution: The solution I'm proposing helps organize things a bit more and makes it easier to use the data to then do whatever you need to do. It requires an onChange Client Script for each of the variables where you'd like to capture both the variable name and the value and uses GlideAjax to communicate with a script include. A client callable script include to be leveraged by the GlideAjax call which executes server script to record the data to the user's relevant service catalog cart record. And then of course the script you need in your Order Guide script field.

Solutions Steps:

  1. Create a client callable script include named: orderGuideVars and check the "Client callable" checkbox prior to writing any script:
    find_real_file.png
    And the full script can look like this (you can copy and paste the entire thing and replace what you have):
    var orderGuideVars = Class.create();
    orderGuideVars.prototype = Object.extendsObject(AbstractAjaxProcessor, {
        addVariable: function() {
            var guideID = this.getParameter('sysparm_guideID'); //retrieves the order guide sys_id from the client script
            var varName = this.getParameter('sysparm_varName'); //retrieves the order guide variable field name from the client script
            var varValue = this.getParameter('sysparm_varValue'); //retrieves the order guide variable value from the client script
            var userID = this.getParameter('sysparm_userID'); //retrieves the current user's sys_id from the client script
    
            var obj; //declares a JavaScript variable called obj for use later in the script
    
            //declares a JavaScript variable called runUpdate that will run function updateCart passing in parameters that we retrieve earlier in this script
            //the function will return true if a cart record associated with the order guide is found for this user
            //the function will return false if no cart record associated with the order guide is found for this user
            var runUpdate = updateCart(guideID, varName, varValue, userID);
    
            if (!runUpdate) { //if runUpdate equals false
    
    			//execute function updateDefault to instead update the default cart record for this user passing in just the variable name, variable value, and the user's sys_id we retrieved earlier in this script
                updateDefault(varName, varValue, userID);
            }
    
            function updateDefault(varName, varValue, userID) {
                var gr = new GlideRecord('sc_cart'); //preparing to query the sc_cart table
                gr.addQuery('name', "DEFAULT"); //filter for records where the name is DEFAULT
                gr.addQuery('user', userID); //filter for record's associated to user's sys_id
                gr.query(); //execute the query
                if (gr.next()) { //if a record is found that matches the above, proceed
                    try { //try the below first
                        obj = JSON.parse(gr.special_instructions); //set JavaScript variable 'obj' to the parsed value of the special instructions field
                        obj[varName] = varValue; //set the key value pair related to the order guide variable name and value, within the object
                    } catch (e) { //if an error occurs from the above, proceed with catch
                        obj = {}; //set JavaScript variable 'obj' as an object
                        obj[varName] = varValue; //set a key value pair related to the order guide variable name and value, within the object
                    }
                    gr.setValue('special_instructions', JSON.stringify(obj)); //set the special instructions field to the stringified JavaScript variable 'obj'
                    gr.update(); //update the cart record
                }
                return true; //return true
            }
    
            function updateCart(guideID, varName, varValue, userID) {
                var gr = new GlideRecord('sc_cart'); //preparing to query the sc_cart table
                gr.addQuery('current_guide', guideID); //filter for records that have the current guide field related to the order guide
                gr.addQuery('user', userID); //filter for record's associated to user's sys_id 
                gr.query(); //execute the query
                if (gr.next()) { //if a record is found that matches the above, proceed
                    try { //try the below first
                        obj = JSON.parse(gr.special_instructions); //set JavaScript variable 'obj' to the parsed value of the special instructions field
                        obj[varName] = varValue; //set the key value pair related to the order guide variable name and value, within the object
                    } catch (e) { //if an error occurs from the above, proceed with catch
                        obj = {}; //set JavaScript variable 'obj' as an object
                        obj[varName] = varValue; //set a key value pair related to the order guide variable name and value, within the object
                    }
                    gr.setValue('special_instructions', JSON.stringify(obj)); //set the special instructions field to the stringified JavaScript variable 'obj'
                    gr.update(); //update the cart record
                    return true; //return true
                } else {
                    return false; //return false
                }
            }
    
            return ''; //return null to client script -- nothing is really needed to go back to the client script anyway
        },
        type: 'orderGuideVars'
    });
  2. Create an onChange Client Script for however many order guide variables you want to capture the name and value of. It's recommended to use settings like this:
    find_real_file.png
    And the full script can look like this (you can copy and paste the entire thing and replace what you have):
    function onChange(control, oldValue, newValue, isLoading) {
        if (isLoading || newValue == '') {
            return;
        }
        var guideID; //creating JavaScript variable called guideID
        if (g_form.getUniqueValue()) { //if g_form.getUniqueValue() returns a valid value, then proceed
            guideID = g_form.getUniqueValue(); //sets the JavaScript variable 'guideID' to the sys_id of the order guide - Portal UI
        } else { //if g_form.getUniqueValue() returns an invalid value, then proceed
            guideID = g_form.getParameter('sysparm_guide'); //sets the JavaScript variable 'guideID' to the sys_id of the order guide - Platform UI
        }
        var ga = new GlideAjax('orderGuideVars'); //preparing a call to the script include 'orderGuideVars'
        ga.addParam('sysparm_name', 'addVariable'); //calling the specific function in the orderGuideVars script include called addVariable
        ga.addParam('sysparm_guideID', guideID); //passing the order guide sys_id to the script include
        ga.addParam('sysparm_varName', 'replace_with_order_guide_variable_name'); //passing the order guide variable name to the script include
        ga.addParam('sysparm_varValue', newValue); //passing the new value selected for this order guide variable to the script include
        ga.addParam('sysparm_userID', g_user.userID); //passing the current user's sys_id to the script include
        ga.getXMLAnswer(callBackFunc); //executing the GlideAjax call
    }
    
    function callBackFunc(response) { //receiving response from the script include -- no real response is needed
        //
    }
  3. Lastly, you can now parse the special instructions field on the current record to then add a catalog item as you need. You can access any order guide variable that you used the onChange Client Script with by referring to it as: obj.variable_name. In this case, we will query the sys_user_group table and use the hiring_group order guide variable (that we're using in this example use case) to retrieve the custom field's value we placed on the group table that houses the special catalog item we want to add to this order guide process. The script would look something like this:
    var getCart = new GlideRecord('sc_cart'); //preparing to query the sc_cart table
    getCart.get(current.sys_id); //retrieving the current order guide record for this user
    
    var obj = JSON.parse(getCart.special_instructions); //setting the JavaScript variable 'obj' to the parsed value of the special instructions field on the retrieved sc_cart record found above
    
    var getItem = new GlideRecord('sys_user_group'); //preparing to query the sys_user_group table
    getItem.get(obj.hiring_group.toString()); //retrieving the hiring group's group record that was selected on the order guide form
    
    guide.add(getItem.getValue('u_custom_catalog_item')); //adding the special catalog item to this order guide process that is listed on the hiring group's group record
  4. Additional note, you can also use guide.remove('sys_id_of_catalog_item'); as well, if needed

Documentation: Order Guide Script field: https://docs.servicenow.com/en-US/bundle/sandiego-servicenow-platform/page/product/service-catalog-m...

----------

Shoutout/Additional Credit: I want to give a special shoutout to @themasterofthecloud (Torsten Brosow) who initially brought this to my attention about a year ago in another thread located here: https://community.servicenow.com/community?id=community_question&sys_id=d6170bc7dbb5c890414eeeb5ca96...

Since then, I've seen this question pop-up a few times and I hadn't really had the time to take things a bit further and write an article, but...better late than never, so here it is. I've taken what was discussed in that thread and then tweaked things a bit to ensure it works in a wider setting, for the right user, for the right service catalog cart record.

----------

If you have any questions/concerns/issues, feel free to comment below

*Please don't forget to mark this article as Helpful and Bookmark it!

Thank you and take care!

Comments
themasterofthec
Tera Contributor

Thx for mentioned it (me). 🙂

Was there a rule, never ever use gr 🙂

Allen Andreas
Administrator
Administrator

It's wrapped in a function 😉

Debbie Elder1
Tera Contributor

Thx, this works beautifully as far as selecting and adding the additional cat items to the order... However! After the summary, once I submit, the extra items (highlighted) don't create RITMs...

only the Email Account, which was rule-based. I have tried it without a rule-based item too, and nothing gets created.

Thoughts? Something missing in my cart configuration perhaps?

Debbie Elder1
Tera Contributor

It seems to be something to do with my instance creating a default cart and sometimes an order-guide-specific cart, seems to be getting tangled up.... 

eg. Carla Humble, new user, no carts... when she opens a cat item form, a default cart gets created. When she changes my variables (persona and job title) your script updates the only cart for Carla, the default one. But when she clicks choose options, a new cart is created specific to this order guide sys id.. but the variables are not on this guide so the order guide script returns empty values from the special instructions obj.

Is this correct default behaviour for carts or is there a cart setting I am missing?

Debbie Elder1
Tera Contributor

I fixed my cart problem by altering your suggested order guide script to:

var getCart = new sn_sc.CartJS();
var obj = JSON.parse(getCart.getSpecialInstructions());

Nitin Panchal
Tera Guru

Thank you so much . This is great ! 

Roy Wallimann
Tera Contributor

Hi Allen

Thank you very much for providing this great functionality!

I noticed that sometimes there are some issues in the following cases:

  • User goes on second page and goes back on first page
  • User has no Default Cart
  • Order Guide Script takes only Special Instruction value from specific cart

To fix this issues I have modified the following scripts (refer to the large comment lines):

Script Include:

  • Create default cart if user has no default cart
addVariable: function() {
		var guideID = this.getParameter('sysparm_guideID'); //retrieves the order guide sys_id from the client script
		var varName = this.getParameter('sysparm_varName'); //retrieves the order guide variable field name from the client script
		var varValue = this.getParameter('sysparm_varValue'); //retrieves the order guide variable value from the client script
		var userID = this.getParameter('sysparm_userID'); //retrieves the current user's sys_id from the client script

		var obj; //declares a JavaScript variable called obj for use later in the script

		//declares a JavaScript variable called runUpdate that will run function updateCart passing in parameters that we retrieve earlier in this script
		//the function will return true if a cart record associated with the order guide is found for this user
		//the function will return false if no cart record associated with the order guide is found for this user
		var runUpdate = updateCart(guideID, varName, varValue, userID);

		if (!runUpdate) { //if runUpdate equals false
/////////////////////////////////////////////////////////////////////////////////////////////
//This is the code I have added to fix the issue with no default cart
			//Generate default cart if cart does not exist
			var cart = new sn_sc.CartJS();
/////////////////////////////////////////////////////////////////////////////////////////////

			//execute function updateDefault to instead update the default cart record for this user passing in just the variable name, variable value, and the user's sys_id we retrieved earlier in this script
			updateDefault(varName, varValue, userID);
		}

		function updateDefault(varName, varValue, userID) {
			var gr = new GlideRecord('sc_cart'); //preparing to query the sc_cart table
			gr.addQuery('name', "DEFAULT"); //filter for records where the name is DEFAULT
			gr.addQuery('user', userID); //filter for record's associated to user's sys_id
			gr.query(); //execute the query
			if (gr.next()) { //if a record is found that matches the above, proceed
				try { //try the below first
					obj = JSON.parse(gr.special_instructions); //set JavaScript variable 'obj' to the parsed value of the special instructions field
					obj[varName] = varValue; //set the key value pair related to the order guide variable name and value, within the object
				} catch (e) { //if an error occurs from the above, proceed with catch
					obj = {}; //set JavaScript variable 'obj' as an object
					obj[varName] = varValue; //set a key value pair related to the order guide variable name and value, within the object
				}
				gr.setValue('special_instructions', JSON.stringify(obj)); //set the special instructions field to the stringified JavaScript variable 'obj'
				gr.update(); //update the cart record
			}
			return true; //return true
		}

		function updateCart(guideID, varName, varValue, userID) {
			var gr = new GlideRecord('sc_cart'); //preparing to query the sc_cart table
			gr.addQuery('current_guide', guideID); //filter for records that have the current guide field related to the order guide
			gr.addQuery('user', userID); //filter for record's associated to user's sys_id 
			gr.query(); //execute the query
			if (gr.next()) { //if a record is found that matches the above, proceed
				try { //try the below first
					obj = JSON.parse(gr.special_instructions); //set JavaScript variable 'obj' to the parsed value of the special instructions field
					obj[varName] = varValue; //set the key value pair related to the order guide variable name and value, within the object
				} catch (e) { //if an error occurs from the above, proceed with catch
					obj = {}; //set JavaScript variable 'obj' as an object
					obj[varName] = varValue; //set a key value pair related to the order guide variable name and value, within the object
				}
				gr.setValue('special_instructions', JSON.stringify(obj)); //set the special instructions field to the stringified JavaScript variable 'obj'
				gr.update(); //update the cart record
				return true; //return true
			} else {
				return false; //return false
			}
		}

		return ''; //return null to client script -- nothing is really needed to go back to the client script anyway
	},

Order Guide Script:

  • Check if Special Instructions from specific cart are available
    • If not, take Special Instructions from default cart
      • At the end, clear the Special Instructions from default cart
var catalogItemSysIds;
var getCartFromOrderGuide = new GlideRecord('sc_cart'); //preparing to query the sc_cart table
getCartFromOrderGuide.get(current.sys_id); //retrieving the current order guide record for this user
if(getCartFromOrderGuide.special_instructions){
	var obj = JSON.parse(getCartFromOrderGuide.**VARIABLE_NAME**); //setting the JavaScript variable 'obj' to the parsed value of the special instructions field on the retrieved sc_cart record found above
	if(obj.**VARIABLE_NAME**){
		catalogItemSysIds = obj.**VARIABLE_NAME**.split(',');
	}
}
if(!catalogItemSysIds){
	//Get Default cart from user
	var getCartDefault = new sn_sc.CartJS();
	var objDefault = JSON.parse(getCartDefault.getSpecialInstructions());
	catalogItemSysIds = objDefault.**VARIABLE_NAME**.split(',');
}
if(catalogItemSysIds.length > 0){
	for(var index in catalogItemSysIds){
		var grPSCI = new GlideRecord('pc_software_cat_item');
		if(grPSCI.get(catalogItemSysIds[index])){
			guide.add(grPSCI.getUniqueValue());
		}
	}
}
//Check if Default Cart has special instructions with "**VARIABLE_NAME**"
var cart = new sn_sc.CartJS();
if(cart.getSpecialInstructions().indexOf('**VARIABLE_NAME**') > -1){
	//Clear special instructions
	cart.setSpecialInstructions(''); 
}

Regards,

Roy

prem kumar2
Tera Contributor

Hi @Allen Andreas 
Is this article work for following scenario.
i have a task when an order guide is requested,In that one of the item (X) is removed manually and then when request is created.The X item should be added automatically with necessary variables.Is that possible?

Allen Andreas
Administrator
Administrator

Hi prem,

My article is about accessing the order guide variables to then do something else.

For your scenario, it sounds like it may be better for you to make the catalog item mandatory so that they don't remove it. Here's another article which explains one way on how to do that: https://www.servicenow.com/community/it-service-management-articles/mandatory-rule-base-in-order-gui... 

Thanks!

Jesper Slot
Tera Guru

Hi @Allen Andreas 

Wow! Great article, thank you for this!

 

Years later, I find that we are still struggling with this - with our software items. We use Client Software Distribution (CSD) and we have a lot of software packages available. It works but portal wise, it's a mess. The guys creating the software packages also have access to create the software items in ServiceNow, but not a lot of other access and I don't think that want it differently.

We don't have a good category structure as the data quality is poor. So you can just go in and either browser through available software items or search if you know exactly what you want. Not a good user experience. 

 

When I asked this question back in 19 I were not ready for client side/server side scripting with ajax calls to script includes etc .. it was way too advanced for me, but luckily I'm still working with this great platform and I learn every day. I could certainly manage to do so now. 

However, I also cannot help thinking is this really the best way? Much time has passed.. do we have other and better options today?

In general I want to build stuff working dynamically so that our packaging team would need to do as little as possible (but of cause we need better data quality, for example some categories) for making it available on the portal and also I don't want a task in the future development of software packages, whenever there are new packages available, as I would become a huge bottleneck. Our ServiceNow team is inhouse only and tiny. 

Daniel Rubin
Tera Contributor

Can order guide variables be accessed via the Reporting Module? I have a requirement to report on an order guide variable that isn't passed to a catalog item. I don't see a way to do this but your article here is quite extensive so hoping you have some insight!

Jesper Slot
Tera Guru

@Daniel Rubin 

You could just create a hidden variable on the catalog item and then pass it. Then it is available for reporting.

Version history
Last update:
‎06-24-2022 10:46 AM
Updated by: