The CreatorCon Call for Content is officially open! Get started here.

Martin Rudack
Mega Sage

Note: This is part four of a series of articles where we will develop Custom Components with the Now Experience UI Framework. This series started with two articles about custom GraphQL APIs. We are going to use these to fetch data from the platform for our component. In the third article, we created the first component and displayed it in the Contextual Side Panel. Custom Components - Custom GraphQL API Part1 | Custom Components - Custom GraphQL API Part2 | Custom Components - Use GraphQL and Contextual Side Panel

 

Now that we have deployed our first component to the instance and it is visible in the Agent Workspace, we need to talk about one important point, User Experience. Technically our component does what it should do. It shows the needed information to the agent. But in reality, this component does not provide any value. An agent would not be satisfied with the use of this component and the acceptance to use it would tend towards zero.

 

What do we need to do?

The agent should be able to quickly see the request of the contact. There should be no overhead and the component should look like everything else in the Workspace. But we don't need to do everything on our own. 

ServiceNow wants to enable everyone to build cohesive, consumer-grade user experience on the Now Platform. That’s why they created the Now Experience Design System with the Now Experience Components as one major part of it. These components come with a lot of built-in capabilities that we can leverage without much effort, like:

  • Accessibility (WCAG 2.0)
  • Desktop and Mobile-Web Ready
  • Support for Internationalization
  • Compatibility with multiple Browser

And that all with a cohesive design and great documentation. Besides the technical documentation, so that we can use the components in our projects, we also get a documentation of the intended usage of the component so that we can use them in the right way.

In this case, the usage of now-card would make sense.

“Use a card to help the user make a decision or quickly view information.”

That’s what we want to do. It also tells us what we'd better not do. This would be one example:

 

find_real_file.png

