The Zurich release has arrived! Interested in new features and functionalities? Click here for more

Amarjeet Pal
Kilo Sage
Kilo Sage

 

M2M table in catalog builder (Wizard)


There’s a helpful article on the ServiceNow Community about this, but here’s a quick summary using the Service Offering example:

  • When a Service Offering is selected, the related list Available for Subscribers on the catalog item should be populated.
  • This behavior can vary based on requirements like User Criteria.

To achieve this:

  1. Create two Catalog Wizard Questions:

    • One for selecting the Service Offering.
    • One for populating the related list.
  2. Mapping Difference:

    • The first question should have map to set = false.
    • The second should have map to set = true.
  3. Technical Setup:

    • The first field must be a List Collector.
    • It should be placed on a Variable Set.
    • Ensure the OOTB Catalog OnChange client script is active on the Variable Set—this is essential for populating the related list dynamically.



function onChange(control, oldValue, newValue, isLoading) {
 var CATALOG_QUESTION_NAME =Service offering variable name;
 var CATALOG_PRODUCER_NAME = M2M producer set name ;
 var CATALOG_PRODUCER_QUESTION_NAME = 'service_offering';
 
 if (isLoading) {
  g_form.setDisplay(CATALOG_PRODUCER_NAME, false);
  convertRowValuesToCommaSeparatedString(CATALOG_QUESTION_NAME, CATALOG_PRODUCER_NAME, CATALOG_PRODUCER_QUESTION_NAME);
  return;
 }
 
 var valueArr = !newValue ? [] : newValue.split(',');
 
 addRows(CATALOG_PRODUCER_NAME, CATALOG_PRODUCER_QUESTION_NAME, valueArr);
}
 
 
//TODO: move to UI scripts and use it in here.
function addRows(producerSetName, producerSetQuestionName, includedValues) {
    var recordMap = getRecordMap(producerSetName, producerSetQuestionName);
    var ans = [];
    for (var i = 0; i < includedValues.length; i++) {
        var row = {};
        row[producerSetQuestionName] = includedValues[i];
  if (recordMap && recordMap[includedValues[i]])
   row['sys_id'] = recordMap[includedValues[i]];
 
        ans.push(row);
    }
 
    g_form.setValue(producerSetName, JSON.stringify(ans));
 
}
 
function convertRowValuesToCommaSeparatedString(questionName, producerSetName, producerQuestionName) {
    var value = g_form.getValue(producerSetName);
 
    try {
        value = JSON.parse(value);
    } catch (e) {
        value = [];
    }
 
    var ans = [];
    for (var i = 0; i < value.length; i++) {
        var row = value[i];
        if (row && row.hasOwnProperty(producerQuestionName))
            ans.push(row[producerQuestionName]);
    }
 
    g_form.setValue(questionName, ans.join());
}
 
function getRecordMap(producerSetName, producerQuestionName) {
 
    var fieldObj = g_form.$private.getField(producerSetName);
 
    if (fieldObj._recordMap)
        return fieldObj._recordMap;
 
    var originalValue = fieldObj.originalValue || '[]';
 
    var originalJSON;
    try {
        originalJSON = JSON.parse(originalValue);
    } catch (e) {
        originalJSON = [];
    }
 
    var recordMap = {};
    for (var i = 0; i < originalJSON.length; i++) {
        var obj = originalJSON[i];
        if (obj && obj[producerQuestionName] && obj['sys_id'])
            recordMap[obj[producerQuestionName]] = obj['sys_id'];
    }
    fieldObj._recordMap = recordMap;
    return fieldObj._recordMap;
}

 

 

This is a major step that isn’t documented. Everything else follows the out-of-the-box behavior—try to replicate it similar to how "Available for" (User Criteria ) works.

 

Thanks
Amarjeet Pal