Jon G Lind
ServiceNow Employee
ServiceNow Employee

Make two connected lists’ values dependent (and exclusive)

In some scenarios you may want to have two lists on the screen which are dependent upon each other.  I.e., once you select a value in a list, you need to add it to another list and remove it from the source.

 

In this example we are setting a list of related Service configuration items with a Many-To-Many (M2M) table.  Once a Service is added it should not be available to add again.  Conversely, once the Service is removed from the list it should become available again.

 

I.e., the two lists are exclusive—a Service can only be in one list and not the other.

 

Screen_Recording_2022-11-21_at_3_24_23_PM_AdobeExpress.gif

 

In this demo we will show you how to set up a ServiceNow Connected List to filter based on the values of another connected list.

 

TL;DR

A working example of this code is available in Github: https://github.com/ServiceNowNextExperience/uib-xor-lists

 

You may fork this repo, install it in your instance, then open “UIB Exclusive List” in UI Builder to see it in action with any Incident record. (Path: /now/exclusive-list/home/incident/41c11034356d7410f877125fed0fc054 )

 

Add State Parameters

Before we configure the two lists, let’s add three state parameters.  Spoiler—we will be saving a filter in client state to use to filter the list on the right each time a value from that list is selected.

 

  1. Name: “availableFilter”

Type: String

Default value: “sys_id=DO_NOT_RETURN_ANYTHING_YET”

(Yes, it’s hacky but we do not want the list to show any values until we are ready.)

 

  1. Name: “selectedRefreshRequested”
    Type: String
    Default value: (Leave it blank)

 

(We will be using this later to force refreshing of the left-hand list.)

 

Setup the Lists

Setup two lists side by side within a container set to Grid layout with two columns, like this:

 

Selected Services (Left-Hand Side)

  1. Label AND Title: “Selected Services” (use the little “i” to set component label)
  2. Table: Impacted CIs (task_cmdb_ci_service)
  3. Filter:
    1. Task is @context.props.sysId
    2. Sort By: Configuration Item ascending
  4. Add Columns: Configuration Item > Name
  5. Max Rows: 20
  6. Refresh Requested: @STate.selectedRefreshRequested (more on this later)

Available Services (Right-Hand Side)

  1. Label AND Title: “Available Services”
  2. Table: Services (cmdb_ci_service)
  3. Max Rows: 20
  4. Filter: @STate.availableFilter (use the DB icon)
  5. Add Columns: Name

Your layout should look like this:

JonGLind_0-1669068378319.png

 

Data Resources

NOTE: Ideally, we would use the event “DATA REQUEST COMPLETED” so when one list is updated the values can be captured and the filter updated.  However, there is a bug and this event does not fire for a connected list component (and there is no plans to fix it).

 

The workaround is to add an extra query to the M2M table so we know which services have been selected in order to build a filter to exclude those services from the available list.

 

Look Up Selected Services

  1. Open Data Resources (with the bottom left DB icon)
  2. Click “+ Add”
  3. Select Applications: Global, Server Data: Look Up Records
  4. Click “Add”
  5. Select the newly created data resource and configure the following:
    1. Click the “I” and change:
      1.      Name: “Look Up Selected Services”
      2.      ID: “look_up_selected”
    2. Table: Impacted CIs (task_cmdb_ci_service)
    3. Conditions: Task is @context.props.sysId
    4. Return fields: Configuration Item

Create Record

  1. Open Data Resources
  2. Click “+ Add”
  3. Select Applications: Global, Server Data: Create Record
  4. Click “Add”
  5. Rename the newly created data resource to “Create Record”

Delete Record

  1. Open Data Resources
  2. Click “+ Add”
  3. Select Applications: Global, Server Data: Create Record
  4. Click “Add”
  5. Rename the newly created data resource to “Delete Record”

Configure Client Script

We will quickly create a helper script to allow us to easily force a refresh of the left-hand Selected list as well as the Selected Services data resource.  Use the script icon at the bottom left.

 

Name: “Refresh Requested”