(source: https://developer.servicenow.com/dev.do#!/reference/now-experience/paris/now-components/now-card/usa...

 

 Visite the documentation for Now Experience Component for more information. 

Another Component that we are going to use, to support the agents to quickly identify issues, is the now-highlighted-value.

The Highlighted Value is a label, with or without an Icon, that has a background color as a highlight. This can be used to either highlight a status, to quickly separate the important things from the less important things, or it can be used as an indication of a category to group similar content.

 

find_real_file.png

(source: https://developer.servicenow.com/dev.do#!/reference/now-experience/paris/now-components/now-highligh...)

 

One important point is that you are aware that certain colors are used to convey a specific message. Don’t use a critical status color to group non-critical stuff. Better pick a category color.

 

Whit that in mind, let's make our component pretty.

 

Each Request should be one card that contains a separate card for every Requested Item. We use the now-label-value-tabbed Component to display the attributes and the values. First, we need to install the packages and import them into our project.

npm install @servicenow/now-card
npm install @servicenow/now-highlighted-value
npm install @servicenow/now-label-value

 

 

import "@servicenow/now-card";
import "@servicenow/now-highlighted-value";
import "@servicenow/now-label-value";

 

There are three pieces of information that we want to show in the header of the request card. A shopping cart icon, to indicate that this is a request, the number of the request, and a Highlighted Value for the status of the request.

 

    <now-card>
        <now-card-header tagline={{ "label": request.number, "icon": "shopping-cart-outline" }} >

 

The card header provides a slot called “metadata” which we can use for the Highlighted Value.

{request.state == "Closed Incomplete" ?
    <now-highlighted-value slot="metadata" color="critical" label={request.state}></now-highlighted-value> : request.state == "Closed Complete" ?
        <now-highlighted-value slot="metadata" color="positive" label={request.state}></now-highlighted-value> :
        <now-highlighted-value slot="metadata" color="info" label={request.state}></now-highlighted-value>
}
</now-card-header >
<now-card-divider></now-card-divider>

 

Inside the request card, we place our cards for the Requested Items. Here we change the icon to something different. To keep the card small we only show the Catalog Item and the Stage using the now-label-value-tabbed.

{request.items.map(item =>
    <now-card>
        <now-card-header tagline={{ "label": item.number, "icon": "configuration-item-outline" }} > </now-card-header>
        <now-card-divider></now-card-divider>
        <now-label-value-tabbed size="sm" items={[{ label: "Catalog Item", value: item.cat_item }, { label: "Stage", value: item.stage }]} />
    </now-card>
)}
</now-card>

 

 

This is what it should look like:

find_real_file.png

 

 

Because the variables are not always necessary, we don’t want to show them directly to keep the card small. So we use a now-popover together with a now-button-stateful. If the Requested Item has no variables, we disable the Button.

 

<now-popover>
    <now-button-stateful slot="trigger" icon="ellipsis-h-outline" disabled={item.variables.length == 0} />
    <now-card slot="content">
        <now-card-header tagline={{ "label": "Variables" }} > </now-card-header>
        <now-card-divider></now-card-divider>
        <now-label-value-tabbed size="sm" items={item.variables.map(variable => { return { label: variable.question_text, value: variable.value } })} />
    </now-card>
</now-popover>

 

This is how the end result looks like when we deploy it to our instance.

find_real_file.png

 

 

 

In the next article, we will look at scripted modals. 

Custom Components - Scripted Modal

 

 

 

 

 

Complete index.js

import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';

import { createCustomElement, actionTypes } from '@servicenow/ui-core';
const { COMPONENT_BOOTSTRAPPED } = actionTypes;
import { createGraphQLEffect } from '@servicenow/ui-effect-graphql';

import "@servicenow/now-card";
import "@servicenow/now-highlighted-value";
import "@servicenow/now-label-value";
import "@servicenow/now-button";
import "@servicenow/now-popover";

const DATA_FETCH_SUCCEEDED = 'DATA_FETCH_SUCCEEDED';
const DATA_FETCH_FAILED = 'DATA_FETCH_FAILED';

const requestQuery = `
query($contactID: ID!){
    x413019 {
        agineo {
            contactRequest(contactID: $contactID) {
                number
                state
                items{
                    number
                    stage
                    cat_item
                    variables{
                        question_text
                        value
                    }  
                }
            }
        }
    }
}`;

const dataFetchHandler = createGraphQLEffect(requestQuery, {
    variableList: ['contactID'],
    successActionType: DATA_FETCH_SUCCEEDED,
    errorActionType: DATA_FETCH_FAILED
});

const view = (state, { updateState }) => {
    return (
        <div>
            {state.requests.map(request =>
                <now-card>
                    <now-card-header tagline={{ "label": request.number, "icon": "shopping-cart-outline" }} >
                        {request.state == "Closed Incomplete" ?
                            <now-highlighted-value slot="metadata" color="critical" label={request.state}></now-highlighted-value> : request.state == "Closed Complete" ?
                                <now-highlighted-value slot="metadata" color="positive" label={request.state}></now-highlighted-value> :
                                <now-highlighted-value slot="metadata" color="info" label={request.state}></now-highlighted-value>}
                    </now-card-header>
                    <now-card-divider></now-card-divider>
                    {request.items.map(item =>
                        <now-card>
                            <now-card-header tagline={{ "label": item.number, "icon": "configuration-item-outline" }} > </now-card-header>
                            <now-card-divider></now-card-divider>
                            <now-label-value-tabbed size="sm" items={[{ label: "Catalog Item", value: item.cat_item }, { label: "Stage", value: item.stage }]} />
                            <now-popover>
                                <now-button-stateful slot="trigger" icon="ellipsis-h-outline" disabled={item.variables.length == 0} />
                                <now-card slot="content">
                                    <now-card-header tagline={{ "label": "Variables" }} > </now-card-header>
                                    <now-card-divider></now-card-divider>
                                    <now-label-value-tabbed size="sm" items={item.variables.map(variable => { return { label: variable.question_text, value: variable.value } })} />
                                </now-card>
                            </now-popover>
                        </now-card>
                    )}
                </now-card>
            )}
        </div>
    );
};

createCustomElement('x-413019-contact-req', {
    renderer: { type: snabbdom },
    view,
    styles,
    initialState: {
        requests: []
    },
    properties: {
        sysId: { default: "9951981c2f996410e1717f572799b6df" }
    },
    actionHandlers: {
        "FETCH_DATA_REQUESTED": dataFetchHandler,
        [COMPONENT_BOOTSTRAPPED]: (coeffects) => {
            coeffects.dispatch("FETCH_DATA_REQUESTED", { contactID: coeffects.properties.sysId });
        },
        [DATA_FETCH_SUCCEEDED]: (coeffects) => { console.log(coeffects.action.payload); coeffects.updateState({ requests: coeffects.action.payload.data.x413019.agineo.contactRequest }) }
    }
});

 

 

 

 

 

Comments
Ran6
Tera Contributor

hey, so i need something more simple, only fetch current incident number into the component. how can i do that? 

Version history
Last update:
‎01-07-2021 06:16 AM
Updated by: