- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
6 hours ago - edited 6 hours ago
Hello Community,
ServiceNow Workspaces are designed for fast, focused work, but many use cases still require custom actions and guided user interaction. A common need is to let users click a button, select records from a list, and perform an action - without leaving the Workspace.
In this article, we’ll demonstrate how a UI Action, UI Page, and GlideModal work together in Service Operations Workspace to add a button on the Incident form, display a table list of Task Templates (sys_template) in a popup, and create corresponding Task records based on user selection.
Requirement -
* Add a Route button (UI Action – `sys_ui_action`) on the Incident (`incident`) form in Service Operations Workspace.
* When the user clicks the button, a GlideModal opens a UI Page (`sys_ui_page`) that displays a searchable, multi-select list of Task Profiles from the Templates table (`sys_template`) where the table is `task`.
* The user selects one or more templates and commits the action.
* For each selected template, the system creates a child Task record (`task`) linked to the same Incident using the parent field, without changing the Incident assignment.
* The Incident context (sys_id) is passed to the server-side logic so that workflows, flows, or integrations can be triggered after task creation.
Core ServiceNow Components
To implement this functionality, you must understand three foundational elements:
UI Action: A button or link that triggers a script. In a Workspace, it acts as the client-side entry point to launch modals or process data on the form.
UI Page: A custom interface built with HTML, Jelly, and Client Scripts. It is used here to create the modal dialog that displays the list of templates.
Script Include: A server-side script library. When set to Client Callable, it allows the UI Page to communicate with the database to create records or perform complex logic.
Step 1: UI Action - Create the Button on record form on Workspace
To add the Route button to the Incident record in the Workspace:
Navigate to System UI > UI Actions (sys_ui_action) and create a new record for the Incident(incident) table.
- Name: Route //provide relavant name
- Action Name: openRouteModal //Must be unique
- onClick: openRouteModal(); //This function will execute when user click on button
- Client: True
- Active: True
- List v2 Compatible: True
- Form Button: True
- Workspace Form Button: True
Enable the Client checkbox and set Format for Configurable Workspace to true.
In the Client Script V2 field, use the g_mdodal API to display your UI Page:
Workspace Client Script -
function openRouteModal() {
var incidentId = g_form.getUniqueValue();
g_modal.showFrame({
title: 'Select Task Profiles',
url: '/route_tasks_modal.do?sysparm_parent_id=' + incidentId,
size: 'lg',
height: '600px',
callback: function(retVal) {
// Refreshes the tasks related list once modal is closed
g_form.refreshRelatedLists();
}
});
}Step 2: Script Include - Server-Side Processing
The Script Include performs the database operations to generate the child tasks from the selected templates. Navigate to System Definition > Script Includes
Name: u_TaskProfileAjax
API Name: global.u_TaskProfileAjax
Glide AJAX enabled : True (Essential for UI Page access)
Active: True
- Script:
var u_TaskProfileAjax = Class.create();
u_TaskProfileAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// Global variables for the class
LOG_PREFIX: "[u_TaskProfileAjax]: ",
TABLE_INCIDENT_TASK: "incident_task",
TABLE_TEMPLATE: "sys_template",
EVENT_NAME: "incident.route.tasks.created",
/**
* Entry point for GlideAjax
*/
createTasksFromTemplates: function() {
var params = {
parentId: this.getParameter('sysparm_parent_id'),
templateIds: this.getParameter('sysparm_template_ids')
};
this._logInfo("Process started for Parent: " + params.parentId);
if (!this._validateParams(params)) {
return "error_invalid_params";
}
return this._processTemplateCreation(params.parentId, params.templateIds.split(','));
},
/**
* Internal logic handler to process record creation
*/
_processTemplateCreation: function(parentId, templateIds) {
var createdTaskIds = [];
var successCount = 0;
for (var i = 0; i < templateIds.length; i++) {
var templateId = templateIds[i];
var templateGr = new GlideRecord(this.TABLE_TEMPLATE);
if (templateGr.get(templateId)) {
var targetTable = templateGr.getValue('table') || 'task';
var newTaskGr = new GlideRecord(targetTable);
newTaskGr.initialize();
// Apply Template logic
var template = GlideTemplate.get(templateGr.getUniqueValue());
if (template) {
template.apply(newTaskGr);
}
// Best Practice: Force Context Relationship
newTaskGr.parent = parentId;
if (targetTable === this.TABLE_INCIDENT_TASK) {
newTaskGr.incident = parentId;
}
var newSysId = newTaskGr.insert();
if (newSysId) {
successCount++;
createdTaskIds.push(newSysId);
this._logInfo("Created " + targetTable + " : " + newSysId);
} else {
this._logError("Failed to insert record for template: " + templateId);
}
} else {
this._logError("Template not found for ID: " + templateId);
}
}
// Trigger integrations/workflows via Event Queue
if (createdTaskIds.length > 0) {
this._triggerDownstreamLogic(parentId, createdTaskIds);
}
return successCount.toString();
},
/**
* Validates input parameters
*/
_validateParams: function(params) {
if (!params.parentId || !params.templateIds) {
this._logError("Validation failed. Missing Parent ID or Template IDs.");
return false;
}
return true;
},
/**
* Fires event to trigger Flows, Notifications, or Integrations
*/
_triggerDownstreamLogic: function(parentId, taskIds) {
var incGr = new GlideRecord('incident');
if (incGr.get(parentId)) {
gs.eventQueue(this.EVENT_NAME, incGr, taskIds.join(","), gs.getUserName());
this._logInfo("Event " + this.EVENT_NAME + " queued.");
}
},
/**
* Standardized Logging
*/
_logInfo: function(msg) {
gs.info(this.LOG_PREFIX + msg);
},
_logError: function(msg) {
gs.error(this.LOG_PREFIX + msg);
},
type: 'u_TaskProfileAjax'
});
Step 3: UI Page - Create the Modal Interface
The UI Page acts as the bridge between the Workspace and the server. It displays the list of templates and captures the user's selection. Navigate to System UI > UI Pages
Name: route_tasks_modal
Direct: false
Category: general
- HTML:
This section renders an iframe containing a filtered list of templates and a "Commit" button.
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<g:ui_form onsubmit="return false;">
Parent : <input id="parent_incident_id" value="${sysparm_parent_id}" />
<!--
<div style="padding: 10px; font-family: sans-serif;">
<div id="parent_incident_id" value="${sysparm_parent_id}" />
<p><strong>Step 1:</strong> Select profiles from the list.</p>
<p><strong>Step 2:</strong> Review selection and click <strong>Create Tasks</strong>.</p>
</div>
-->
<div id="targetList">
<iframe
id="template_iframe"
src="sys_template_list.do?sysparm_query=table%3Dtask%5Eactive%3Dtrue&sysparm_view=default&sysparm_clear_stack=true"
style="min-height:400px; width:100%; border:1px solid #ddd;"
frameborder="0">
</iframe>
</div>
<div id="selection_summary" style="padding: 10px; background: #f4f4f4; border: 1px solid #ddd; border-top: none; font-family: monospace; font-size: 11px;">
<strong>Selected Sys_IDs:</strong>
<div id="display_ids" style="color: #005eda; word-break: break-all; margin-top: 5px;">None</div>
</div>
<footer class="modal-footer" style="margin-top: 15px; text-align: right;">
<!-- <button class="btn btn-default" type="button" onclick="handleWorkspaceClose()">Cancel</button> -->
<button class="btn btn-primary" type="button" id="submit_button" onclick="processIframeSelection()">Commit</button>
</footer>
</g:ui_form>
</j:jelly>- Client Script:
This script handles the selection logic and calls the Script Include via GlideAjax.
(function() {
var LOG_PREFIX = "[u_TaskProfileAjax_UI]: ";
var SCRIPT_INCLUDE = "u_TaskProfileAjax";
var IFRAME_ID = "template_iframe";
function _log(msg, isError) {
var fullMsg = LOG_PREFIX + msg;
if (isError) console.error(fullMsg); else console.info(fullMsg);
}
setInterval(function() {
var ids = _getSelectedTemplateIds();
var display = document.getElementById('display_ids');
if (display) {
display.innerHTML = ids.length > 0 ? ids.join(', ') : 'None';
}
}, 1000);
/**
* CLOSURE LOGIC: Optimized for Workspace
*/
window.handleWorkspaceClose = function() {
_log("Closing modal window.");
// 1. Try modern Workspace Modal API first
if (window.top.g_modal && typeof window.top.g_modal.destroy === 'function') {
window.top.g_modal.destroy();
}
// 2. Try the iframe message approach
else if (window.parent && window.parent.postMessage) {
window.parent.postMessage({ msg: 'ui_action_close_modal' }, '*');
}
// 3. Fallback for Classic UI
else if (typeof GlideModalWindow !== 'undefined' && GlideModalWindow.get()) {
GlideModalWindow.get().destroy();
}
};
function _getSelectedTemplateIds() {
var iframe = document.getElementById(IFRAME_ID);
if (!iframe || !iframe.contentWindow) return [];
var w = iframe.contentWindow;
try {
if (w.g_list) return (w.g_list.getChecked() || '').split(',').filter(Boolean);
var boxes = iframe.contentDocument.querySelectorAll('input[name="cb_sys_template"]:checked');
var ids = [];
for (var i = 0; i < boxes.length; i++) { ids.push(boxes[i].value); }
return ids;
} catch (e) { return []; }
}
/**
* USER FRIENDLY SUBMISSION
*/
window.processIframeSelection = function() {
var parentId = document.getElementById('parent_incident_id').value;
var selectedIds = _getSelectedTemplateIds();
if (!selectedIds.length) {
alert('Please select at least one Task Profile.');
return;
}
// --- STEP 1: UI Feedback ---
var btn = document.getElementById('submit_button');
btn.disabled = true;
btn.className = "btn btn-disabled"; // Visual change
btn.innerHTML = '<span class="icon-loading" style="margin-right:5px"></span> Creating Tasks...';
_log("Sending GlideAjax request...");
// --- STEP 2: Server Call ---
var ga = new GlideAjax(SCRIPT_INCLUDE);
ga.addParam('sysparm_name', 'createTasksFromTemplates');
ga.addParam('sysparm_parent_id', parentId);
ga.addParam('sysparm_template_ids', selectedIds.join(','));
ga.getXMLAnswer(function(answer) {
_log("Server Response: " + answer);
// --- STEP 3: Handle Parent UI Refresh ---
// In Workspace, we use window.parent to access g_form
var parentGForm = window.parent.g_form;
if (answer.indexOf("error") > -1) {
if (parentGForm) parentGForm.addErrorMessage("Failed to create tasks. Check system logs.");
btn.disabled = false;
btn.innerHTML = "Create Tasks";
} else {
// SUCCESS: Notify user on the ACTUAL record page
if (parentGForm) {
parentGForm.clearMessages();
parentGForm.addInfoMessage("Successfully routed " + answer + " task(s).");
// Force-refresh the related lists so the tasks appear immediately
parentGForm.refreshRelatedLists();
}
// --- STEP 4: Close Modal Immediately ---
handleWorkspaceClose();
}
});
};
})();
DEMO -
If you find it helpful in any way, please mark it Helpful or hit a Like button. It will help others to find this helpful article.