function handler({api, event, helpers, imports}) {
    api.data.look_up_selected.refresh();
    api.setState("selectedRefreshRequested", Date.now());
}

 

Configure Events

This is the heart of this exercise.  Whenever the selected list changes we want to change the filter for the available services to exclude the already selected values.  To do so we will use our Look Up Selected Services data resource to build a “not in” filter when the data resource is successfully updated.

 

Look Up Selected Services Data Resource

Next we’ll grab the sys ids of the already selected services and build a “not in” query to filter the right-hand list of available services and save it in a state variable which we have already bound to that list’s filter property.

 

  1. Select Look Up Selected Resources under data resources and choose the Events tab.
  2. Click “Add Event Mapping” and choose “Data Fetch Succeeded”
  3. Click “Add event handler” and choose “Update client state parameter”
  4. In the top right of this modal select “Mode: Script” and use the following script:
function evaluateEvent({api : {data : {look_up_selected }}, event}) {
    const alreadySelectedSysIds = look_up_selected.results.map(({cmdb_ci_service}) => cmdb_ci_service.value);
    const filter = `active=true^sys_idNOT IN${alreadySelectedSysIds}^ORDERBYname`;
    console.log(filter);

    return {
        propName: "availableFilter",
        value: filter
    };
}

 

Add Available Services List Events

Go to the Available Services list component and choose the “Events” tab.

 

Use “Add event mapping” and selected “Row Clicked”.

 

Use “Add event handler” and create the following two handlers:

 

  1. Create Record > Execute (down at the bottom of the list)
    1. Table: Impacted CIs (task_cmdb_ci_service)
    2. Edit Field Values:
      1.  Task is @context.props.sysId
      2.  AND Configuration Item is @payload.sys_id
      3.  Click “Apply” and your configuration should look as follows:

Picture2.png

2. Scripts > Refresh Requested

 

These events will add a new record to your Selected M2M table and refresh the list.  Your list of events for Row Clicked should look like this:

JonGLind_2-1669068378337.png

Try it out!  At this point you should be able to load the page and click the link on a Service in the right-hand list and see the left-hand list update.  We still need to update events for the left-hand for it all to work, but we’re getting there!

 

Left (Selected) List

Now we need to configure the left list to delete selections when a row is clicked and to refresh both of the lists.

 

Add Selected Services List Events

Follow the same process as above to add two event mappings for “Row Clicked” on the left-hand Selected Services list:

 

  1. Delete Record > Execute
    1. Table: Impacted CIs (task_cmdb_ci_service)
    2. Record: @payload.sys_id

  2. Scripts > Refresh Requested

Your events for the left-hand list should like this:

Picture3.png

Conclusion

When you run the page you should be able to select services by clicking the links for each row on the right hand side, and remove them by clicking in the list on the left-hand side.  Each time you update one list the service should “ping-pong” back and forth, only appearing in each list once.

 

The way this works is that every time we modify the M2M table we update the filter with the “NOT IN” clause for all current selected services and then refresh the associated lists on the screen. 

 

It’s that simple!  Whenever you add or remove a record from that table merely update the data resource, which then updates the filter causing the right-hand list to automatically update.  Sweet!

 

PS: What about Checkboxes?

You may want to use a regular now-list component instead of the simple list in this demo, and instead of clicking each row and having it refresh instantly use the checkboxes and then your own "Add to list" type declarative action button

 

If so, you will notice that if the user does not have write access to the table on the right the checkboxes won't appear. That is because the list component doesn't render the checkboxes unless there is at least one declarative action visible that requires the checkbox (by default that's the "Edit" and "Delete" buttons, which don't appear if you can't edit the table).

 

To fix this go to your button's declarative action assignment (in sys_declarative_action_assignment) click "advanced" link and choose "Record Selection Required".  This will notify the list that the checkboxes are indeed needed and they will be rendered.

JonGLind_0-1670515132877.png

 

Version history
Last update:
‎12-08-2022 08:00 AM
Updated by:
Contributors