Martin Rudack
Giga Sage

bannerklein.jpg

 

 

At Knowledge26, ServiceNow introduced EmployeeWorks, the new enterprise-wide AI front door. One component of EmployeeWorks is Employee Slate, a modern employee experience built on the new AI Experience Framework (AIUX).

Under the hood, the AI Experience Framework creates widgets using Web Components based on lit.js and DaisyUI.

 

Currently, there is not much official documentation from ServiceNow. However, Semaphore Partners has done excellent work documenting what is already known for the ServiceNow community (you can check out their documentation here).

To add to this, I document the creation of my first widget created with the AI Experience Framework in this article

 

 

The Use Case

 

My main focus is on learning the technology rather than having a perfectly looking widget which can be deployed directly to Prod. I chose a simple use case:

A widget that displays a user's open private tasks. It includes:

  • Display the open tasks.
  • A button to close all open tasks at once.
  • An input field and a button to create a new private task.

This covers the fundamental CRUD actions: retrieving data, displaying it, and writing it back to the instance.

 

Let’s get started.

 

 

The Widget Builder

 

The AI Experience Framework comes with a new widget builder. You can access it on your instance at: <your-instance-url>/aiux/builder/widgets

 

new0.png

 

This gives you an overview of all available widgets. To start, simply click Create Widget.

 

new1.png

 

Right away, looking at the Widget Properties, it is clear this framework is designed with AI in mind. Alongside the standard description, we can suggest when this widget should be used. This allows the AI to dynamically decide which widget is best suited to present data to the user.

 

 

The Architecture

 

The widget consists of two major elements:

 

The Component

 Unlike traditional Service Portal widgets, AI Experience Widgets are no longer written in AngularJS. They are standard Web Components based on lit.js and DaisyUI. The Component tab holds the actual code for your Web Component.

 

componentDefault.png

 

 

The Server Script

 

This runs server-side and populates a global data object, which is then accessible inside the component. The Server Script can also receive data back from the component via the input object. This allows the widget to communicate with the ServiceNow instance.

 

ServerScriptDefault.png

 

 

Fetching and Displaying Data

 

Within the Server Script, we have access to all the standard server-side APIs we are used to. Once we fetch the necessary data, we store it in the global data object.

 

(function (data, options, input) {
    /* Edit your server-side script here */

    data.user = gs.getUser().getDisplayName();
    data.tasks = [];
    var grTask = new GlideRecord("vtb_task");
    grTask.addQuery("state",1);
    grTask.addQuery("owner", gs.getUserID());
    grTask.query();
    data.count = grTask.getRowCount();
    while(grTask.next()){
        data.tasks.push({sys_id: grTask.getUniqueValue(), short_description: grTask.getValue("short_description")});  
    }
   
})(data, options, input);

 

 

By default, the Component tab provides a basic Web Component template. Inside the render method, you define the HTML template used to display your content. You can use JavaScript expressions within the template to add dynamic values to the template.

 

return html`   
            <div>
                <h2 class="widget-heading text-sm font-medium mb-6">
                    ${this.headingTitle} ${this.data.user} you have <span class="aiux-badge aiux-badge-error"> ${this.data.count}</span> todos
                </h2>
                <table class="aiux-table aiux-table-zebra">
                    <thead>
                        <tr>
                            <th>sys_id</th>
                            <th>Short Description</th>
                        </tr>
                    </thead>
                    <tbody>
                    ${(this.data.tasks ?? []).map(task => html`
                       <tr>
                            <td>${task.sys_id}</td>
                            <td>${task.short_description}</td> 
                       </tr>
                    `)}
                    </tbody>
                </table>
            </div>
        `;

 

The Preview tab will give you a live look at the widget as you build it.

 

preview 1.png

 

 

Adding Interactivity

 

Now that we are fetching and displaying data, we need to interact with the instance. Let's add an input field to create new tasks, and buttons to submit or close existing ones.

First, we update the HTML template:

 

<div>
    <input @Input=${this._onInputChange} type="text" placeholder="Todo" class="aiux-input" />
</div>
<button type="button" class="aiux-btn aiux-btn-secondary" @click=${this._onDelete}>Close all</button>
<button type="button" class="aiux-btn aiux-btn-primary" @click=${this._onCreate}>Create</button>

 

Next, we handle the input state. When a user types in the input field, _onInputChange(event) is called. We need to add a taskText property to the component to store this value.

 

static properties = {
        headingTitle: { type: String, description: 'Title of the heading' },
        taskText: { type: String, description: 'Text of the todo' }
    }

 

    _onInputChange(event){
        this.taskText = event.target.value;
    }

 

When the user clicks "Create", we send this text back to the instance. This is done in the _onCreate() function.

 

    _onCreate(){
        this.server
            .get({
                action: 'create',
                text: this.taskText,
            })
            .then(()=>{
                this.server.update();
            });      
    }

 

 

Because we are extending the AIUXWidgetElement class, we can use this.server.get(request) to call the Server Script.

 

 

Processing Actions on the Server

 

In the Server script we can use the input object to access the action and text attributes and create a new task. Because we want to see this new task directly in the widget without doing a manual refresh, we use the promise returned by that function to call the server.update() function to update the server data.

 

In the Server Script we can check the input object to see whether a button was clicked. Then we create a new task or close all open tasks of the user.

 

if (input && input.action) {
        if (input.action == "closeAll") {
            var grTask = new GlideRecord("vtb_task");
            grTask.addQuery("owner", gs.getUserID());
            grTask.addQuery("state", 1);
            grTask.query();
            while (grTask.next()) {
                grTask.setValue("state",3);
                grTask.update();
            }
        }
        else if(input.action == "create"){
            var grTask = new GlideRecord("vtb_task");
            grTask.setValue("short_description",input.text||"");
            grTask.setValue("owner", gs.getUserID());
            grTask.setValue("state", 1);
            grTask.insert();
        }
    }

 

 

That’s the final result of the Widget.

 

result.png

 

 

Version history
Last update:
2 hours ago
Updated by:
Contributors