Creating and Using Modal Components in Agent Workspace

Agent Workspace is built on the great new Now Experience UI Framework. For those used to developing on the traditional ServiceNow interface, things have changed a bit! One of those things that’s changed is how you would create a modal dialog box, meaning you can say goodbye to GlideModal! This article describes how you can develop a Now UI Experience Framework component, and surface it in Agent Workspace in a modal in a way that’s connected to the form context you’re launching it from.

Prerequisites

Part of the answer is already covered on the official ServiceNow Documentation site, on the page “render a now component in a modal“. However, it covers only what to do once you have created a component, but not how to create a component that can interact with a modal itself.

Something worth noting is that it mentions you need to create a record in the sys_aw_registered_scripting_modal table, however the ACL’s applied to this table at least in the Orlando release of ServiceNow are set to only allow users with the maint role to do so. So the first thing you’ll need to do is modify those ACL’s. There’s one for each action (create, read, write, delete) you’ll need to modify. You can simply just enable the Admin overrides checkbox on each of these ACL’s for a quick-fix to allow users with the admin role to edit records in this table, or specifically modify the ACL’s the allow more granular permissions.

Deployment

Let’s first see what we need to do to get an already-created component from our local machine into an instance, and associate it with an Agent Workspace UI Action.

  1. From the directory of the component, use the now-cli deploy command to deploy it to your instance.
  2. As an admin, create a new record in the sys_aw_registered_scripting_modal table.
    • Component: point this reference field to the component you deployed in step 1.
    • API: a function name which you will use to launch this modal from a UI Action, for example openMyModalNow or openUserFeedbackModal. It can be anything you want, but it must be unique for the scope you’re creating this record in.
  3. Copy the value from the automatically populated Public API field. This will contain a concatenation separated by periods of g_modal + the scope you’re in + the API name you chose in the previous step, for example g_modal.global.openCompModalTest(). You can use this code in an Agent Workspace UI Action client script to open your modal.
  4. Create a UI Action applied to the table you want to open the modal from. Set an action name, and ensure one of the “workspace” options is enabled (button or menu). This will determined whether your UI action appears as a main button at the top right of the Agent Workspace screen, or hidden within the menu at the top right of the screen.
  5. In the workspace client script, use code similar to the below:
function onClick(g_form) {

    // Build the data you want to pass to your component here.
    // Example:
    var myOptions = [{
        id: 'it',
        label: 'IT Workflows'
    },{
        id: 'customer',
        label: 'Customer Workflows'
    },{
        id: 'employee',
        label: 'Employee Workflows'
    }];

    g_modal.global.openCompModalTest({
        title: 'Please select a workflow!',
        confirmTitle: "OK",
        cancelTitle: "Cancel",
        size: 'sm',
        height: 'md',
        params:{
            options: myOptions,
            selectedValue: myOptions[0].id
        }
    }).then(function (modalResult) {
        // Code to handle the result of your modal here.
        // Example:
        g_form.setValue('my_field', modalResult.selectedValue);
    });

}

Registered Scripting Modal Parameter

As you can see in the example above, the auto-generated functions you use to call your modal (e.g. g_modal.global.openCompModalTest()) accept an object as a parameter, which has a number of properties you can define on it.

  • title: the title of the modal dialog window
  • confirmTitle: the label of the “OK” button
  • cancelTitle: the label of the “Close” button
  • size: from testing I believe sm, lg, xl, and fw are accepted values
  • height: a height (in pixels) for the modal
  • params: the parameters to pass in to the inner component

The params property is probably the most important one to highlight. It’s how you can pass context from your form (for example, the sysId of the record you’re on, or the value from a form field obtained by g_form.getValue()) into your component. In the example above, I’ve hard-coded an array containing a list of items (ServiceNow workflows) all having both an id, and a label. The object supplied as params gets passed into the component inside the modal as it’s properties.

You can also see the auto-generated function returns a promise, and the function passed into then() that’s called when the promise is resolved will get a parameter passed into it. This parameter I’ve called modalResult in my example, and contains the data passed back to the UI Script from the modal.

Creating the Component

Now we know we can pass values into the component through the params property, and receive data from the component using the first parameter of the callback function that executes when the modal closes (modalResult in my example). We can now look into using these params inside a component, and broadcasting data out of the component into the callback function.

The component shown in the screenshot above is one I created called snc-component-modal-test. It simply accepts as properties two things:

  • options:
  • selectedValue

It shows a dropdown list of the options supplied to it (options), and accepts a default selected value (selectedValue).

import { createCustomElement } from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import '@servicenow/now-dropdown';
import styles from './styles.scss';

const handleSelect = ({action, dispatch}) => {
	dispatch('SN_SCRIPTED_MODAL#DATA_SET', {
		selectedValue: properties.selectedValue
	});
}

const view = ({properties}, dispatch) => {

	dispatch('SN_SCRIPTED_MODAL#DATA_SET', {
		selectedValue: properties.selectedValue
	});

	return (
		<div>
			<now-dropdown
				items={properties.options}
				size="md"
				select="single"
				selected-items={[properties.selectedValue]}
			/>
		</div>
	);
};

createCustomElement('snc-component-modal-test', {
	renderer: { type: snabbdom },
	view,
	styles,
	actionHandlers: {
		'NOW_DROPDOWN#SELECTED_ITEMS_SET': handleSelect
	},
	properties: {
		options: {
			default: [{
				label: 'First Option',
				id: 'one'
			}, {
				label: 'Option 2',
				id: 'two'
			}]
		},
		selectedValue: {
			default: 'one'
		}
	}
});

Some notes to explain the above:

  • I’m using the now-dropdown component so that I didn’t need to create a dropdown myself. You can install this using the command npm install @servicenow/now-dropdown. Other standard components are documented on the official ServiceNow Developer website.
  • The now-dropdown component has the action NOW_DROPDOWN#SELECTED_ITEMS_SET, which gets called when the selected item(s) change. To respond to this action, I have the handleSelect function being called.
  • The component built into Agent Workspace to create modals has the SN_SCRIPTED_MODAL#DATA_SET action, which you can dispatch to update the data that will be passed back to your client script.
    • I’m dispatching this action to set the the selectedValue property.
  • In the UI Action’s client script, I’m using this data from the selectedValue property to update my form using g_form as per a normal client script within the callback function that gets executed when the modal is closed.

Once you’ve created your component, you can deploy it to your instance as per the steps in the previous section. If you’ve already deployed the component, you’ll need to use the --force option to overwrite the existing component on the instance.

Conclusion

If you have any questions, or you’ve discovered some additional information about using Modals in Agent Workspace, please leave a comment below or reach out to me on twitter using my handle @dylanlindgren!